@amalgm/channels 0.1.1 → 0.1.2

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 (126) hide show
  1. package/PURPOSE.md +48 -13
  2. package/README.md +64 -6
  3. package/dist/host/auth.js +7 -3
  4. package/dist/host/auth.js.map +1 -1
  5. package/dist/host/config.d.ts +3 -1
  6. package/dist/host/config.js +3 -1
  7. package/dist/host/config.js.map +1 -1
  8. package/dist/host/inbox-repository.d.ts +10 -0
  9. package/dist/host/inbox-repository.js +57 -0
  10. package/dist/host/inbox-repository.js.map +1 -0
  11. package/dist/host/index.d.ts +4 -0
  12. package/dist/host/index.js +4 -0
  13. package/dist/host/index.js.map +1 -1
  14. package/dist/host/ingress-repository.d.ts +10 -0
  15. package/dist/host/ingress-repository.js +61 -0
  16. package/dist/host/ingress-repository.js.map +1 -0
  17. package/dist/host/main.js +32 -3
  18. package/dist/host/main.js.map +1 -1
  19. package/dist/host/repository.d.ts +4 -3
  20. package/dist/host/repository.js +23 -3
  21. package/dist/host/repository.js.map +1 -1
  22. package/dist/host/resend-webhook.d.ts +14 -0
  23. package/dist/host/resend-webhook.js +172 -0
  24. package/dist/host/resend-webhook.js.map +1 -0
  25. package/dist/host/resend.js +4 -0
  26. package/dist/host/resend.js.map +1 -1
  27. package/dist/host/server.d.ts +2 -1
  28. package/dist/host/server.js +85 -17
  29. package/dist/host/server.js.map +1 -1
  30. package/dist/host/wakeups.d.ts +22 -0
  31. package/dist/host/wakeups.js +30 -0
  32. package/dist/host/wakeups.js.map +1 -0
  33. package/dist/src/api-error.d.ts +2 -0
  34. package/dist/src/api-error.js +13 -0
  35. package/dist/src/api-error.js.map +1 -0
  36. package/dist/src/cli/arguments.d.ts +20 -0
  37. package/dist/src/cli/arguments.js +46 -0
  38. package/dist/src/cli/arguments.js.map +1 -0
  39. package/dist/src/cli/input.d.ts +6 -0
  40. package/dist/src/cli/input.js +38 -0
  41. package/dist/src/cli/input.js.map +1 -0
  42. package/dist/src/cli/main.d.ts +7 -0
  43. package/dist/src/cli/main.js +27 -0
  44. package/dist/src/cli/main.js.map +1 -0
  45. package/dist/src/cli/run.d.ts +13 -0
  46. package/dist/src/cli/run.js +47 -0
  47. package/dist/src/cli/run.js.map +1 -0
  48. package/dist/src/cli/shell-runtime.d.ts +9 -0
  49. package/dist/src/cli/shell-runtime.js +106 -0
  50. package/dist/src/cli/shell-runtime.js.map +1 -0
  51. package/dist/src/cli-main.d.ts +2 -0
  52. package/dist/src/cli-main.js +4 -0
  53. package/dist/src/cli-main.js.map +1 -0
  54. package/dist/src/cli.d.ts +3 -0
  55. package/dist/src/cli.js +4 -0
  56. package/dist/src/cli.js.map +1 -0
  57. package/dist/src/client.js +5 -4
  58. package/dist/src/client.js.map +1 -1
  59. package/dist/src/command-surface.d.ts +23 -0
  60. package/dist/src/command-surface.js +53 -0
  61. package/dist/src/command-surface.js.map +1 -0
  62. package/dist/src/contract.d.ts +33 -7
  63. package/dist/src/email/blocks.d.ts +7 -0
  64. package/dist/src/email/blocks.js +43 -0
  65. package/dist/src/email/blocks.js.map +1 -0
  66. package/dist/src/email/escape.d.ts +3 -0
  67. package/dist/src/email/escape.js +19 -0
  68. package/dist/src/email/escape.js.map +1 -0
  69. package/dist/src/email/inbound.d.ts +83 -0
  70. package/dist/src/email/inbound.js +162 -0
  71. package/dist/src/email/inbound.js.map +1 -0
  72. package/dist/src/email/inline.d.ts +1 -0
  73. package/dist/src/email/inline.js +31 -0
  74. package/dist/src/email/inline.js.map +1 -0
  75. package/dist/src/email/markdown.d.ts +1 -0
  76. package/dist/src/email/markdown.js +117 -0
  77. package/dist/src/email/markdown.js.map +1 -0
  78. package/dist/src/email/notification.d.ts +2 -0
  79. package/dist/src/email/notification.js +18 -0
  80. package/dist/src/email/notification.js.map +1 -0
  81. package/dist/src/email/styles.d.ts +33 -0
  82. package/dist/src/email/styles.js +34 -0
  83. package/dist/src/email/styles.js.map +1 -0
  84. package/dist/src/email/system.d.ts +13 -0
  85. package/dist/src/email/system.js +31 -0
  86. package/dist/src/email/system.js.map +1 -0
  87. package/dist/src/email/thread-token.d.ts +8 -0
  88. package/dist/src/email/thread-token.js +70 -0
  89. package/dist/src/email/thread-token.js.map +1 -0
  90. package/dist/src/errors.d.ts +3 -2
  91. package/dist/src/errors.js +3 -1
  92. package/dist/src/errors.js.map +1 -1
  93. package/dist/src/http.d.ts +2 -0
  94. package/dist/src/http.js +84 -3
  95. package/dist/src/http.js.map +1 -1
  96. package/dist/src/inbox/client.d.ts +7 -0
  97. package/dist/src/inbox/client.js +79 -0
  98. package/dist/src/inbox/client.js.map +1 -0
  99. package/dist/src/inbox/contract.d.ts +37 -0
  100. package/dist/src/inbox/contract.js +2 -0
  101. package/dist/src/inbox/contract.js.map +1 -0
  102. package/dist/src/inbox/events.d.ts +7 -0
  103. package/dist/src/inbox/events.js +46 -0
  104. package/dist/src/inbox/events.js.map +1 -0
  105. package/dist/src/inbox/runner.d.ts +14 -0
  106. package/dist/src/inbox/runner.js +93 -0
  107. package/dist/src/inbox/runner.js.map +1 -0
  108. package/dist/src/inbox/service.d.ts +9 -0
  109. package/dist/src/inbox/service.js +56 -0
  110. package/dist/src/inbox/service.js.map +1 -0
  111. package/dist/src/index.d.ts +11 -0
  112. package/dist/src/index.js +11 -0
  113. package/dist/src/index.js.map +1 -1
  114. package/dist/src/mcp.js +20 -52
  115. package/dist/src/mcp.js.map +1 -1
  116. package/dist/src/service.js +7 -3
  117. package/dist/src/service.js.map +1 -1
  118. package/dist/src/validation.d.ts +2 -2
  119. package/dist/src/validation.js +2 -2
  120. package/dist/src/validation.js.map +1 -1
  121. package/dist/src/worker.d.ts +4 -1
  122. package/dist/src/worker.js +27 -5
  123. package/dist/src/worker.js.map +1 -1
  124. package/package.json +12 -5
  125. package/skills/channels/SKILL.md +14 -14
  126. package/supabase/migrations/20260903000000_bidirectional_email.sql +630 -0
package/PURPOSE.md CHANGED
@@ -2,30 +2,40 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- Channels is the sole owner of delivering a message from Amalgm to the current
6
- user. A caller supplies content and an optional channel preference; Channels
7
- resolves the authenticated user's destination, persists one durable delivery,
8
- and hands it to the configured provider. Callers never choose another user,
9
- handle provider credentials, or infer whether delivery happened from a network
10
- attempt.
5
+ Channels is the sole owner of carrying discrete messages between an Amalgm
6
+ user and external communication channels. An agent supplies content and a
7
+ channel; Channels resolves that authenticated user's destination, persists one
8
+ durable delivery, and hands it to the configured provider. A verified inbound
9
+ provider event resolves one user, one channel thread, and one pinned computer,
10
+ then waits durably until that computer admits it into the matching local Chat
11
+ session. Callers never choose another user, handle provider credentials, or
12
+ infer whether delivery happened from a network attempt.
11
13
 
12
- The SDK owns notification validation, idempotency, delivery state, and history.
13
- The HTTP API, MCP server, CLI, worker, and Fly host are adapters over that SDK.
14
- Supabase is the durable authority; providers are effects supplied by the host.
15
- The hosted service is its own deployable and does not run inside or depend on
16
- Amalgm Gateway.
14
+ The SDK owns send validation, channel-thread identity, inbound admission,
15
+ idempotency, leases, delivery state, and history. The HTTP API, MCP server,
16
+ global CLI, machine runner, provider adapters, and Fly host are adapters over
17
+ that SDK.
18
+ Supabase is the durable authority only for provider delivery and inbound work
19
+ that no computer has accepted yet. Chat sessions, transcripts, and agent
20
+ execution stay on the pinned user's computer. The hosted service is its own
21
+ deployable and does not run inside or depend on Amalgm Gateway.
17
22
 
18
23
  ## Axioms
19
24
 
20
- 1. Every delivery belongs to exactly one authenticated user.
25
+ 1. Every user delivery and every accepted inbound message belongs to exactly
26
+ one authenticated user.
21
27
  2. A public send input never contains a user id, email address, phone number,
22
28
  provider credential, or other recipient coordinate.
23
29
  3. One idempotency key names at most one delivery for a user.
30
+ Provider retries use that delivery's globally unique id, never the caller's
31
+ user-scoped key.
24
32
  4. A provider attempt begins only after a durable, exclusively leased delivery
25
33
  exists; completion is accepted only from that lease.
26
34
  5. Failed and expired leases remain visible and retryable. They are never
27
35
  reported as delivered.
28
- 6. Supabase is authoritative for pending work and permanent delivery history.
36
+ 6. Supabase is authoritative for pending provider work, pending inbound work,
37
+ channel-thread routing, and permanent delivery history; it is never a Chat
38
+ transcript store.
29
39
  7. Provider responses are reduced to bounded identifiers and safe errors before
30
40
  persistence; secrets and recipient coordinates never enter public records.
31
41
  8. The SDK is the behavior. HTTP, MCP, CLI, workers, and provider adapters stay
@@ -36,6 +46,27 @@ Amalgm Gateway.
36
46
  or deliver its work.
37
47
  11. The configured public origin, never forwarding headers, reconstructs the
38
48
  DPoP request URL behind the Fly proxy.
49
+ 12. One Channels renderer derives the HTML email and plaintext fallback from
50
+ the same persisted message; provider adapters cannot invent presentation.
51
+ 13. One channel thread names exactly one Chat session and one pinned computer.
52
+ A new external thread creates a new session; every reply in that thread
53
+ reuses it.
54
+ 14. A provider webhook is authenticated and durably deduplicated before it can
55
+ create work. Inbound account identity additionally requires an aligned
56
+ DMARC pass for the visible sender; subjects never authenticate or route.
57
+ 15. An inbound envelope is complete offline state. A machine may claim only
58
+ envelopes pinned to its DPoP-bound computer identity, and hosted message
59
+ content is removed after the local Chat turn is durably admitted.
60
+ 16. Agent text is not an outbound message. Only an explicit Channels send call
61
+ creates a delivery, and session identity is supplied by the trusted local
62
+ Chat relay rather than by the model.
63
+ 17. Machine wakeups are content-free, cross-host broadcasts that accelerate
64
+ claims but never own correctness. Connect and reconnect always reconcile
65
+ the durable inbox.
66
+ 18. Provider acceptance means sent, not delivered. Provider events alone move
67
+ a sent delivery to delivered or failed.
68
+ 19. The global CLI projects the same command definitions as MCP and reaches
69
+ hosted effects only through Shell's authenticated loopback MCP route.
39
70
 
40
71
  ## Tree
41
72
 
@@ -43,9 +74,13 @@ Amalgm Gateway.
43
74
  src/contract.ts public SDK shapes
44
75
  src/service.ts validation and user-bound behavior
45
76
  src/worker.ts lease-to-provider delivery pump
77
+ src/inbox/ machine claim, acceptance, and event-driven drain
46
78
  src/http.ts HTTP adapter
47
79
  src/client.ts browser/host API client
48
80
  src/mcp.ts agent adapter
81
+ src/command-surface.ts shared MCP and CLI command definitions
82
+ src/cli/ global Shell-loopback command adapter
83
+ src/email/ canonical presentation, threading, and inbound rules
49
84
  host/ Supabase, auth, provider, server, and Fly composition
50
85
  supabase/migrations/ durable schema and atomic RPCs
51
86
  skills/channels/ agent usage guidance
package/README.md CHANGED
@@ -1,16 +1,74 @@
1
1
  # @amalgm/channels
2
2
 
3
- The SDK and standalone hosted service for sending notifications to the current
4
- Amalgm user.
3
+ Channels carries deliberate, discrete messages between an Amalgm user and
4
+ external communication channels. Email is the first bidirectional channel.
5
+
6
+ An agent does its work locally and explicitly calls one send command with the
7
+ text it wants the user to receive. Ordinary assistant output is never copied,
8
+ streamed, or inferred into an external message.
5
9
 
6
10
  ```ts
7
11
  const channels = service.for({ userId, scopes: ['channels:send'] });
8
- await channels.notify({ message: 'Call Mom', idempotencyKey: runId });
12
+ await channels.send({
13
+ channel: 'email',
14
+ title: 'Research complete',
15
+ message: 'The report is ready.',
16
+ idempotencyKey: runId,
17
+ });
18
+ ```
19
+
20
+ ## Email behavior
21
+
22
+ - A new email thread creates one new local Chat session.
23
+ - Every reply in that email thread resumes the same session.
24
+ - The first message pins the thread to the user's last-used computer.
25
+ - If that computer is offline, the inbound message waits durably and drains in
26
+ order when Shell reconnects. The user does not need to resend it.
27
+ - Once the machine durably admits the turn, hosted message content is erased.
28
+ - Agent replies are separate sends through `amalgm_channels_send`; partial
29
+ assistant output never leaves the machine.
30
+
31
+ Production mail is sent as `Amalgm <emails@amalgm.ai>`. Replies use an opaque,
32
+ signed address on `reply.amalgm.ai`, so threading does not depend on a subject
33
+ line or a guess about the user's latest session.
34
+
35
+ ## One command surface
36
+
37
+ The SDK owns the send command definition, validation, and behavior. MCP and
38
+ both CLI entry points project that same definition:
39
+
40
+ ```bash
41
+ amalgm channels send --input '{
42
+ "channel": "email",
43
+ "title": "Finished",
44
+ "message": "The work is complete.",
45
+ "idempotency_key": "run-123"
46
+ }'
47
+
48
+ amalgm-channels send --file message.json
49
+ printf '%s' '{"channel":"email","message":"Done"}' |
50
+ amalgm channels send --stdin
9
51
  ```
10
52
 
11
- Use `createChannelsApi` for HTTP, `createChannelsMcpServer` for agents, and
12
- `createChannelsHost` for the Fly service. See [PURPOSE.md](./PURPOSE.md) for the
13
- ownership and security laws.
53
+ The global `amalgm channels` command discovers the running Shell and uses its
54
+ private loopback MCP route and local admission token. It never exports a hosted
55
+ access token or the machine's DPoP key. The recipient is always the signed-in
56
+ user; no command accepts an email address or user id.
57
+
58
+ ## Composition
59
+
60
+ - `createChannelsApi` exposes the HTTP adapter.
61
+ - `createChannelsMcpServer` exposes the agent tool.
62
+ - `createChannelsCommands` is the reusable MCP/CLI command surface.
63
+ - `createChannelMachineRunner` drains held inbound work on Shell.
64
+ - `createChannelsHost` composes the Fly service.
65
+
66
+ Supabase stores only provider delivery history, thread routing, and inbound
67
+ work not yet admitted by a computer. Chat sessions, transcripts, and execution
68
+ remain on the user's machine. See [PURPOSE.md](./PURPOSE.md) for the governing
69
+ purpose and invariants.
70
+
71
+ ## Verify
14
72
 
15
73
  ```bash
16
74
  npm run verify
package/dist/host/auth.js CHANGED
@@ -17,9 +17,12 @@ export function createChannelsAuthenticator(options) {
17
17
  scopes: Object.freeze(['channels:read', 'channels:send']),
18
18
  });
19
19
  }
20
- const required = request.method.toUpperCase() === 'POST'
21
- ? ['channels:send']
22
- : ['channels:read'];
20
+ const pathname = new URL(request.url).pathname;
21
+ const required = pathname.startsWith('/v1/machine/')
22
+ ? ['channels:receive']
23
+ : request.method.toUpperCase() === 'POST'
24
+ ? ['channels:send']
25
+ : ['channels:read'];
23
26
  const principal = await authorizeMachineResource({
24
27
  authorization,
25
28
  dpop: request.headers.get('dpop'),
@@ -31,6 +34,7 @@ export function createChannelsAuthenticator(options) {
31
34
  }, ports);
32
35
  return Object.freeze({
33
36
  userId: principal.userId,
37
+ computerId: principal.computerId,
34
38
  scopes: principal.scopes.filter((scope) => scope.startsWith('channels:')),
35
39
  });
36
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../host/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../host/auth.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,9 @@ export interface ChannelsHostConfig {
5
5
  readonly supabaseServiceRoleKey: string;
6
6
  readonly authorizationIssuer: string;
7
7
  readonly resendApiKey: string;
8
+ readonly resendWebhookSecret: string;
8
9
  readonly emailFrom: string;
9
- readonly pollIntervalMs: number;
10
+ readonly emailInbox: string;
11
+ readonly emailThreadSecret: string;
10
12
  }
11
13
  export declare function channelsHostConfig(env?: Readonly<Record<string, string | undefined>>): ChannelsHostConfig;
@@ -6,8 +6,10 @@ export function channelsHostConfig(env = process.env) {
6
6
  supabaseServiceRoleKey: required(env.SUPABASE_SERVICE_ROLE_KEY, 'SUPABASE_SERVICE_ROLE_KEY'),
7
7
  authorizationIssuer: httpsUrl(env.AMALGM_AUTHORIZATION_ISSUER, 'AMALGM_AUTHORIZATION_ISSUER'),
8
8
  resendApiKey: required(env.RESEND_API_KEY, 'RESEND_API_KEY'),
9
+ resendWebhookSecret: required(env.RESEND_WEBHOOK_SECRET, 'RESEND_WEBHOOK_SECRET'),
9
10
  emailFrom: required(env.CHANNELS_EMAIL_FROM, 'CHANNELS_EMAIL_FROM'),
10
- pollIntervalMs: integer(env.CHANNELS_POLL_INTERVAL_MS, 1_000, 250, 60_000),
11
+ emailInbox: required(env.CHANNELS_EMAIL_INBOX, 'CHANNELS_EMAIL_INBOX'),
12
+ emailThreadSecret: required(env.CHANNELS_EMAIL_THREAD_SECRET, 'CHANNELS_EMAIL_THREAD_SECRET'),
11
13
  });
12
14
  }
13
15
  function required(value, name) {
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../host/config.ts"],"names":[],"mappings":"AAWA,MAAM,UAAU,kBAAkB,CAChC,MAAoD,OAAO,CAAC,GAAG;IAE/D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;QACxC,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;QAC1E,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,wBAAwB,EAAE,cAAc,CAAC;QACvF,sBAAsB,EAAE,QAAQ,CAAC,GAAG,CAAC,yBAAyB,EAAE,2BAA2B,CAAC;QAC5F,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,2BAA2B,EAAE,6BAA6B,CAAC;QAC7F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAC5D,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;QACnE,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC;KAC3E,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,CAAC;IACjF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,OAAO,CACd,KAAyB,EACzB,QAAgB,EAChB,OAAe,EACf,OAAe;IAEf,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,OAAO,IAAI,MAAM,GAAG,OAAO,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../host/config.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,kBAAkB,CAChC,MAAoD,OAAO,CAAC,GAAG;IAE/D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC;QACxC,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;QAC1E,WAAW,EAAE,QAAQ,CAAC,GAAG,CAAC,YAAY,IAAI,GAAG,CAAC,wBAAwB,EAAE,cAAc,CAAC;QACvF,sBAAsB,EAAE,QAAQ,CAAC,GAAG,CAAC,yBAAyB,EAAE,2BAA2B,CAAC;QAC5F,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,2BAA2B,EAAE,6BAA6B,CAAC;QAC7F,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,gBAAgB,CAAC;QAC5D,mBAAmB,EAAE,QAAQ,CAAC,GAAG,CAAC,qBAAqB,EAAE,uBAAuB,CAAC;QACjF,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,mBAAmB,EAAE,qBAAqB,CAAC;QACnE,UAAU,EAAE,QAAQ,CAAC,GAAG,CAAC,oBAAoB,EAAE,sBAAsB,CAAC;QACtE,iBAAiB,EAAE,QAAQ,CAAC,GAAG,CAAC,4BAA4B,EAAE,8BAA8B,CAAC;KAC9F,CAAC,CAAC;AACL,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,cAAc,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAyB,EAAE,IAAY;IACvD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,KAAK,WAAW,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,CAAC;IACjF,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,CAAC,EAAE,CAAC;QAC5E,MAAM,IAAI,KAAK,CAAC,GAAG,IAAI,iBAAiB,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,OAAO,CACd,KAAyB,EACzB,QAAgB,EAChB,OAAe,EACf,OAAe;IAEf,MAAM,MAAM,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,OAAO,IAAI,MAAM,GAAG,OAAO,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,4BAA4B,OAAO,OAAO,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { ClaimedInboundMessage, InboundLease, MachineInboxRepository } from '../src/inbox/contract.js';
3
+ export declare class SupabaseMachineInboxRepository implements MachineInboxRepository {
4
+ #private;
5
+ private readonly client;
6
+ constructor(client: SupabaseClient);
7
+ claim(userId: string, computerId: string, limit: number, leaseSeconds: number): Promise<readonly ClaimedInboundMessage[]>;
8
+ accept(userId: string, computerId: string, message: InboundLease, now: Date): Promise<boolean>;
9
+ release(userId: string, computerId: string, message: InboundLease, error: string, now: Date): Promise<boolean>;
10
+ }
@@ -0,0 +1,57 @@
1
+ import { ChannelsError } from '../src/errors.js';
2
+ export class SupabaseMachineInboxRepository {
3
+ client;
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ async claim(userId, computerId, limit, leaseSeconds) {
8
+ const rows = await this.#rpc('claim_amalgm_channel_inbound', {
9
+ p_user_id: userId,
10
+ p_computer_id: computerId,
11
+ p_limit: limit,
12
+ p_lease_seconds: leaseSeconds,
13
+ });
14
+ return rows.map((row) => Object.freeze({
15
+ id: text(row.id),
16
+ channel: 'email',
17
+ threadId: text(row.thread_id),
18
+ sessionId: text(row.session_id),
19
+ startsSession: row.starts_session === true,
20
+ subject: text(row.subject),
21
+ message: text(row.message),
22
+ providerMessageId: text(row.provider_message_id),
23
+ receivedAt: timestamp(row.received_at),
24
+ leaseToken: text(row.lease_token),
25
+ attempts: number(row.attempts),
26
+ }));
27
+ }
28
+ accept(userId, computerId, message, now) {
29
+ return this.#rpc('accept_amalgm_channel_inbound', {
30
+ p_user_id: userId,
31
+ p_computer_id: computerId,
32
+ p_message_id: message.id,
33
+ p_lease_token: message.leaseToken,
34
+ p_now: now.toISOString(),
35
+ });
36
+ }
37
+ release(userId, computerId, message, error, now) {
38
+ return this.#rpc('release_amalgm_channel_inbound', {
39
+ p_user_id: userId,
40
+ p_computer_id: computerId,
41
+ p_message_id: message.id,
42
+ p_lease_token: message.leaseToken,
43
+ p_error: error,
44
+ p_now: now.toISOString(),
45
+ });
46
+ }
47
+ async #rpc(name, arguments_) {
48
+ const result = await this.client.rpc(name, arguments_);
49
+ if (result.error)
50
+ throw new ChannelsError('internal', `Channels inbox storage failed: ${result.error.message}`);
51
+ return result.data;
52
+ }
53
+ }
54
+ const text = (value) => typeof value === 'string' ? value : String(value ?? '');
55
+ const number = (value) => Number(value) || 0;
56
+ const timestamp = (value) => new Date(String(value)).toISOString();
57
+ //# sourceMappingURL=inbox-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox-repository.js","sourceRoot":"","sources":["../../host/inbox-repository.ts"],"names":[],"mappings":"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"}
@@ -4,3 +4,7 @@ export * from './recipient.js';
4
4
  export * from './repository.js';
5
5
  export * from './resend.js';
6
6
  export * from './server.js';
7
+ export * from './wakeups.js';
8
+ export * from './inbox-repository.js';
9
+ export * from './ingress-repository.js';
10
+ export * from './resend-webhook.js';
@@ -4,4 +4,8 @@ export * from './recipient.js';
4
4
  export * from './repository.js';
5
5
  export * from './resend.js';
6
6
  export * from './server.js';
7
+ export * from './wakeups.js';
8
+ export * from './inbox-repository.js';
9
+ export * from './ingress-repository.js';
10
+ export * from './resend-webhook.js';
7
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../host/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../host/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { EmailIngressRepository, InboundAdmission, NormalizedReceivedEmail, ProviderEmailEvent } from '../src/email/inbound.js';
3
+ export declare class SupabaseEmailIngressRepository implements EmailIngressRepository {
4
+ #private;
5
+ private readonly client;
6
+ constructor(client: SupabaseClient);
7
+ admit(input: NormalizedReceivedEmail): Promise<InboundAdmission>;
8
+ recordNotice(recordId: string, providerId: string, now: Date): Promise<boolean>;
9
+ applyProviderEvent(input: ProviderEmailEvent): Promise<boolean>;
10
+ }
@@ -0,0 +1,61 @@
1
+ import { ChannelsError } from '../src/errors.js';
2
+ export class SupabaseEmailIngressRepository {
3
+ client;
4
+ constructor(client) {
5
+ this.client = client;
6
+ }
7
+ async admit(input) {
8
+ const row = await this.#rpc('admit_amalgm_channel_email', { p_email: input });
9
+ const kind = text(row.kind);
10
+ if (kind === 'accepted') {
11
+ return Object.freeze({
12
+ kind,
13
+ userId: text(row.user_id),
14
+ messageId: text(row.message_id),
15
+ threadId: text(row.thread_id),
16
+ sessionId: text(row.session_id),
17
+ targetComputerId: text(row.target_computer_id),
18
+ targetComputerName: text(row.target_computer_name),
19
+ targetOnline: row.target_online === true,
20
+ senderEmail: text(row.sender_email),
21
+ subject: text(row.subject),
22
+ providerMessageId: text(row.provider_message_id),
23
+ references: strings(row.references),
24
+ });
25
+ }
26
+ if (kind === 'unknown_sender' || kind === 'no_computer') {
27
+ return Object.freeze({
28
+ kind,
29
+ recordId: text(row.record_id),
30
+ senderEmail: text(row.sender_email),
31
+ subject: text(row.subject),
32
+ providerMessageId: text(row.provider_message_id),
33
+ references: strings(row.references),
34
+ });
35
+ }
36
+ if (kind === 'duplicate')
37
+ return Object.freeze({ kind, recordId: text(row.record_id) });
38
+ if (kind === 'ignored')
39
+ return Object.freeze({ kind, reason: text(row.reason) });
40
+ throw new ChannelsError('internal', 'Channels ingress returned an unknown admission');
41
+ }
42
+ recordNotice(recordId, providerId, now) {
43
+ return this.#rpc('record_amalgm_channel_inbound_notice', {
44
+ p_message_id: recordId,
45
+ p_provider_id: providerId,
46
+ p_now: now.toISOString(),
47
+ });
48
+ }
49
+ applyProviderEvent(input) {
50
+ return this.#rpc('apply_amalgm_channel_provider_event', { p_event: input });
51
+ }
52
+ async #rpc(name, arguments_) {
53
+ const result = await this.client.rpc(name, arguments_);
54
+ if (result.error)
55
+ throw new ChannelsError('internal', `Channels ingress storage failed: ${result.error.message}`);
56
+ return result.data;
57
+ }
58
+ }
59
+ const text = (value) => typeof value === 'string' ? value : String(value ?? '');
60
+ const strings = (value) => Array.isArray(value) ? value.map(text).filter(Boolean) : [];
61
+ //# sourceMappingURL=ingress-repository.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingress-repository.js","sourceRoot":"","sources":["../../host/ingress-repository.ts"],"names":[],"mappings":"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"}
package/dist/host/main.js CHANGED
@@ -2,32 +2,61 @@ import { createClient } from '@supabase/supabase-js';
2
2
  import { createChannelsApi } from '../src/http.js';
3
3
  import { createChannelsService } from '../src/service.js';
4
4
  import { ChannelsWorker } from '../src/worker.js';
5
+ import { EmailIngress } from '../src/email/inbound.js';
6
+ import { EmailThreadAddressing } from '../src/email/thread-token.js';
7
+ import { MachineEventHub } from '../src/inbox/events.js';
8
+ import { createMachineInboxService } from '../src/inbox/service.js';
5
9
  import { createChannelsAuthenticator } from './auth.js';
6
10
  import { channelsHostConfig } from './config.js';
7
11
  import { SupabaseRecipientDirectory } from './recipient.js';
8
12
  import { SupabaseDeliveryRepository } from './repository.js';
9
13
  import { ResendEmailProvider } from './resend.js';
10
14
  import { createChannelsHost } from './server.js';
15
+ import { SupabaseMachineInboxRepository } from './inbox-repository.js';
16
+ import { SupabaseEmailIngressRepository } from './ingress-repository.js';
17
+ import { createResendWebhookApi } from './resend-webhook.js';
18
+ import { HostedMachineWakeups } from './wakeups.js';
11
19
  const config = channelsHostConfig();
12
20
  const supabase = createClient(config.supabaseUrl, config.supabaseServiceRoleKey, {
13
21
  auth: { persistSession: false, autoRefreshToken: false },
14
22
  });
15
23
  const repository = new SupabaseDeliveryRepository(supabase);
16
- const worker = new ChannelsWorker(repository, new SupabaseRecipientDirectory(supabase), new ResendEmailProvider(config.resendApiKey, config.emailFrom), { log });
24
+ const addressing = new EmailThreadAddressing(config.emailThreadSecret, config.emailInbox);
25
+ const email = new ResendEmailProvider(config.resendApiKey, config.emailFrom);
26
+ const worker = new ChannelsWorker(repository, new SupabaseRecipientDirectory(supabase), email, addressing, { log });
27
+ const events = new MachineEventHub();
28
+ const realtime = supabase.channel('amalgm-channels-work');
29
+ const wakeups = new HostedMachineWakeups(events, realtime, log);
30
+ const ingress = new EmailIngress(new SupabaseEmailIngressRepository(supabase), addressing);
17
31
  const api = createChannelsApi({
18
32
  service: createChannelsService(repository),
19
33
  authenticate: createChannelsAuthenticator({ issuer: config.authorizationIssuer, supabase }),
34
+ inbox: createMachineInboxService(new SupabaseMachineInboxRepository(supabase), events.stream),
20
35
  });
21
36
  const host = createChannelsHost({
22
37
  publicOrigin: config.publicOrigin,
23
38
  api,
39
+ webhookApi: createResendWebhookApi({
40
+ secret: config.resendWebhookSecret,
41
+ ingress,
42
+ email,
43
+ addressing,
44
+ wakeMachine: (userId, computerId) => wakeups.publish(userId, computerId),
45
+ apiKey: config.resendApiKey,
46
+ }),
24
47
  worker,
25
- pollIntervalMs: config.pollIntervalMs,
26
48
  log,
27
49
  });
50
+ realtime
51
+ .on('broadcast', { event: 'machine_wakeup' }, ({ payload }) => {
52
+ wakeups.receive(payload);
53
+ })
54
+ .subscribe((status) => log('realtime.status', { status }));
28
55
  host.server.listen(config.port, '0.0.0.0', () => log('host.ready', { port: config.port }));
29
56
  for (const signal of ['SIGINT', 'SIGTERM']) {
30
- process.once(signal, () => void host.close().finally(() => process.exit(0)));
57
+ process.once(signal, () => void supabase.removeChannel(realtime)
58
+ .then(() => host.close())
59
+ .finally(() => process.exit(0)));
31
60
  }
32
61
  function log(event, details = {}) {
33
62
  console.log(JSON.stringify({ service: 'amalgm-channels', event, ...details }));
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../host/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,2BAA2B,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;AACpC,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,sBAAsB,EAAE;IAC/E,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE;CACzD,CAAC,CAAC;AACH,MAAM,UAAU,GAAG,IAAI,0BAA0B,CAAC,QAAQ,CAAC,CAAC;AAC5D,MAAM,MAAM,GAAG,IAAI,cAAc,CAC/B,UAAU,EACV,IAAI,0BAA0B,CAAC,QAAQ,CAAC,EACxC,IAAI,mBAAmB,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,SAAS,CAAC,EAC9D,EAAE,GAAG,EAAE,CACR,CAAC;AACF,MAAM,GAAG,GAAG,iBAAiB,CAAC;IAC5B,OAAO,EAAE,qBAAqB,CAAC,UAAU,CAAC;IAC1C,YAAY,EAAE,2BAA2B,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,mBAAmB,EAAE,QAAQ,EAAE,CAAC;CAC5F,CAAC,CAAC;AACH,MAAM,IAAI,GAAG,kBAAkB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC,YAAY;IACjC,GAAG;IACH,MAAM;IACN,cAAc,EAAE,MAAM,CAAC,cAAc;IACrC,GAAG;CACJ,CAAC,CAAC;AAEH,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3F,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAU,EAAE,CAAC;IACpD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,SAAS,GAAG,CAAC,KAAa,EAAE,UAA6C,EAAE;IACzE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;AACjF,CAAC"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../host/main.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  import type { SupabaseClient } from '@supabase/supabase-js';
2
- import type { Delivery, DeliveryPage, DeliveryRepository, NormalizedListDeliveries, NotifyUserInput, PendingDelivery } from '../src/contract.js';
2
+ import type { Delivery, DeliveryPage, DeliveryRepository, NormalizedListDeliveries, NormalizedSendMessage, PendingDelivery, ChannelSendContext } from '../src/contract.js';
3
3
  export declare class SupabaseDeliveryRepository implements DeliveryRepository {
4
4
  #private;
5
5
  private readonly client;
6
6
  constructor(client: SupabaseClient);
7
- enqueue(userId: string, input: Required<NotifyUserInput>): Promise<Delivery>;
7
+ enqueue(userId: string, computerId: string | undefined, input: NormalizedSendMessage, context: ChannelSendContext): Promise<Delivery>;
8
8
  list(userId: string, query: NormalizedListDeliveries): Promise<DeliveryPage>;
9
9
  get(userId: string, deliveryId: string): Promise<Delivery | null>;
10
10
  claim(limit: number, leaseSeconds: number): Promise<readonly PendingDelivery[]>;
11
- complete(item: PendingDelivery, providerId: string, now: Date): Promise<boolean>;
11
+ markSent(item: PendingDelivery, providerId: string, now: Date): Promise<boolean>;
12
12
  fail(item: PendingDelivery, error: string, now: Date): Promise<boolean>;
13
+ nextWorkAt(): Promise<Date | null>;
13
14
  }
@@ -4,10 +4,12 @@ export class SupabaseDeliveryRepository {
4
4
  constructor(client) {
5
5
  this.client = client;
6
6
  }
7
- async enqueue(userId, input) {
7
+ async enqueue(userId, computerId, input, context) {
8
8
  return delivery(await this.#rpc('enqueue_amalgm_channel_delivery', {
9
9
  p_user_id: userId,
10
+ p_computer_id: computerId ?? null,
10
11
  p_delivery: input,
12
+ p_session_id: context.sessionId ?? null,
11
13
  }));
12
14
  }
13
15
  async list(userId, query) {
@@ -39,6 +41,7 @@ export class SupabaseDeliveryRepository {
39
41
  });
40
42
  return rows.map((row) => {
41
43
  const title = optional(row.title);
44
+ const replyToMessageId = optional(row.reply_to_message_id);
42
45
  return {
43
46
  id: text(row.id),
44
47
  userId: text(row.user_id),
@@ -48,11 +51,19 @@ export class SupabaseDeliveryRepository {
48
51
  idempotencyKey: text(row.idempotency_key),
49
52
  leaseToken: text(row.lease_token),
50
53
  attempts: number(row.attempts),
54
+ thread: {
55
+ id: text(row.thread_id),
56
+ sessionId: text(row.session_id),
57
+ subject: text(row.thread_subject),
58
+ messageId: text(row.rfc_message_id),
59
+ ...(replyToMessageId ? { replyToMessageId } : {}),
60
+ references: strings(row.references),
61
+ },
51
62
  };
52
63
  });
53
64
  }
54
- complete(item, providerId, now) {
55
- return this.#rpc('complete_amalgm_channel_delivery', {
65
+ markSent(item, providerId, now) {
66
+ return this.#rpc('mark_amalgm_channel_delivery_sent', {
56
67
  p_delivery_id: item.id,
57
68
  p_lease_token: item.leaseToken,
58
69
  p_provider_id: providerId,
@@ -67,6 +78,10 @@ export class SupabaseDeliveryRepository {
67
78
  p_now: now.toISOString(),
68
79
  });
69
80
  }
81
+ async nextWorkAt() {
82
+ const value = await this.#rpc('next_amalgm_channel_delivery_at', {});
83
+ return value ? new Date(value) : null;
84
+ }
70
85
  async #rpc(name, arguments_) {
71
86
  const { data, error } = await this.client.rpc(name, arguments_);
72
87
  if (error) {
@@ -81,6 +96,8 @@ function delivery(row) {
81
96
  const title = optional(row.title);
82
97
  const providerId = optional(row.provider_id);
83
98
  const error = optional(row.error);
99
+ const threadId = optional(row.thread_id);
100
+ const sessionId = optional(row.session_id);
84
101
  return {
85
102
  id: text(row.id),
86
103
  channel: 'email',
@@ -94,10 +111,13 @@ function delivery(row) {
94
111
  ...(optional(row.delivered_at) ? { deliveredAt: timestamp(row.delivered_at) } : {}),
95
112
  ...(providerId === undefined ? {} : { providerId }),
96
113
  ...(error === undefined ? {} : { error }),
114
+ ...(threadId === undefined ? {} : { threadId }),
115
+ ...(sessionId === undefined ? {} : { sessionId }),
97
116
  };
98
117
  }
99
118
  const text = (value) => typeof value === 'string' ? value : String(value ?? '');
100
119
  const optional = (value) => typeof value === 'string' && value ? value : undefined;
101
120
  const number = (value) => Number(value) || 0;
102
121
  const timestamp = (value) => new Date(String(value)).toISOString();
122
+ const strings = (value) => Array.isArray(value) ? value.map(text).filter(Boolean) : [];
103
123
  //# sourceMappingURL=repository.js.map