@adonis-agora/agent 0.76.3 → 0.77.1

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 (279) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/providers/agent_provider.d.ts.map +1 -1
  3. package/dist/providers/agent_provider.js +9 -0
  4. package/dist/providers/agent_provider.js.map +1 -1
  5. package/dist/providers/mcp_provider.d.ts.map +1 -1
  6. package/dist/providers/mcp_provider.js +46 -10
  7. package/dist/providers/mcp_provider.js.map +1 -1
  8. package/dist/providers/poppy_connections_provider.d.ts +17 -0
  9. package/dist/providers/poppy_connections_provider.d.ts.map +1 -0
  10. package/dist/providers/poppy_connections_provider.js +186 -0
  11. package/dist/providers/poppy_connections_provider.js.map +1 -0
  12. package/dist/providers/poppy_provider.d.ts +12 -0
  13. package/dist/providers/poppy_provider.d.ts.map +1 -0
  14. package/dist/providers/poppy_provider.js +159 -0
  15. package/dist/providers/poppy_provider.js.map +1 -0
  16. package/dist/src/a2a/handler.d.ts.map +1 -1
  17. package/dist/src/a2a/handler.js +4 -2
  18. package/dist/src/a2a/handler.js.map +1 -1
  19. package/dist/src/a2a/turn.d.ts +27 -4
  20. package/dist/src/a2a/turn.d.ts.map +1 -1
  21. package/dist/src/a2a/turn.js +66 -12
  22. package/dist/src/a2a/turn.js.map +1 -1
  23. package/dist/src/action-proposal-approval.d.ts.map +1 -1
  24. package/dist/src/action-proposal-approval.js +3 -3
  25. package/dist/src/action-proposal-approval.js.map +1 -1
  26. package/dist/src/action-proposal-listeners.d.ts +12 -0
  27. package/dist/src/action-proposal-listeners.d.ts.map +1 -0
  28. package/dist/src/action-proposal-listeners.js +27 -0
  29. package/dist/src/action-proposal-listeners.js.map +1 -0
  30. package/dist/src/action-proposal-receipt.d.ts +4 -0
  31. package/dist/src/action-proposal-receipt.d.ts.map +1 -1
  32. package/dist/src/action-proposal-receipt.js +3 -1
  33. package/dist/src/action-proposal-receipt.js.map +1 -1
  34. package/dist/src/action-proposal-worker.d.ts +6 -1
  35. package/dist/src/action-proposal-worker.d.ts.map +1 -1
  36. package/dist/src/action-proposal-worker.js +10 -1
  37. package/dist/src/action-proposal-worker.js.map +1 -1
  38. package/dist/src/agent-loop.d.ts.map +1 -1
  39. package/dist/src/agent-loop.js +8 -5
  40. package/dist/src/agent-loop.js.map +1 -1
  41. package/dist/src/channels/handler.d.ts +10 -0
  42. package/dist/src/channels/handler.d.ts.map +1 -1
  43. package/dist/src/channels/handler.js +47 -1
  44. package/dist/src/channels/handler.js.map +1 -1
  45. package/dist/src/channels/texts.d.ts +9 -0
  46. package/dist/src/channels/texts.d.ts.map +1 -1
  47. package/dist/src/channels/texts.js +4 -0
  48. package/dist/src/channels/texts.js.map +1 -1
  49. package/dist/src/client/sse.d.ts.map +1 -1
  50. package/dist/src/client/sse.js +3 -0
  51. package/dist/src/client/sse.js.map +1 -1
  52. package/dist/src/define_config.d.ts +3 -0
  53. package/dist/src/define_config.d.ts.map +1 -1
  54. package/dist/src/define_config.js.map +1 -1
  55. package/dist/src/genui/channels.d.ts +5 -0
  56. package/dist/src/genui/channels.d.ts.map +1 -1
  57. package/dist/src/genui/channels.js +11 -0
  58. package/dist/src/genui/channels.js.map +1 -1
  59. package/dist/src/index.d.ts +2 -1
  60. package/dist/src/index.d.ts.map +1 -1
  61. package/dist/src/index.js +2 -1
  62. package/dist/src/index.js.map +1 -1
  63. package/dist/src/mcp/auth.d.ts +17 -0
  64. package/dist/src/mcp/auth.d.ts.map +1 -1
  65. package/dist/src/mcp/auth.js +56 -1
  66. package/dist/src/mcp/auth.js.map +1 -1
  67. package/dist/src/mcp/discovery.d.ts +13 -2
  68. package/dist/src/mcp/discovery.d.ts.map +1 -1
  69. package/dist/src/mcp/discovery.js +17 -2
  70. package/dist/src/mcp/discovery.js.map +1 -1
  71. package/dist/src/mcp/index.d.ts +1 -1
  72. package/dist/src/mcp/index.d.ts.map +1 -1
  73. package/dist/src/mcp/index.js.map +1 -1
  74. package/dist/src/mcp/scope.d.ts +13 -0
  75. package/dist/src/mcp/scope.d.ts.map +1 -0
  76. package/dist/src/mcp/scope.js +24 -0
  77. package/dist/src/mcp/scope.js.map +1 -0
  78. package/dist/src/mcp/server.d.ts +6 -1
  79. package/dist/src/mcp/server.d.ts.map +1 -1
  80. package/dist/src/mcp/server.js +1 -1
  81. package/dist/src/mcp/server.js.map +1 -1
  82. package/dist/src/poppy/auth.d.ts +77 -0
  83. package/dist/src/poppy/auth.d.ts.map +1 -0
  84. package/dist/src/poppy/auth.js +16 -0
  85. package/dist/src/poppy/auth.js.map +1 -0
  86. package/dist/src/poppy/conversations.d.ts +249 -0
  87. package/dist/src/poppy/conversations.d.ts.map +1 -0
  88. package/dist/src/poppy/conversations.js +1017 -0
  89. package/dist/src/poppy/conversations.js.map +1 -0
  90. package/dist/src/poppy/define_config.d.ts +98 -0
  91. package/dist/src/poppy/define_config.d.ts.map +1 -0
  92. package/dist/src/poppy/define_config.js +11 -0
  93. package/dist/src/poppy/define_config.js.map +1 -0
  94. package/dist/src/poppy/endpoints.d.ts +25 -0
  95. package/dist/src/poppy/endpoints.d.ts.map +1 -0
  96. package/dist/src/poppy/endpoints.js +31 -0
  97. package/dist/src/poppy/endpoints.js.map +1 -0
  98. package/dist/src/poppy/handler.d.ts +80 -0
  99. package/dist/src/poppy/handler.d.ts.map +1 -0
  100. package/dist/src/poppy/handler.js +362 -0
  101. package/dist/src/poppy/handler.js.map +1 -0
  102. package/dist/src/poppy/index.d.ts +17 -0
  103. package/dist/src/poppy/index.d.ts.map +1 -0
  104. package/dist/src/poppy/index.js +9 -0
  105. package/dist/src/poppy/index.js.map +1 -0
  106. package/dist/src/poppy/protocol.d.ts +132 -0
  107. package/dist/src/poppy/protocol.d.ts.map +1 -0
  108. package/dist/src/poppy/protocol.js +178 -0
  109. package/dist/src/poppy/protocol.js.map +1 -0
  110. package/dist/src/poppy/redis-store.d.ts +27 -0
  111. package/dist/src/poppy/redis-store.d.ts.map +1 -0
  112. package/dist/src/poppy/redis-store.js +148 -0
  113. package/dist/src/poppy/redis-store.js.map +1 -0
  114. package/dist/src/poppy/runtime.d.ts +6 -0
  115. package/dist/src/poppy/runtime.d.ts.map +1 -0
  116. package/dist/src/poppy/runtime.js +12 -0
  117. package/dist/src/poppy/runtime.js.map +1 -0
  118. package/dist/src/poppy/server_middleware.d.ts +11 -0
  119. package/dist/src/poppy/server_middleware.d.ts.map +1 -0
  120. package/dist/src/poppy/server_middleware.js +16 -0
  121. package/dist/src/poppy/server_middleware.js.map +1 -0
  122. package/dist/src/poppy/store.d.ts +207 -0
  123. package/dist/src/poppy/store.d.ts.map +1 -0
  124. package/dist/src/poppy/store.js +444 -0
  125. package/dist/src/poppy/store.js.map +1 -0
  126. package/dist/src/poppy-client/api.d.ts +36 -0
  127. package/dist/src/poppy-client/api.d.ts.map +1 -0
  128. package/dist/src/poppy-client/api.js +77 -0
  129. package/dist/src/poppy-client/api.js.map +1 -0
  130. package/dist/src/poppy-client/bin/poppy-agent.d.ts +3 -0
  131. package/dist/src/poppy-client/bin/poppy-agent.d.ts.map +1 -0
  132. package/dist/src/poppy-client/bin/poppy-agent.js +9 -0
  133. package/dist/src/poppy-client/bin/poppy-agent.js.map +1 -0
  134. package/dist/src/poppy-client/browser.d.ts +24 -0
  135. package/dist/src/poppy-client/browser.d.ts.map +1 -0
  136. package/dist/src/poppy-client/browser.js +83 -0
  137. package/dist/src/poppy-client/browser.js.map +1 -0
  138. package/dist/src/poppy-client/cli/main.d.ts +2 -0
  139. package/dist/src/poppy-client/cli/main.d.ts.map +1 -0
  140. package/dist/src/poppy-client/cli/main.js +776 -0
  141. package/dist/src/poppy-client/cli/main.js.map +1 -0
  142. package/dist/src/poppy-client/cli/prompt.d.ts +13 -0
  143. package/dist/src/poppy-client/cli/prompt.d.ts.map +1 -0
  144. package/dist/src/poppy-client/cli/prompt.js +50 -0
  145. package/dist/src/poppy-client/cli/prompt.js.map +1 -0
  146. package/dist/src/poppy-client/company.d.ts +87 -0
  147. package/dist/src/poppy-client/company.d.ts.map +1 -0
  148. package/dist/src/poppy-client/company.js +336 -0
  149. package/dist/src/poppy-client/company.js.map +1 -0
  150. package/dist/src/poppy-client/conversations.d.ts +89 -0
  151. package/dist/src/poppy-client/conversations.d.ts.map +1 -0
  152. package/dist/src/poppy-client/conversations.js +332 -0
  153. package/dist/src/poppy-client/conversations.js.map +1 -0
  154. package/dist/src/poppy-client/discovery.d.ts +41 -0
  155. package/dist/src/poppy-client/discovery.d.ts.map +1 -0
  156. package/dist/src/poppy-client/discovery.js +217 -0
  157. package/dist/src/poppy-client/discovery.js.map +1 -0
  158. package/dist/src/poppy-client/dpop.d.ts +19 -0
  159. package/dist/src/poppy-client/dpop.d.ts.map +1 -0
  160. package/dist/src/poppy-client/dpop.js +43 -0
  161. package/dist/src/poppy-client/dpop.js.map +1 -0
  162. package/dist/src/poppy-client/errors.d.ts +50 -0
  163. package/dist/src/poppy-client/errors.d.ts.map +1 -0
  164. package/dist/src/poppy-client/errors.js +55 -0
  165. package/dist/src/poppy-client/errors.js.map +1 -0
  166. package/dist/src/poppy-client/http.d.ts +40 -0
  167. package/dist/src/poppy-client/http.d.ts.map +1 -0
  168. package/dist/src/poppy-client/http.js +95 -0
  169. package/dist/src/poppy-client/http.js.map +1 -0
  170. package/dist/src/poppy-client/identity.d.ts +61 -0
  171. package/dist/src/poppy-client/identity.d.ts.map +1 -0
  172. package/dist/src/poppy-client/identity.js +111 -0
  173. package/dist/src/poppy-client/identity.js.map +1 -0
  174. package/dist/src/poppy-client/identity_server.d.ts +29 -0
  175. package/dist/src/poppy-client/identity_server.d.ts.map +1 -0
  176. package/dist/src/poppy-client/identity_server.js +84 -0
  177. package/dist/src/poppy-client/identity_server.js.map +1 -0
  178. package/dist/src/poppy-client/index.d.ts +19 -0
  179. package/dist/src/poppy-client/index.d.ts.map +1 -0
  180. package/dist/src/poppy-client/index.js +18 -0
  181. package/dist/src/poppy-client/index.js.map +1 -0
  182. package/dist/src/poppy-client/keys.d.ts +13 -0
  183. package/dist/src/poppy-client/keys.d.ts.map +1 -0
  184. package/dist/src/poppy-client/keys.js +31 -0
  185. package/dist/src/poppy-client/keys.js.map +1 -0
  186. package/dist/src/poppy-client/llm.d.ts +31 -0
  187. package/dist/src/poppy-client/llm.d.ts.map +1 -0
  188. package/dist/src/poppy-client/llm.js +230 -0
  189. package/dist/src/poppy-client/llm.js.map +1 -0
  190. package/dist/src/poppy-client/mcp.d.ts +16 -0
  191. package/dist/src/poppy-client/mcp.d.ts.map +1 -0
  192. package/dist/src/poppy-client/mcp.js +57 -0
  193. package/dist/src/poppy-client/mcp.js.map +1 -0
  194. package/dist/src/poppy-client/open_url.d.ts +3 -0
  195. package/dist/src/poppy-client/open_url.d.ts.map +1 -0
  196. package/dist/src/poppy-client/open_url.js +13 -0
  197. package/dist/src/poppy-client/open_url.js.map +1 -0
  198. package/dist/src/poppy-client/signin.d.ts +59 -0
  199. package/dist/src/poppy-client/signin.d.ts.map +1 -0
  200. package/dist/src/poppy-client/signin.js +210 -0
  201. package/dist/src/poppy-client/signin.js.map +1 -0
  202. package/dist/src/poppy-client/sse.d.ts +15 -0
  203. package/dist/src/poppy-client/sse.d.ts.map +1 -0
  204. package/dist/src/poppy-client/sse.js +74 -0
  205. package/dist/src/poppy-client/sse.js.map +1 -0
  206. package/dist/src/poppy-client/store.d.ts +89 -0
  207. package/dist/src/poppy-client/store.d.ts.map +1 -0
  208. package/dist/src/poppy-client/store.js +79 -0
  209. package/dist/src/poppy-client/store.js.map +1 -0
  210. package/dist/src/poppy-client/token_client.d.ts +69 -0
  211. package/dist/src/poppy-client/token_client.d.ts.map +1 -0
  212. package/dist/src/poppy-client/token_client.js +154 -0
  213. package/dist/src/poppy-client/token_client.js.map +1 -0
  214. package/dist/src/poppy-client/types.d.ts +184 -0
  215. package/dist/src/poppy-client/types.d.ts.map +1 -0
  216. package/dist/src/poppy-client/types.js +8 -0
  217. package/dist/src/poppy-client/types.js.map +1 -0
  218. package/dist/src/poppy-client/util.d.ts +42 -0
  219. package/dist/src/poppy-client/util.d.ts.map +1 -0
  220. package/dist/src/poppy-client/util.js +122 -0
  221. package/dist/src/poppy-client/util.js.map +1 -0
  222. package/dist/src/poppy-connections/config.d.ts +134 -0
  223. package/dist/src/poppy-connections/config.d.ts.map +1 -0
  224. package/dist/src/poppy-connections/config.js +44 -0
  225. package/dist/src/poppy-connections/config.js.map +1 -0
  226. package/dist/src/poppy-connections/crypto.d.ts +12 -0
  227. package/dist/src/poppy-connections/crypto.d.ts.map +1 -0
  228. package/dist/src/poppy-connections/crypto.js +36 -0
  229. package/dist/src/poppy-connections/crypto.js.map +1 -0
  230. package/dist/src/poppy-connections/http.d.ts +63 -0
  231. package/dist/src/poppy-connections/http.d.ts.map +1 -0
  232. package/dist/src/poppy-connections/http.js +217 -0
  233. package/dist/src/poppy-connections/http.js.map +1 -0
  234. package/dist/src/poppy-connections/index.d.ts +14 -0
  235. package/dist/src/poppy-connections/index.d.ts.map +1 -0
  236. package/dist/src/poppy-connections/index.js +17 -0
  237. package/dist/src/poppy-connections/index.js.map +1 -0
  238. package/dist/src/poppy-connections/resume.d.ts +46 -0
  239. package/dist/src/poppy-connections/resume.d.ts.map +1 -0
  240. package/dist/src/poppy-connections/resume.js +109 -0
  241. package/dist/src/poppy-connections/resume.js.map +1 -0
  242. package/dist/src/poppy-connections/runtime.d.ts +4 -0
  243. package/dist/src/poppy-connections/runtime.d.ts.map +1 -0
  244. package/dist/src/poppy-connections/runtime.js +9 -0
  245. package/dist/src/poppy-connections/runtime.js.map +1 -0
  246. package/dist/src/poppy-connections/server_middleware.d.ts +10 -0
  247. package/dist/src/poppy-connections/server_middleware.d.ts.map +1 -0
  248. package/dist/src/poppy-connections/server_middleware.js +14 -0
  249. package/dist/src/poppy-connections/server_middleware.js.map +1 -0
  250. package/dist/src/poppy-connections/service.d.ts +179 -0
  251. package/dist/src/poppy-connections/service.d.ts.map +1 -0
  252. package/dist/src/poppy-connections/service.js +648 -0
  253. package/dist/src/poppy-connections/service.js.map +1 -0
  254. package/dist/src/poppy-connections/store.d.ts +139 -0
  255. package/dist/src/poppy-connections/store.d.ts.map +1 -0
  256. package/dist/src/poppy-connections/store.js +260 -0
  257. package/dist/src/poppy-connections/store.js.map +1 -0
  258. package/dist/src/poppy-connections/tools.d.ts +65 -0
  259. package/dist/src/poppy-connections/tools.d.ts.map +1 -0
  260. package/dist/src/poppy-connections/tools.js +574 -0
  261. package/dist/src/poppy-connections/tools.js.map +1 -0
  262. package/dist/src/react/core/approvals/use-action-proposals.d.ts +6 -1
  263. package/dist/src/react/core/approvals/use-action-proposals.d.ts.map +1 -1
  264. package/dist/src/react/core/approvals/use-action-proposals.js +18 -3
  265. package/dist/src/react/core/approvals/use-action-proposals.js.map +1 -1
  266. package/dist/src/react/core/use-agent-chat.d.ts +6 -0
  267. package/dist/src/react/core/use-agent-chat.d.ts.map +1 -1
  268. package/dist/src/react/core/use-agent-chat.js +47 -1
  269. package/dist/src/react/core/use-agent-chat.js.map +1 -1
  270. package/dist/src/tool-presentation.d.ts +6 -0
  271. package/dist/src/tool-presentation.d.ts.map +1 -1
  272. package/dist/src/tool-presentation.js +1 -0
  273. package/dist/src/tool-presentation.js.map +1 -1
  274. package/dist/src/types.d.ts +6 -0
  275. package/dist/src/types.d.ts.map +1 -1
  276. package/package.json +41 -2
  277. package/skills/agent-governance/SKILL.md +13 -2
  278. package/skills/agent-personas-agents/SKILL.md +1 -1
  279. package/skills/agent-tools/SKILL.md +20 -3
@@ -0,0 +1,648 @@
1
+ import { createHmac, randomBytes } from 'node:crypto';
2
+ import { calculateJwkThumbprint } from 'jose';
3
+ import { AgentIdentity, ConversationClient, ConversationError, completeDirectSignIn, connectMcp, discover, mediatedSignIn, newMessageId, OAuthError, PoppyAgent, PoppyError, pollDeviceSignInOnce, ResourceAuthError, StateStore, startDirectSignIn, submitMediatedCode, } from '../poppy-client/index.js';
4
+ import { DEFAULT_POPPY_CONNECTIONS_TEXTS, normalizeDomain, } from './config.js';
5
+ import { Sealer } from './crypto.js';
6
+ /** A Company outside the allow-list. The model is told plainly, and nothing is fetched. */
7
+ export class PoppyCompanyNotAllowedError extends Error {
8
+ domain;
9
+ constructor(domain) {
10
+ super(`${domain} is not a company this assistant may connect to`);
11
+ this.domain = domain;
12
+ this.name = 'PoppyCompanyNotAllowedError';
13
+ }
14
+ }
15
+ const DEFAULT_READ_SCOPES = ['poppy:read'];
16
+ /**
17
+ * This app's agent acting on other Poppy Companies for its users: the agent identity, the
18
+ * connections (sealed at rest), the connect flows and the calls the tools make.
19
+ *
20
+ * @experimental Tracks Personal Agent Protocol Draft 0.1 — will change as the spec evolves.
21
+ */
22
+ export class PoppyConnections {
23
+ #names = new Map();
24
+ texts;
25
+ path;
26
+ baseUrl;
27
+ insecureDev;
28
+ #config;
29
+ #store;
30
+ #sealer;
31
+ #secret;
32
+ #locks = new Map();
33
+ #onConnected;
34
+ #log;
35
+ #identity = null;
36
+ constructor(options) {
37
+ const { config } = options;
38
+ if (config.insecureDev === true && options.production) {
39
+ throw new Error('[@adonis-agora/agent] poppy-connections: `insecureDev` is for development only and cannot be on in production.');
40
+ }
41
+ this.#config = config;
42
+ this.#store = options.store;
43
+ this.#sealer = new Sealer(options.secret);
44
+ this.#secret = options.secret;
45
+ this.#onConnected = options.onConnected;
46
+ this.#log = options.log ?? (() => { });
47
+ this.insecureDev = config.insecureDev === true;
48
+ this.texts = { ...DEFAULT_POPPY_CONNECTIONS_TEXTS, ...config.texts };
49
+ this.path = (config.path ?? 'poppy-agent').replace(/^\/+|\/+$/g, '');
50
+ this.baseUrl = config.baseUrl.replace(/\/+$/, '');
51
+ }
52
+ /** How long a connect link works, in ms (`connectTtlSeconds`, default 15 minutes). */
53
+ get connectTtlMs() {
54
+ return (this.#config.connectTtlSeconds ?? 900) * 1000;
55
+ }
56
+ /** The agent's display name (its `client_name`). */
57
+ get identityName() {
58
+ return this.#config.identity.name;
59
+ }
60
+ /** The app's agent identity (`client_id` = `{baseUrl}/{path}/agent.json`). */
61
+ async identity() {
62
+ if (this.#identity === null) {
63
+ this.#identity = new AgentIdentity({
64
+ baseUrl: `${this.baseUrl}/${this.path}`,
65
+ clientName: this.#config.identity.name,
66
+ ...(this.#config.identity.logoUri !== undefined
67
+ ? { logoUri: this.#config.identity.logoUri }
68
+ : {}),
69
+ keys: await normalizeKeys(this.#config.identity.keys),
70
+ }, { insecureDev: this.insecureDev });
71
+ }
72
+ return this.#identity;
73
+ }
74
+ ownerOf(actor) {
75
+ return this.#config.owner?.(actor) ?? actor.tenantRef ?? actor.id;
76
+ }
77
+ /** The policy for `domain`, or `null` when it is not allow-listed (default: nothing is). */
78
+ policy(domain) {
79
+ const d = normalizeDomain(domain);
80
+ const companies = this.#config.companies;
81
+ if (companies === undefined)
82
+ return null;
83
+ if (typeof companies === 'function')
84
+ return companies(d) ? {} : null;
85
+ if (Array.isArray(companies)) {
86
+ return companies.map(normalizeDomain).includes(d) ? {} : null;
87
+ }
88
+ for (const [key, policy] of Object.entries(companies)) {
89
+ if (normalizeDomain(key) === d)
90
+ return policy;
91
+ }
92
+ return null;
93
+ }
94
+ /** The allow-listed Companies (when the allow-list is a list or a map). */
95
+ allowedCompanies() {
96
+ const companies = this.#config.companies;
97
+ if (companies === undefined || typeof companies === 'function')
98
+ return [];
99
+ if (Array.isArray(companies))
100
+ return companies.map((d) => ({ domain: normalizeDomain(d) }));
101
+ return Object.entries(companies).map(([d, p]) => ({
102
+ domain: normalizeDomain(d),
103
+ ...(p.label !== undefined ? { label: p.label } : {}),
104
+ }));
105
+ }
106
+ /**
107
+ * The allow-listed domain the model meant by `company`. A model often names a company rather than
108
+ * its domain ("MeuProntoo", "meuprontoo.com" for `localhost:3841`): when `company` is not itself
109
+ * allowed, it resolves to the ONE allowed company whose label, or the organization of the
110
+ * owner's connection to it, it names. Ambiguous or unknown → `company` as given (then refused).
111
+ */
112
+ async resolveCompany(owner, company) {
113
+ const given = normalizeDomain(company);
114
+ if (this.policy(given) !== null)
115
+ return given;
116
+ const key = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
117
+ const wanted = new Set([key(given), key(given.split('.')[0] ?? '')].filter(Boolean));
118
+ const connected = await this.#store.listConnections(owner);
119
+ const allowed = this.allowedCompanies();
120
+ const names = await Promise.all(allowed.map(async (c) => [
121
+ c.label,
122
+ connected.find((x) => x.domain === c.domain)?.organization,
123
+ await this.organizationOf(c.domain),
124
+ ]
125
+ .filter((n) => typeof n === 'string' && n !== '')
126
+ .map(key)));
127
+ const matches = allowed.filter((_, i) => names[i].some((n) => wanted.has(n)));
128
+ return matches.length === 1 ? matches[0].domain : given;
129
+ }
130
+ /**
131
+ * The organization an allow-listed company names itself in its `poppy.json` (cached ten
132
+ * minutes), or `undefined` when it cannot be reached — no owner, no sign-in needed.
133
+ */
134
+ async organizationOf(domain) {
135
+ const d = normalizeDomain(domain);
136
+ if (this.policy(d) === null)
137
+ return undefined;
138
+ const cached = this.#names.get(d);
139
+ if (cached && cached.until > Date.now())
140
+ return cached.name;
141
+ let name;
142
+ try {
143
+ const found = await discover(d, {
144
+ insecureDev: this.insecureDev,
145
+ ...(this.#config.fetch !== undefined ? { fetch: this.#config.fetch } : {}),
146
+ });
147
+ name = found.document.organization.name;
148
+ }
149
+ catch {
150
+ name = undefined;
151
+ }
152
+ this.#names.set(d, { name, until: Date.now() + (name ? 600_000 : 30_000) });
153
+ return name;
154
+ }
155
+ // ── state ──────────────────────────────────────────────────────────────────
156
+ /** Serialize everything one owner does: Account Tokens rotate, and a lost rotation is a lost login. */
157
+ async #exclusive(owner, fn) {
158
+ const previous = this.#locks.get(owner) ?? Promise.resolve();
159
+ const run = previous.catch(() => { }).then(fn);
160
+ const tail = run.catch(() => { });
161
+ this.#locks.set(owner, tail);
162
+ try {
163
+ return await run;
164
+ }
165
+ finally {
166
+ if (this.#locks.get(owner) === tail)
167
+ this.#locks.delete(owner);
168
+ }
169
+ }
170
+ /** A `PoppyAgent` over this owner's connections; every save seals them back to the store. */
171
+ async #agent(owner) {
172
+ const rows = await this.#store.listConnections(owner);
173
+ const byIssuer = new Map(rows.map((row) => [row.issuer, row]));
174
+ const domains = new Map(rows.map((row) => [row.issuer, row.domain]));
175
+ const companies = {};
176
+ for (const row of rows)
177
+ companies[row.issuer] = this.#sealer.open(row.state);
178
+ const data = { version: 1, profiles: { [owner]: { companies } } };
179
+ const store = StateStore.memory(data, async (current) => {
180
+ const now = Date.now();
181
+ for (const [issuer, state] of Object.entries(current.profiles[owner]?.companies ?? {})) {
182
+ const existing = byIssuer.get(issuer);
183
+ const domain = domains.get(issuer) ?? existing?.domain ?? state.domains[0] ?? issuer;
184
+ const status = state.accountToken
185
+ ? 'active'
186
+ : existing?.status === 'active' || existing?.status === 'revoked'
187
+ ? 'revoked'
188
+ : 'pending';
189
+ const record = {
190
+ owner,
191
+ issuer,
192
+ domain,
193
+ organization: existing?.organization ?? domain,
194
+ scope: state.accountToken?.scope ?? '',
195
+ status,
196
+ state: this.#sealer.seal(state),
197
+ createdAt: existing?.createdAt ?? now,
198
+ updatedAt: now,
199
+ };
200
+ await this.#store.saveConnection(record);
201
+ byIssuer.set(issuer, record);
202
+ }
203
+ });
204
+ const identity = await this.identity();
205
+ const agent = new PoppyAgent({
206
+ store,
207
+ identity,
208
+ profile: owner,
209
+ insecureDev: this.insecureDev,
210
+ ...(this.#config.fetch !== undefined ? { fetch: this.#config.fetch } : {}),
211
+ log: (line) => this.#log(`poppy-connections: ${line}`),
212
+ });
213
+ return { agent, domains };
214
+ }
215
+ /** Discover `domain` (allow-listed) and load this owner's state there. */
216
+ async #company(owner, domain) {
217
+ const d = normalizeDomain(domain);
218
+ const policy = this.policy(d);
219
+ if (policy === null)
220
+ throw new PoppyCompanyNotAllowedError(d);
221
+ const { agent, domains } = await this.#agent(owner);
222
+ // The requested domain (with its port) is what rediscovers the Company later.
223
+ const discovered = await agent.company(d);
224
+ domains.set(discovered.issuer, d);
225
+ const row = await this.#store.getConnection(owner, discovered.issuer);
226
+ const organization = discovered.document.organization.name;
227
+ if (row !== null && (row.domain !== d || row.organization !== organization)) {
228
+ await this.#store.saveConnection({ ...row, domain: d, organization });
229
+ }
230
+ return { client: discovered, policy };
231
+ }
232
+ /** Run `fn` with this owner's client at `domain`, serialized per owner. */
233
+ withCompany(owner, domain, fn) {
234
+ return this.#exclusive(owner, async () => {
235
+ const { client, policy } = await this.#company(owner, domain);
236
+ return fn(client, policy);
237
+ });
238
+ }
239
+ // ── connections ────────────────────────────────────────────────────────────
240
+ async list(owner) {
241
+ const rows = await this.#store.listConnections(owner);
242
+ return rows.map((row) => ({
243
+ domain: row.domain,
244
+ organization: row.organization,
245
+ status: row.status,
246
+ scope: row.scope,
247
+ connectedSince: row.status === 'active' ? row.updatedAt : null,
248
+ }));
249
+ }
250
+ /** The connection at `domain`, without contacting it. */
251
+ async connection(owner, domain) {
252
+ const d = normalizeDomain(domain);
253
+ return (await this.list(owner)).find((c) => c.domain === d) ?? null;
254
+ }
255
+ /** Sign out at the Company (revokes the Account Token, §4.9) and mark the connection revoked. */
256
+ async disconnect(owner, domain) {
257
+ return this.withCompany(owner, domain, async (client) => {
258
+ let revoked = false;
259
+ try {
260
+ revoked = await client.signOut();
261
+ }
262
+ catch (error) {
263
+ this.#log(`poppy-connections: revocation at ${client.issuer} failed: ${error.message}`);
264
+ client.state.accountToken = undefined;
265
+ client.state.tokens = {};
266
+ await client.save();
267
+ }
268
+ const row = await this.#store.getConnection(owner, client.issuer);
269
+ if (row !== null)
270
+ await this.#store.saveConnection({ ...row, status: 'revoked', scope: '' });
271
+ return revoked || row !== null;
272
+ });
273
+ }
274
+ // ── connect flows ──────────────────────────────────────────────────────────
275
+ /**
276
+ * Start a connect: the link the person opens (web or WhatsApp — it is a URL), bound to the parked
277
+ * `connect` call so completing it resumes the conversation. `id` makes a replayed call land on the
278
+ * same link.
279
+ */
280
+ async startConnect(input) {
281
+ const owner = this.ownerOf(input.actor);
282
+ const id = input.id ?? randomBytes(16).toString('base64url');
283
+ const existing = await this.#store.getPending(id);
284
+ if (existing !== null) {
285
+ return {
286
+ id,
287
+ url: this.connectUrl(id),
288
+ organization: existing.organization,
289
+ scope: existing.scope,
290
+ };
291
+ }
292
+ return this.withCompany(owner, input.domain, async (client, policy) => {
293
+ const scope = input.scope ?? policy.scopes?.join(' ') ?? offeredScopes(client).join(' ');
294
+ const now = Date.now();
295
+ await this.#store.createPending({
296
+ id,
297
+ owner,
298
+ domain: normalizeDomain(input.domain),
299
+ issuer: client.issuer,
300
+ organization: client.document.organization.name,
301
+ scope,
302
+ actor: {
303
+ id: input.actor.id,
304
+ ...(input.actor.roles !== undefined ? { roles: input.actor.roles } : {}),
305
+ ...(input.actor.tenantRef !== undefined ? { tenantRef: input.actor.tenantRef } : {}),
306
+ },
307
+ threadId: input.threadId ?? null,
308
+ runId: input.runId ?? null,
309
+ toolCallId: input.toolCallId ?? null,
310
+ flow: null,
311
+ status: 'open',
312
+ expiresAt: now + this.connectTtlMs,
313
+ createdAt: now,
314
+ });
315
+ return {
316
+ id,
317
+ url: this.connectUrl(id),
318
+ organization: client.document.organization.name,
319
+ scope,
320
+ };
321
+ });
322
+ }
323
+ /** The connect started by one tool call (`runId`, `toolCallId`), if any. */
324
+ pendingFor(runId, toolCallId) {
325
+ return this.#store.getPending(this.connectIdFor(`${runId}:${toolCallId}`));
326
+ }
327
+ /**
328
+ * The connect id of one tool call (`runId:toolCallId`): a replayed call reaches the same link.
329
+ * Keyed with the app secret — the link is a bearer capability, so it must not be derivable.
330
+ */
331
+ connectIdFor(key) {
332
+ return createHmac('sha256', this.#secret)
333
+ .update(`poppy-connect:${key}`)
334
+ .digest('base64url')
335
+ .slice(0, 32);
336
+ }
337
+ connectUrl(id) {
338
+ return `${this.baseUrl}/${this.path}/connect/${id}`;
339
+ }
340
+ async #openPending(id) {
341
+ const pending = await this.#store.getPending(id);
342
+ if (pending === null || pending.expiresAt <= Date.now())
343
+ return null;
344
+ return pending;
345
+ }
346
+ async page(id) {
347
+ const pending = await this.#openPending(id);
348
+ if (pending === null)
349
+ return { kind: 'expired' };
350
+ if (pending.status === 'connected')
351
+ return { kind: 'connected', organization: pending.organization };
352
+ return this.withCompany(pending.owner, pending.domain, async (client) => {
353
+ const auth = client.document.auth;
354
+ return {
355
+ kind: 'open',
356
+ id,
357
+ organization: pending.organization,
358
+ direct: Boolean(auth?.direct && client.metadata.authorization_endpoint),
359
+ device: Boolean(auth?.device && client.metadata.device_authorization_endpoint),
360
+ mediated: auth?.mediated?.fields?.map((f) => ({
361
+ name: f.name,
362
+ label: f.label,
363
+ secret: f.secret,
364
+ })) ?? null,
365
+ };
366
+ });
367
+ }
368
+ /** Direct Sign-In (§4.5): the authorization URL to send the browser to. */
369
+ async directUrl(id) {
370
+ const pending = await this.#openPending(id);
371
+ if (pending === null || pending.status !== 'open')
372
+ return null;
373
+ return this.withCompany(pending.owner, pending.domain, async (client) => {
374
+ const scope = withinType(client, 'direct', pending.scope);
375
+ const signIn = await startDirectSignIn(client, scope);
376
+ // `state` carries the connect id: the callback finds its sign-in without a session.
377
+ const state = `${id}.${signIn.state}`;
378
+ const url = new URL(signIn.url);
379
+ url.searchParams.set('state', state);
380
+ await this.#store.updatePending(id, {
381
+ flow: this.#sealer.seal({
382
+ kind: 'direct',
383
+ signIn: { ...signIn, state, url: url.toString() },
384
+ }),
385
+ });
386
+ return url.toString();
387
+ });
388
+ }
389
+ /** The Direct Sign-In redirect back to `redirect_uri`. */
390
+ async directCallback(params) {
391
+ const id = (params.get('state') ?? '').split('.')[0] ?? '';
392
+ const pending = await this.#openPending(id);
393
+ if (pending === null || pending.flow === null)
394
+ return { ok: false, organization: null };
395
+ const flow = this.#sealer.open(pending.flow);
396
+ if (flow.kind !== 'direct')
397
+ return { ok: false, organization: pending.organization };
398
+ try {
399
+ await this.withCompany(pending.owner, pending.domain, (client) => completeDirectSignIn(client, flow.signIn, params));
400
+ }
401
+ catch (error) {
402
+ this.#log(`poppy-connections: Direct Sign-In failed: ${error.message}`);
403
+ return { ok: false, organization: pending.organization };
404
+ }
405
+ await this.#connected(pending);
406
+ return { ok: true, organization: pending.organization };
407
+ }
408
+ /**
409
+ * Mediated Sign-In (§4.7) from this app's own page: the credentials go from the person's browser
410
+ * to this server and straight to the Company — never stored, logged, or shown to the model.
411
+ */
412
+ async mediated(id, credentials) {
413
+ const pending = await this.#openPending(id);
414
+ if (pending === null || pending.status !== 'open')
415
+ return { status: 'expired' };
416
+ const outcome = await this.withCompany(pending.owner, pending.domain, async (client) => {
417
+ const scope = withinType(client, 'mediated', pending.scope);
418
+ const result = await mediatedSignIn(client, scope, credentials);
419
+ return { result, scope };
420
+ });
421
+ return this.#mediatedOutcome(pending, outcome.result, outcome.scope);
422
+ }
423
+ async mediatedCode(id, code) {
424
+ const pending = await this.#openPending(id);
425
+ if (pending === null || pending.flow === null || pending.status !== 'open')
426
+ return { status: 'expired' };
427
+ const flow = this.#sealer.open(pending.flow);
428
+ if (flow.kind !== 'mediated')
429
+ return { status: 'failed' };
430
+ const result = await this.withCompany(pending.owner, pending.domain, (client) => submitMediatedCode(client, flow.scope, flow.signInId, code));
431
+ return this.#mediatedOutcome(pending, result, flow.scope);
432
+ }
433
+ async #mediatedOutcome(pending, result, scope) {
434
+ if (result.status === 'complete') {
435
+ await this.#connected(pending);
436
+ return { status: 'connected' };
437
+ }
438
+ if (result.status === 'code_required') {
439
+ const sentTo = result.sentTo ?? '';
440
+ await this.#store.updatePending(pending.id, {
441
+ flow: this.#sealer.seal({
442
+ kind: 'mediated',
443
+ signInId: result.signInId,
444
+ sentTo,
445
+ scope,
446
+ }),
447
+ });
448
+ return { status: 'code_required', sentTo };
449
+ }
450
+ return { status: result.status };
451
+ }
452
+ /** Device Sign-In (§4.6): the code and the Company page where the person approves it. */
453
+ async deviceStart(id) {
454
+ const pending = await this.#openPending(id);
455
+ if (pending === null || pending.status !== 'open')
456
+ return null;
457
+ return this.withCompany(pending.owner, pending.domain, async (client) => {
458
+ const scope = withinType(client, 'device', pending.scope);
459
+ const d = await client.tokens.deviceAuthorization(scope);
460
+ const flow = {
461
+ kind: 'device',
462
+ deviceCode: d.device_code,
463
+ userCode: d.user_code,
464
+ verificationUri: d.verification_uri,
465
+ ...(d.verification_uri_complete !== undefined
466
+ ? { verificationUriComplete: d.verification_uri_complete }
467
+ : {}),
468
+ scope,
469
+ };
470
+ await this.#store.updatePending(id, { flow: this.#sealer.seal(flow) });
471
+ return {
472
+ userCode: d.user_code,
473
+ verificationUri: d.verification_uri_complete ?? d.verification_uri,
474
+ };
475
+ });
476
+ }
477
+ /** The device code already started for this connect, if any. */
478
+ async deviceInfo(id) {
479
+ const pending = await this.#openPending(id);
480
+ if (pending === null || pending.flow === null)
481
+ return null;
482
+ const flow = this.#sealer.open(pending.flow);
483
+ if (flow.kind !== 'device')
484
+ return null;
485
+ return {
486
+ userCode: flow.userCode,
487
+ verificationUri: flow.verificationUriComplete ?? flow.verificationUri,
488
+ };
489
+ }
490
+ /** One device-code exchange, when the person says they approved. */
491
+ async deviceCheck(id) {
492
+ const pending = await this.#openPending(id);
493
+ if (pending === null || pending.flow === null)
494
+ return 'expired';
495
+ if (pending.status === 'connected')
496
+ return 'connected';
497
+ const flow = this.#sealer.open(pending.flow);
498
+ if (flow.kind !== 'device')
499
+ return 'failed';
500
+ try {
501
+ const result = await this.withCompany(pending.owner, pending.domain, (client) => pollDeviceSignInOnce(client, flow.deviceCode, flow.scope));
502
+ if (result === 'pending')
503
+ return 'pending';
504
+ }
505
+ catch (error) {
506
+ this.#log(`poppy-connections: Device Sign-In failed: ${error.message}`);
507
+ return 'failed';
508
+ }
509
+ await this.#connected(pending);
510
+ return 'connected';
511
+ }
512
+ async #connected(pending) {
513
+ await this.#store.updatePending(pending.id, { status: 'connected', flow: null });
514
+ const row = await this.#store.getConnection(pending.owner, pending.issuer);
515
+ if (row !== null && row.organization !== pending.organization) {
516
+ await this.#store.saveConnection({ ...row, organization: pending.organization });
517
+ }
518
+ try {
519
+ await this.#onConnected?.({ ...pending, status: 'connected' });
520
+ }
521
+ catch (error) {
522
+ this.#log(`poppy-connections: resuming the conversation failed: ${error.message}`);
523
+ }
524
+ }
525
+ // ── the Company's APIs and agent ──────────────────────────────────────────
526
+ /** The Company's MCP server, with a Bearer token for it (§6). Closed by the caller. */
527
+ mcp(owner, domain) {
528
+ return this.withCompany(owner, domain, (client) => connectMcp(client));
529
+ }
530
+ /**
531
+ * One exchange with the Company's agent (§7): `act: false` narrows the token to the read scopes
532
+ * (`policy.readScopes`, default `poppy:read`), so a question can never make the Company act;
533
+ * `act: true` uses the whole grant — the tools gate that behind the person's confirmation.
534
+ */
535
+ ask(owner, domain, text, options) {
536
+ return this.withCompany(owner, domain, async (client, policy) => {
537
+ let scope;
538
+ if (!options.act) {
539
+ const granted = new Set((client.accountToken?.scope ?? '').split(' ').filter(Boolean));
540
+ const read = (policy.readScopes ?? DEFAULT_READ_SCOPES).filter((s) => granted.has(s));
541
+ if (client.accountToken && read.length === 0) {
542
+ return {
543
+ conversationId: '',
544
+ replies: [],
545
+ authorization: { error: 'no_read_scope' },
546
+ status: 'idle',
547
+ };
548
+ }
549
+ scope = read.length > 0 ? read.join(' ') : undefined;
550
+ }
551
+ const conv = new ConversationClient(client, undefined, { scope });
552
+ const message = { id: newMessageId(), sender: 'agent', text };
553
+ const open = Object.entries(client.state.conversations)
554
+ .filter(([, c]) => !c.closed && c.endpoint === conv.endpoint && c.parentConversationId === undefined)
555
+ .sort(([, a], [, b]) => b.createdAt - a.createdAt)[0]?.[0];
556
+ let conversationId;
557
+ try {
558
+ if (open === undefined)
559
+ throw new ConversationError('conversation_not_found');
560
+ await conv.send(open, message);
561
+ conversationId = open;
562
+ }
563
+ catch (error) {
564
+ if (!(error instanceof ConversationError))
565
+ throw error;
566
+ if (open !== undefined)
567
+ client.state.conversations[open] = { ...client.state.conversations[open], closed: true };
568
+ conversationId = (await conv.start(message)).conversation_id;
569
+ }
570
+ const deadline = Date.now() + (this.#config.replyTimeoutMs ?? 90_000);
571
+ const ac = new AbortController();
572
+ const timer = setTimeout(() => ac.abort(), Math.max(1000, deadline - Date.now()));
573
+ const reply = { conversationId, replies: [], status: 'working' };
574
+ try {
575
+ for await (const item of conv.follow(conversationId, {
576
+ untilSettled: true,
577
+ signal: ac.signal,
578
+ })) {
579
+ if (item.kind !== 'event')
580
+ continue;
581
+ const ev = item.event;
582
+ if (ev.type === 'message' && ev.message?.role === 'company') {
583
+ reply.replies.push({
584
+ text: ev.message.text ?? '',
585
+ ...(ev.message.data !== undefined ? { data: ev.message.data } : {}),
586
+ });
587
+ }
588
+ else if (ev.type === 'authorization') {
589
+ reply.authorization = {
590
+ error: String(ev.error ?? 'insufficient_scope'),
591
+ ...(typeof ev.scope === 'string' ? { scope: ev.scope } : {}),
592
+ };
593
+ }
594
+ else if (ev.type === 'state' && typeof ev.status === 'string') {
595
+ reply.status = ev.status;
596
+ }
597
+ }
598
+ }
599
+ finally {
600
+ clearTimeout(timer);
601
+ }
602
+ if (ac.signal.aborted && reply.status === 'working')
603
+ reply.status = 'timeout';
604
+ await client.save();
605
+ return reply;
606
+ });
607
+ }
608
+ }
609
+ /** The scopes a Company offers for signing in, broadest list first. */
610
+ function offeredScopes(client) {
611
+ const auth = client.document.auth;
612
+ return auth?.direct?.scopes ?? auth?.device?.scopes ?? auth?.mediated?.scopes ?? [];
613
+ }
614
+ /** `scope` narrowed to what one sign-in type can grant (falls back to all it offers). */
615
+ function withinType(client, type, scope) {
616
+ const offered = client.document.auth?.[type]?.scopes ?? [];
617
+ const wanted = scope.split(' ').filter((s) => offered.includes(s));
618
+ return (wanted.length > 0 ? wanted : offered).join(' ');
619
+ }
620
+ /** Private EC JWKs (or poppy-client `StoredKey`s, or a JSON string of either) → `StoredKey[]`. */
621
+ export async function normalizeKeys(input) {
622
+ const list = (typeof input === 'string' ? JSON.parse(input) : input);
623
+ if (!Array.isArray(list) || list.length === 0) {
624
+ throw new Error('[@adonis-agora/agent] poppy-connections: `identity.keys` needs at least one private key');
625
+ }
626
+ return Promise.all(list.map(async (raw) => {
627
+ const value = raw;
628
+ if (value.privateJwk && value.publicJwk && typeof value.kid === 'string')
629
+ return value;
630
+ const jwk = value;
631
+ if (jwk.kty !== 'EC' ||
632
+ jwk.crv !== 'P-256' ||
633
+ typeof jwk.d !== 'string' ||
634
+ typeof jwk.x !== 'string' ||
635
+ typeof jwk.y !== 'string') {
636
+ throw new Error('[@adonis-agora/agent] poppy-connections: identity keys must be private EC P-256 JWKs');
637
+ }
638
+ const publicJwk = { kty: jwk.kty, crv: jwk.crv, x: jwk.x, y: jwk.y };
639
+ const kid = jwk.kid ?? (await calculateJwkThumbprint(publicJwk, 'sha256'));
640
+ return {
641
+ kid,
642
+ privateJwk: { ...jwk, kid, alg: 'ES256', use: 'sig' },
643
+ publicJwk: { ...publicJwk, kid, alg: 'ES256', use: 'sig' },
644
+ };
645
+ }));
646
+ }
647
+ export { OAuthError, PoppyError, ResourceAuthError };
648
+ //# sourceMappingURL=service.js.map