@amalgm/chat 0.1.2 → 0.2.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 (286) hide show
  1. package/AGENTS.md +98 -0
  2. package/PURPOSE.md +143 -112
  3. package/README.md +87 -54
  4. package/dist/acp/index.d.ts +7 -0
  5. package/dist/acp/index.d.ts.map +1 -0
  6. package/dist/acp/index.js +7 -0
  7. package/dist/acp/index.js.map +1 -0
  8. package/dist/acp/projection.d.ts +30 -0
  9. package/dist/acp/projection.d.ts.map +1 -0
  10. package/dist/acp/projection.js +147 -0
  11. package/dist/acp/projection.js.map +1 -0
  12. package/dist/api/conversations.d.ts +66 -0
  13. package/dist/api/conversations.d.ts.map +1 -0
  14. package/dist/api/conversations.js +202 -0
  15. package/dist/api/conversations.js.map +1 -0
  16. package/dist/api/index.d.ts +3 -3
  17. package/dist/api/index.d.ts.map +1 -1
  18. package/dist/api/index.js +1 -2
  19. package/dist/api/index.js.map +1 -1
  20. package/dist/conversations/index.d.ts +3 -0
  21. package/dist/conversations/index.d.ts.map +1 -0
  22. package/dist/conversations/index.js +3 -0
  23. package/dist/conversations/index.js.map +1 -0
  24. package/dist/conversations/transcript.d.ts +13 -0
  25. package/dist/conversations/transcript.d.ts.map +1 -0
  26. package/dist/conversations/transcript.js +171 -0
  27. package/dist/conversations/transcript.js.map +1 -0
  28. package/dist/conversations/types.d.ts +147 -0
  29. package/dist/conversations/types.d.ts.map +1 -0
  30. package/dist/conversations/types.js +9 -0
  31. package/dist/conversations/types.js.map +1 -0
  32. package/dist/events/constructors.d.ts +9 -15
  33. package/dist/events/constructors.d.ts.map +1 -1
  34. package/dist/events/constructors.js +6 -12
  35. package/dist/events/constructors.js.map +1 -1
  36. package/dist/events/index.d.ts +2 -3
  37. package/dist/events/index.d.ts.map +1 -1
  38. package/dist/events/index.js +2 -3
  39. package/dist/events/index.js.map +1 -1
  40. package/dist/events/schema.d.ts +2 -5
  41. package/dist/events/schema.d.ts.map +1 -1
  42. package/dist/events/schema.js +2 -5
  43. package/dist/events/schema.js.map +1 -1
  44. package/dist/execution/contract.d.ts +51 -0
  45. package/dist/execution/contract.d.ts.map +1 -0
  46. package/dist/execution/contract.js +127 -0
  47. package/dist/execution/contract.js.map +1 -0
  48. package/dist/execution/index.d.ts +6 -0
  49. package/dist/execution/index.d.ts.map +1 -0
  50. package/dist/execution/index.js +4 -0
  51. package/dist/execution/index.js.map +1 -0
  52. package/dist/execution/revision.d.ts +5 -0
  53. package/dist/execution/revision.d.ts.map +1 -0
  54. package/dist/execution/revision.js +17 -0
  55. package/dist/execution/revision.js.map +1 -0
  56. package/dist/execution/store.d.ts +26 -0
  57. package/dist/execution/store.d.ts.map +1 -0
  58. package/dist/execution/store.js +38 -0
  59. package/dist/execution/store.js.map +1 -0
  60. package/dist/index.d.ts +8 -25
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +7 -25
  63. package/dist/index.js.map +1 -1
  64. package/dist/input/index.d.ts +3 -0
  65. package/dist/input/index.d.ts.map +1 -0
  66. package/dist/input/index.js +2 -0
  67. package/dist/input/index.js.map +1 -0
  68. package/dist/input/prompt.d.ts +17 -0
  69. package/dist/input/prompt.d.ts.map +1 -0
  70. package/dist/input/prompt.js +104 -0
  71. package/dist/input/prompt.js.map +1 -0
  72. package/dist/normalizers/claude.d.ts +6 -9
  73. package/dist/normalizers/claude.d.ts.map +1 -1
  74. package/dist/normalizers/claude.js +5 -8
  75. package/dist/normalizers/claude.js.map +1 -1
  76. package/dist/normalizers/codex.d.ts +5 -8
  77. package/dist/normalizers/codex.d.ts.map +1 -1
  78. package/dist/normalizers/codex.js +4 -7
  79. package/dist/normalizers/codex.js.map +1 -1
  80. package/dist/normalizers/cursor.d.ts +1 -2
  81. package/dist/normalizers/cursor.d.ts.map +1 -1
  82. package/dist/normalizers/cursor.js +2 -3
  83. package/dist/normalizers/cursor.js.map +1 -1
  84. package/dist/normalizers/index.d.ts +3 -5
  85. package/dist/normalizers/index.d.ts.map +1 -1
  86. package/dist/normalizers/index.js +3 -5
  87. package/dist/normalizers/index.js.map +1 -1
  88. package/dist/normalizers/opencode.d.ts +2 -4
  89. package/dist/normalizers/opencode.d.ts.map +1 -1
  90. package/dist/normalizers/opencode.js +2 -4
  91. package/dist/normalizers/opencode.js.map +1 -1
  92. package/dist/normalizers/pi.d.ts +3 -4
  93. package/dist/normalizers/pi.d.ts.map +1 -1
  94. package/dist/normalizers/pi.js +3 -4
  95. package/dist/normalizers/pi.js.map +1 -1
  96. package/dist/sessions/chat.d.ts +176 -0
  97. package/dist/sessions/chat.d.ts.map +1 -0
  98. package/dist/sessions/chat.js +701 -0
  99. package/dist/sessions/chat.js.map +1 -0
  100. package/dist/sessions/index.d.ts +3 -0
  101. package/dist/sessions/index.d.ts.map +1 -0
  102. package/dist/sessions/index.js +2 -0
  103. package/dist/sessions/index.js.map +1 -0
  104. package/dist/transcript/index.d.ts +1 -1
  105. package/dist/transcript/index.js +1 -1
  106. package/dist/transcript/message-reducer.d.ts +1 -1
  107. package/dist/transcript/message-reducer.js +1 -1
  108. package/dist/transcript/part-accumulator.d.ts +1 -1
  109. package/dist/transcript/part-accumulator.js +1 -1
  110. package/dist/transcript/tool-display.d.ts +1 -1
  111. package/dist/transcript/tool-display.js +1 -1
  112. package/dist/transcript/tool-shape.d.ts +1 -1
  113. package/dist/transcript/tool-shape.js +1 -1
  114. package/dist/usage/finalize.d.ts +7 -9
  115. package/dist/usage/finalize.d.ts.map +1 -1
  116. package/dist/usage/finalize.js +11 -13
  117. package/dist/usage/finalize.js.map +1 -1
  118. package/dist/usage/index.d.ts +2 -3
  119. package/dist/usage/index.d.ts.map +1 -1
  120. package/dist/usage/index.js +2 -3
  121. package/dist/usage/index.js.map +1 -1
  122. package/dist/usage/model-catalog.d.ts +2 -3
  123. package/dist/usage/model-catalog.d.ts.map +1 -1
  124. package/dist/usage/model-catalog.js +10 -3
  125. package/dist/usage/model-catalog.js.map +1 -1
  126. package/dist/usage/normalize.d.ts +5 -7
  127. package/dist/usage/normalize.d.ts.map +1 -1
  128. package/dist/usage/normalize.js +3 -5
  129. package/dist/usage/normalize.js.map +1 -1
  130. package/dist/usage/ports.d.ts +14 -24
  131. package/dist/usage/ports.d.ts.map +1 -1
  132. package/dist/usage/ports.js +9 -17
  133. package/dist/usage/ports.js.map +1 -1
  134. package/dist/usage/steps.d.ts +70 -0
  135. package/dist/usage/steps.d.ts.map +1 -0
  136. package/dist/usage/steps.js +152 -0
  137. package/dist/usage/steps.js.map +1 -0
  138. package/dist/usage/tokens.d.ts +4 -7
  139. package/dist/usage/tokens.d.ts.map +1 -1
  140. package/dist/usage/tokens.js +4 -7
  141. package/dist/usage/tokens.js.map +1 -1
  142. package/docs/contracts/acp-and-step-usage.md +51 -0
  143. package/docs/contracts/conversation-persistence.md +47 -0
  144. package/docs/contracts/input-and-execution.md +177 -0
  145. package/docs/contracts/platform-authorization.md +22 -0
  146. package/host/adapters/acp-capabilities.js +30 -0
  147. package/host/adapters/acp-client.js +156 -0
  148. package/host/adapters/acp.js +284 -0
  149. package/host/adapters/claude.js +235 -0
  150. package/host/adapters/codex.js +733 -0
  151. package/host/adapters/cursor.js +347 -0
  152. package/host/adapters/input-capabilities.js +89 -0
  153. package/host/adapters/opencode.js +486 -0
  154. package/host/adapters/pi.js +519 -0
  155. package/host/adapters/prompt.js +212 -0
  156. package/host/auth.js +257 -0
  157. package/host/credentials/store.js +209 -0
  158. package/host/http.d.ts +32 -0
  159. package/host/http.js +238 -0
  160. package/host/index.d.ts +84 -0
  161. package/host/index.js +59 -0
  162. package/host/lib/runtime-paths.js +44 -0
  163. package/host/model-resolution.js +313 -0
  164. package/host/native-contract.js +185 -0
  165. package/host/native-runtime.js +194 -0
  166. package/host/permissions.js +27 -0
  167. package/host/platform-egress.js +146 -0
  168. package/host/recorder.js +61 -0
  169. package/host/sqlite/index.d.ts +69 -0
  170. package/host/sqlite/index.js +704 -0
  171. package/host/tooling/mcp-bundle.js +214 -0
  172. package/host/tooling/mcp-relay.js +83 -0
  173. package/host/tooling/native-binaries.js +667 -0
  174. package/host/tooling/native-config.js +460 -0
  175. package/host/tooling/package-import.js +111 -0
  176. package/host/tooling/runtime-home.js +26 -0
  177. package/host/tooling/system-instructions.js +69 -0
  178. package/host/tooling/system-prompt.js +35 -0
  179. package/package.json +45 -26
  180. package/skills/chat/SKILL.md +97 -0
  181. package/skills/chat/agents/openai.yaml +4 -0
  182. package/skills/chat/references/contracts.md +131 -0
  183. package/dist/api/chat-payloads.d.ts +0 -10
  184. package/dist/api/chat-payloads.d.ts.map +0 -1
  185. package/dist/api/chat-payloads.js +0 -57
  186. package/dist/api/chat-payloads.js.map +0 -1
  187. package/dist/contract/chat-input.d.ts +0 -144
  188. package/dist/contract/chat-input.d.ts.map +0 -1
  189. package/dist/contract/chat-input.js +0 -261
  190. package/dist/contract/chat-input.js.map +0 -1
  191. package/dist/contract/create.d.ts +0 -191
  192. package/dist/contract/create.d.ts.map +0 -1
  193. package/dist/contract/create.js +0 -249
  194. package/dist/contract/create.js.map +0 -1
  195. package/dist/contract/freeze.d.ts +0 -85
  196. package/dist/contract/freeze.d.ts.map +0 -1
  197. package/dist/contract/freeze.js +0 -151
  198. package/dist/contract/freeze.js.map +0 -1
  199. package/dist/contract/harnesses.d.ts +0 -21
  200. package/dist/contract/harnesses.d.ts.map +0 -1
  201. package/dist/contract/harnesses.js +0 -39
  202. package/dist/contract/harnesses.js.map +0 -1
  203. package/dist/contract/index.d.ts +0 -17
  204. package/dist/contract/index.d.ts.map +0 -1
  205. package/dist/contract/index.js +0 -17
  206. package/dist/contract/index.js.map +0 -1
  207. package/dist/contract/input.d.ts +0 -45
  208. package/dist/contract/input.d.ts.map +0 -1
  209. package/dist/contract/input.js +0 -78
  210. package/dist/contract/input.js.map +0 -1
  211. package/dist/contract/models.d.ts +0 -57
  212. package/dist/contract/models.d.ts.map +0 -1
  213. package/dist/contract/models.js +0 -380
  214. package/dist/contract/models.js.map +0 -1
  215. package/dist/contract/payload-merge.d.ts +0 -76
  216. package/dist/contract/payload-merge.d.ts.map +0 -1
  217. package/dist/contract/payload-merge.js +0 -215
  218. package/dist/contract/payload-merge.js.map +0 -1
  219. package/dist/events/tool-display.d.ts +0 -22
  220. package/dist/events/tool-display.d.ts.map +0 -1
  221. package/dist/events/tool-display.js +0 -60
  222. package/dist/events/tool-display.js.map +0 -1
  223. package/dist/events/tool-shape.d.ts +0 -48
  224. package/dist/events/tool-shape.d.ts.map +0 -1
  225. package/dist/events/tool-shape.js +0 -225
  226. package/dist/events/tool-shape.js.map +0 -1
  227. package/dist/relay/index.d.ts +0 -65
  228. package/dist/relay/index.d.ts.map +0 -1
  229. package/dist/relay/index.js +0 -197
  230. package/dist/relay/index.js.map +0 -1
  231. package/dist/turns/index.d.ts +0 -11
  232. package/dist/turns/index.d.ts.map +0 -1
  233. package/dist/turns/index.js +0 -11
  234. package/dist/turns/index.js.map +0 -1
  235. package/dist/turns/queries.d.ts +0 -36
  236. package/dist/turns/queries.d.ts.map +0 -1
  237. package/dist/turns/queries.js +0 -37
  238. package/dist/turns/queries.js.map +0 -1
  239. package/dist/turns/replay.d.ts +0 -105
  240. package/dist/turns/replay.d.ts.map +0 -1
  241. package/dist/turns/replay.js +0 -163
  242. package/dist/turns/replay.js.map +0 -1
  243. package/dist/turns/status.d.ts +0 -49
  244. package/dist/turns/status.d.ts.map +0 -1
  245. package/dist/turns/status.js +0 -56
  246. package/dist/turns/status.js.map +0 -1
  247. package/dist/turns/turn-store.d.ts +0 -109
  248. package/dist/turns/turn-store.d.ts.map +0 -1
  249. package/dist/turns/turn-store.js +0 -169
  250. package/dist/turns/turn-store.js.map +0 -1
  251. package/dist/warm/client-cache.d.ts +0 -47
  252. package/dist/warm/client-cache.d.ts.map +0 -1
  253. package/dist/warm/client-cache.js +0 -48
  254. package/dist/warm/client-cache.js.map +0 -1
  255. package/dist/warm/envelope-cache.d.ts +0 -79
  256. package/dist/warm/envelope-cache.d.ts.map +0 -1
  257. package/dist/warm/envelope-cache.js +0 -85
  258. package/dist/warm/envelope-cache.js.map +0 -1
  259. package/dist/warm/index.d.ts +0 -18
  260. package/dist/warm/index.d.ts.map +0 -1
  261. package/dist/warm/index.js +0 -12
  262. package/dist/warm/index.js.map +0 -1
  263. package/dist/warm/payload.d.ts +0 -137
  264. package/dist/warm/payload.d.ts.map +0 -1
  265. package/dist/warm/payload.js +0 -112
  266. package/dist/warm/payload.js.map +0 -1
  267. package/dist/warm/revision.d.ts +0 -70
  268. package/dist/warm/revision.d.ts.map +0 -1
  269. package/dist/warm/revision.js +0 -63
  270. package/dist/warm/revision.js.map +0 -1
  271. package/dist/warm/store-policy.d.ts +0 -57
  272. package/dist/warm/store-policy.d.ts.map +0 -1
  273. package/dist/warm/store-policy.js +0 -89
  274. package/dist/warm/store-policy.js.map +0 -1
  275. package/dist/warm/store.d.ts +0 -42
  276. package/dist/warm/store.d.ts.map +0 -1
  277. package/dist/warm/store.js +0 -69
  278. package/dist/warm/store.js.map +0 -1
  279. package/dist/wire/frames.d.ts +0 -208
  280. package/dist/wire/frames.d.ts.map +0 -1
  281. package/dist/wire/frames.js +0 -245
  282. package/dist/wire/frames.js.map +0 -1
  283. package/dist/wire/index.d.ts +0 -7
  284. package/dist/wire/index.d.ts.map +0 -1
  285. package/dist/wire/index.js +0 -7
  286. package/dist/wire/index.js.map +0 -1
package/AGENTS.md ADDED
@@ -0,0 +1,98 @@
1
+ # @amalgm/chat contributor guide
2
+
3
+ Read `PURPOSE.md` before changing this package. It is the load-bearing purpose
4
+ and axiom document. Update it in the same change whenever a public law changes.
5
+
6
+ ## Ownership
7
+
8
+ Chat answers: “run this resolved agent in a durable conversation.”
9
+
10
+ - `@amalgm/agents` owns agent identity, immutable revisions, installation, and
11
+ Agent Home descriptors.
12
+ - `@amalgm/tools` owns the Toolbox catalog and immutable tool selection.
13
+ - `@amalgm/chat` owns prepared executions, conversations, turns, the runtime
14
+ pump, official ACP updates, persistence, reconnect, interrupt, and usage.
15
+ - A host owns credentials, processes, filesystems, routing, and provider
16
+ network effects.
17
+ - `@amalgm/stream` and application UIs render Chat output; they do not own turn
18
+ lifetime or transport.
19
+
20
+ There is one implementation of each concern. Git is history; do not add a
21
+ second route, store, event grammar, payload cache, or session subsystem for
22
+ older callers.
23
+
24
+ ## Working laws
25
+
26
+ 1. The authenticated host supplies the owner. Requests cannot choose it.
27
+ 2. Agents resolves one installation and exact immutable revision before Chat
28
+ executes it.
29
+ 3. Preparation may resolve control-plane facts. Start and send consume one
30
+ immutable prepared handle and never rediscover configuration.
31
+ 4. Prompt content and streamed output use official ACP shapes. Amalgm adds only
32
+ namespaced durability metadata and its separate step-usage record.
33
+ 5. One runtime pump owns provider iteration. Disconnect only removes a
34
+ subscriber; interrupt is the explicit provider-stop operation.
35
+ 6. Accepted input and every output update commit before execution or fanout
36
+ advances past them.
37
+ 7. A turn has monotonically increasing durable sequence numbers and seals once.
38
+ Reconnect replays after a cursor and then follows the same live turn.
39
+ 8. Restart preserves committed output and marks uncertain work interrupted
40
+ unless an adapter proves reattachment.
41
+ 9. Auth mode is explicit and never falls back: platform, subscription, or BYOK.
42
+ 10. Unknown usage is `null`, not fabricated zero. Billing authority is separate
43
+ from context telemetry.
44
+
45
+ ## Public composition
46
+
47
+ Application code uses `Chat` or the browser-safe `@amalgm/chat/api` client.
48
+ Node hosts use `createChatHost` from `@amalgm/chat/host`. That composed host
49
+ owns one owner-scoped SQLite journal and one HTTP protocol under `/chat`:
50
+
51
+ ```text
52
+ POST /chat/executions
53
+ GET /chat/conversations
54
+ POST /chat/conversations
55
+ GET /chat/conversations/:id
56
+ PATCH /chat/conversations/:id
57
+ DELETE /chat/conversations/:id
58
+ POST /chat/conversations/:id/resume
59
+ POST /chat/conversations/:id/turns
60
+ GET /chat/conversations/:id/turns/:turnId/events
61
+ GET /chat/conversations/:id/active
62
+ POST /chat/conversations/:id/interrupt
63
+ ```
64
+
65
+ Platform egress and selected remote MCP relays are session-local auxiliary
66
+ doors under that same `/chat` host. They are not separate Chat services.
67
+
68
+ ## Tree
69
+
70
+ ```text
71
+ src/input/ official ACP prompt validation
72
+ src/execution/ normalized contracts and immutable prepared handles
73
+ src/sessions/ Chat capability, runtime pump, reconnect, interrupt
74
+ src/conversations/ private records, transcript projection, storage ports
75
+ src/acp/ official ACP projection
76
+ src/events/ provider-neutral native adapter events
77
+ src/normalizers/ provider event translators
78
+ src/transcript/ transcript reduction
79
+ src/usage/ context, step, turn, and billing-normalization laws
80
+ host/adapters/ native and generic ACP effects
81
+ host/sqlite/ WAL conversation store and durable turn journal
82
+ host/http.js the one HTTP/SSE protocol
83
+ host/index.js the one Node host composition
84
+ ```
85
+
86
+ ## Verification
87
+
88
+ Every change must leave purpose, axioms, file tree, and behavior aligned.
89
+
90
+ ```bash
91
+ npm test
92
+ npm pack --dry-run
93
+ ```
94
+
95
+ Test invariants rather than individual symptoms: exact revision resolution,
96
+ input-before-invocation, update-before-fanout, one pump, disconnect survival,
97
+ cursor replay, idempotent interrupt, restart recovery, and official ACP-only
98
+ surface output.
package/PURPOSE.md CHANGED
@@ -1,115 +1,146 @@
1
- # @amalgm/chat — the conversation SDK
2
-
3
- Status: extraction in progress · founded 2026-08-06 · source of truth for laws:
4
- `amalgm-engine/runtime/scripts/chat-core/` (production until cutover)
1
+ # @amalgm/chat — purpose and axioms
5
2
 
6
3
  ## Purpose
7
4
 
8
- Chat is Amalgm's central product surface: every user message, every agent
9
- turn, every streamed token flows through one pipeline. This repo owns that
10
- pipeline's **laws** what a turn is, what events mean, what a transcript
11
- contains, how a send is prepared and billed as a standalone SDK any host
12
- can run: the engine runtime today, the Fly workers, the channels bridge
13
- (email/Telegram/WhatsApp/Slack), and third-party apps tomorrow.
14
-
15
- **Membership test:** a rule belongs here if it is a law of the conversation
16
- itself independent of any particular host, harness, or UI. If it spawns a
17
- process, opens a socket, or touches a disk, it is a host adapter behind a
18
- port, not a law.
19
-
20
- ## The eight jobs
21
-
22
- 1. **The event grammar.** One canonical vocabulary (`text.delta` …
23
- `usage.final` `done`) that every harness's native events are translated
24
- into, schema-validated, and one projection of it onto the wire (the
25
- ACP-shaped SSE frames). The grammar is the product's spine.
26
- 2. **Normalization.** Per-harness translators (claude, codex, opencode,
27
- cursor, pi) from native protocol events into the grammar. Pure functions;
28
- every claim traceable to a recorded fixture.
29
- 3. **Transcript truth.** One message reducer shared by both ends of the
30
- wire: what the user watched streaming is byte-shape-identical to what
31
- persists and reloads. Parts (text / reasoning / tool / image /
32
- compaction) are the transcript's atoms.
33
- 4. **The turn machine.** One active turn per session; the in-memory chunk
34
- log (TurnStore) that makes reconnect/replay possible; the status
35
- vocabulary (`streaming → cancelling → complete | cancelled | error |
36
- save_failed | aborted`); the finalization order that never loses a
37
- transcript silently.
38
- 5. **The frozen contract.** A session's identity harness, auth, model,
39
- cwd, instructions, tool set resolves once at creation and freezes.
40
- Model names canonicalize through four deliberate identities (display /
41
- runtime-CLI / billing / context-profile). Changing frozen fields means a
42
- new session; that is what makes billing identity trustworthy.
43
- 6. **Warm payloads.** The send path carries ids, not configuration: the full
44
- launch payload is published to the machine's local store while the user
45
- types (revision-hashed, 409 on mismatch), and the machine record is the
46
- authority client-supplied billing-relevant fields are deleted, never
47
- trusted.
48
- 7. **The usage law.** Token-list algebra, per-harness exactness rules,
49
- the ownership fork (amalgm billing proxy meters via egress; byok /
50
- provider_auth local logging), and the cost rules (unknown cost is
51
- null, never zero).
52
- 8. **Session-bound relays.** Amalgm-auth turns reach the billing proxy only
53
- through a per-session egress token, and harness MCP traffic reaches only
54
- servers frozen into that session's contract. Request paths never choose
55
- an arbitrary credential or destination.
56
-
57
- ## Axioms
58
-
59
- 1. **The engine copy is production truth until cutover.** Laws here are
60
- ported byte-exact from `amalgm-engine/runtime/scripts/chat-core/`;
61
- deliberate deviations carry an `engineParity: false` marker and a reason.
62
- 2. **One reducer, both ends of the wire.** The engine's part accumulation
63
- and the browser's live rendering are the same algorithm. Divergence is a
64
- bug by definition.
65
- 3. **The wire grammar is a public contract.** Tasks, subagents, Slack
66
- inbound, email inbound, and hosted apps already speak `POST /chat` + the
67
- SSE grammar. Evolution is additive; a removed or reshaped frame is a
68
- breaking change to systems that never imported this package.
69
- 4. **Send carries ids, not payloads.** The machine payload store is the
70
- authority; precedence is payload record > chatInput > raw body; the
71
- browser is never trusted for billing-relevant fields.
72
- 5. **Session identity freezes at creation.** The frozen-contract fingerprint
73
- decides; a turn whose frozen fields drifted continues the old contract.
74
- 6. **Storage is a choice; the meter is not.** Conversation persistence can
75
- be declined (`storage: 'none'`); usage logging on amalgm billing cannot.
76
- 7. **The host supplies effects.** Nothing in `src/` imports `node:*`, reads
77
- `process.env`, or calls `Date.now()`/`Math.random()` directly clocks,
78
- crypto, stores, transports arrive as injected ports.
79
- 8. **Contracts are byte-exact.** Error strings, header names, status
80
- vocabularies, thresholds, and merge precedence match the engine to the
81
- byte; exams pin them.
82
- 9. **One runtime dependency: zod.** The event grammar's executable form is
83
- the same validator the engine runs a hand-rolled clone would make
84
- parity a fiction. Nothing else gets in.
85
- 10. **Amalgm dogfoods its own SDK.** The end state (spec §10): ChatView's
86
- data layer imports nothing but `@amalgm/chat`; the engine host mounts
87
- these laws instead of containing them.
88
- 11. **Relay authority comes from the frozen contract.** Egress replaces the
89
- session token with the host's proxy credential and stamps usage identity;
90
- MCP relay replaces caller auth with the selected frozen server headers.
91
- Neither relay accepts an upstream URL from the request.
92
-
93
- ## Five surfaces
94
-
95
- | Surface | Form | Status |
96
- |---|---|---|
97
- | SDK | `@amalgm/chat` (this package's `src/`) | porting |
98
- | API | router factory a host mounts | chat payloads served |
99
- | CLI | `amalgm-chat` diagnostic commands | after SDK |
100
- | MCP | tool definitions + dispatch | after SDK |
101
- | Skill | `skills/chat/SKILL.md` | after SDK |
102
-
103
- All doors are thin: one behavior in `src/`, adapters around it.
104
-
105
- ## The prior art this repo unifies
106
-
107
- - `docs/chat-sdk-spec.md` the locked public-API spec (2026-07-13).
108
- - `docs/chat-internals-and-plans.md` the canonical pipeline map.
109
- - `amalgm-engine/packages/chat/` — the milestone-1 client prototype.
110
- - The orphaned 84-exam suite (`amalgm-ui/scripts/chat-core/tests/run.js`)
111
- the richest chat test suite ever written for this codebase, currently
112
- running nowhere. It is revived here.
113
-
114
- See `docs/responsibilities.md` for every duty, the knife table, the garbage
115
- ledger, and the known wounds.
5
+ Amalgm Chat is the small, reliable, high-performance SDK for talking to any
6
+ agent. It serves developers and agents building chat products: they should be
7
+ able to create a new interface in minutes, switch among Claude Code, Codex,
8
+ Cursor, OpenCode, Pi, and ACP agents without changing the interface's
9
+ conversation model, and receive one trustworthy transcript and usage record.
10
+
11
+ The SDK owns conversation laws. It does not own process spawning, credentials,
12
+ cloud synchronization, workspaces, provider installation, or UI rendering.
13
+ Those effects are supplied by a host. Amalgm's UI, API, CLI, MCP surface,
14
+ automations, and third-party apps must all use the same public SDK.
15
+
16
+ ## Workstream 1 primitives
17
+
18
+ - An **ACP session** is the standard agent/client interaction boundary.
19
+ - A **turn** is one accepted user prompt and its ordered result.
20
+ - A **model step** is one provider model invocation inside a turn.
21
+ - A **session update** is the official ACP output shape.
22
+ - A **step usage record** is Amalgm's immutable usage extension for one model
23
+ step.
24
+ - A **turn usage record** is derived from its finalized step usage records.
25
+ - A **raw record** is the native provider evidence retained beside a normalized
26
+ projection.
27
+ - A **prompt** is an ordered list of official ACP content blocks.
28
+ - An **execution contract** is the normalized set of choices required by one
29
+ future turn.
30
+ - A **prepared execution** is an immutable execution revision plus an opaque,
31
+ already-resolved host binding.
32
+
33
+ ## Workstream 1 axioms
34
+
35
+ 1. ACP owns session lifecycle, prompt content, MCP server configuration,
36
+ permission requests, agent output, tool activity, and terminal stop reasons
37
+ wherever the stable protocol has those semantics.
38
+ 2. Amalgm does not copy ACP types. The public ACP boundary imports and exposes
39
+ the official TypeScript SDK.
40
+ 3. Amalgm extensions use ACP's `_meta` mechanism when they annotate an ACP
41
+ value. Rich usage remains a separate typed record because stable ACP does
42
+ not describe step-level token usage.
43
+ 4. Text and reasoning are ACP content chunks. Tool activity is ACP `tool_call`
44
+ and `tool_call_update` with `pending`, `in_progress`, `completed`, or
45
+ `failed` state.
46
+ 5. MCP describes remote tools and their transport. In-process tools may skip
47
+ MCP transport, but they expose the same content and result semantics.
48
+ 6. A model step has one stable id and at most one finalized usage record.
49
+ Replay, reconnect, and additional subscribers cannot add usage.
50
+ 7. Unknown token counts are `null`, never zero. Zero means the provider
51
+ explicitly measured zero.
52
+ 8. Turn usage is the bucket-wise sum of its steps only when every contributing
53
+ step reports that bucket. Otherwise that turn bucket is `null`.
54
+ 9. A cumulative provider counter becomes a step delta only against a proven
55
+ prior baseline. A missing baseline or counter reset remains unknown.
56
+ 10. Raw provider usage and its provenance survive normalization.
57
+ 11. Every surface consumes official ACP session updates; Chat has no second
58
+ event grammar.
59
+ 12. Provider-specific behavior stays in adapters. Surfaces do not branch on
60
+ provider names.
61
+ 13. Prompt content and execution configuration are separate. Send carries an
62
+ ACP prompt and a prepared execution handle, never an untrusted launch
63
+ payload.
64
+ 14. Preparing may discover and resolve. Starting and sending may only consume
65
+ an already-prepared binding.
66
+ 15. Auth and computer are selected per turn, not frozen to a conversation. A
67
+ running provider operation retains the execution facts it actually used.
68
+ 16. A conversation store commits accepted input and every streamed update
69
+ before the provider or subscribers can advance past it.
70
+ 17. Provider iteration belongs to the runtime pump, never to a client
71
+ subscriber. Disconnect and reconnect are cursor changes, not turn lifetime.
72
+ 18. A process restart preserves committed output and marks uncertain active
73
+ work interrupted; it never silently repeats external effects.
74
+ 19. Agents resolves identity and immutable revision; Chat owns the conversation
75
+ that executes that resolved revision. Chat never imports the Agents store.
76
+ 20. Interrupt is a provider operation followed by a durable terminal state. A
77
+ turn is not cancelled merely because a subscriber disconnected or a UI
78
+ stopped rendering it.
79
+ 21. Interrupt is idempotent for one active turn. The runtime receives at most
80
+ one stop request and every subscriber observes the same persisted result.
81
+ 22. A host exposes Chat by composing the public `Chat` capability and its one
82
+ HTTP protocol; no other turn store, pump, or transport grammar exists.
83
+ 23. A host supplies the machine-local runtime-state root. Every managed
84
+ provider home descends from that root and never enters portable user ground.
85
+ 24. A prepared model binding retains three distinct identities: the normalized
86
+ selected model, the gateway identity used for auth and usage, and the exact
87
+ CLI token accepted by its adapter. Surfaces select; preparation translates.
88
+ No identity may be reconstructed from another after preparation.
89
+ 25. Platform credentials are live host capabilities, never frozen strings. The
90
+ egress boundary requests one audience- and provider-scope-exact grant for
91
+ each HTTP operation, retries one rejected grant once after renewal, and
92
+ lets any remaining failure become the turn's durable failed state.
93
+
94
+ ## North-star proof
95
+
96
+ A tiny example interface must use only the public SDK to:
97
+
98
+ - start or resume a session;
99
+ - send text and attachments;
100
+ - expose MCP and ordinary tools;
101
+ - handle permission requests;
102
+ - render text, reasoning, and tool activity from official ACP shapes;
103
+ - reconnect to the same turn; and
104
+ - interrupt a running turn without confusing disconnect with cancellation;
105
+ - show finalized usage for every model step and the derived turn.
106
+
107
+ Changing the selected adapter may change negotiated capabilities. It may not
108
+ require a new event grammar, transcript reducer, or UI data model.
109
+
110
+ ## Current truth
111
+
112
+ The public SDK now has a high-level `Chat` capability, official ACP prompt
113
+ content, a normalized prepared-execution contract, exact revision handles, and
114
+ an official ACP output projection. Five native adapters and one generic ACP
115
+ adapter receive the canonical prompt directly. Every native adapter has been
116
+ exercised through the real public session path with text and an image. Claude
117
+ Code and Pi also passed combined image, selected-MCP-tool, `ask` permission,
118
+ tool-result, and streamed-output turns. Cursor and OpenCode have equivalent
119
+ real tool acceptance, and the generic adapter passed text and a selected MCP
120
+ tool against the live Cursor ACP server. Cursor accepts its native login or a
121
+ Cursor API key; OpenCode accepts an exact catalog variant. Step-usage identity
122
+ and aggregation are first-class laws. The public path now persists private
123
+ conversation headers, prompts, transcript projections, terminal turns, and
124
+ official ACP updates through an injected store. The reference SQLite adapter
125
+ uses WAL and commits input before provider invocation and output before fanout.
126
+ One background pump continues without subscribers; cursor replay survives a
127
+ host restart, and uncertain running turns recover as interrupted. The shipped
128
+ skill teaches this path.
129
+
130
+ The composable Node host uses the same owner-scoped SQLite journal and exposes
131
+ one HTTP protocol for execution preparation, conversation start/resume, turns,
132
+ official ACP updates, cursor reconnect, and interrupt. One user database holds
133
+ many private conversations across agent installations, while provider-native
134
+ homes remain isolated per installation beneath a host-supplied machine-local
135
+ runtime-state root.
136
+
137
+ It is not yet the complete north star. The remote gateway/Live authority does
138
+ not yet expose the conversation catalog and change stream; Live/R2
139
+ synchronization is not connected; provider adapters do not yet feed the full
140
+ step-usage ledger; every attachment form has not been exercised against every
141
+ provider; and permission requests are not yet a first-class application
142
+ stream. Generic ACP admission works from an immutable agent launch descriptor,
143
+ but Amp, Gemini CLI, and Kiro were not installed for individual acceptance
144
+ tests.
145
+
146
+ The public contracts live in `docs/contracts/` and the shipped Chat skill.
package/README.md CHANGED
@@ -1,66 +1,99 @@
1
1
  # @amalgm/chat
2
2
 
3
- The Amalgm conversation SDK: every law of the chat pipeline — the event
4
- grammar, harness normalization, transcript truth, the turn machine, the
5
- frozen session contract, warm payloads, and the usage law extracted from
6
- the engine as a standalone package, plus a faithful reference host. Start
7
- with [PURPOSE.md](PURPOSE.md) (the charter, seven jobs, ten axioms) and
8
- [docs/responsibilities.md](docs/responsibilities.md) (every duty, the
9
- four-copies ground truth, the garbage ledger, the known wounds).
3
+ The small, provider-agnostic SDK for agent conversations. Application code
4
+ prepares an execution, starts or resumes a session, sends official ACP content,
5
+ and consumes official ACP updates. Claude Code, Codex, Cursor, OpenCode, Pi,
6
+ and generic ACP behavior stay behind host adapters.
10
7
 
11
- ```
12
- src/events/ the canonical event grammar (constructors + zod schema)
13
- src/wire/ SSE frame projection (ACP-shaped AgentStreamEvent)
14
- src/transcript/ message reducer, parts, tool shape/display
15
- src/turns/ TurnStore, status machine, replay grammar
16
- src/contract/ harnesses, model identities, freeze algebra, chatInput,
17
- payload merge, input normalization
18
- src/warm/ warm-payload laws: revision, fingerprint, cache policies
19
- src/usage/ token algebra, turn finalization, ownership fork, catalog
20
- src/normalizers/ claude · codex · opencode · cursor · pi → the grammar
21
- src/api/ fetch adapters over SDK services
22
- src/ports.ts the ports a host injects (clock, hash, uuid)
23
- host/ the reference host: adapters, tooling, orchestration,
24
- chat-server shell (effectful, imports the laws from dist)
25
- docs/ the chat corpus: SDK spec, runtime law, normalizer
26
- contracts + errata, internals map, warm payloads,
27
- reducer divergences
28
- ```
29
-
30
- ```bash
31
- npm install
32
- npm test # build + every exam
33
- ```
8
+ Read [AGENTS.md](AGENTS.md) for the architecture and current gaps,
9
+ [PURPOSE.md](PURPOSE.md) for the axioms, and
10
+ [docs/contracts/input-and-execution.md](docs/contracts/input-and-execution.md)
11
+ for the public input path, and
12
+ [docs/contracts/conversation-persistence.md](docs/contracts/conversation-persistence.md)
13
+ for local conversations and durable turns.
34
14
 
35
- ## Serve the machine chat-payload API
15
+ ```ts
16
+ import { Chat } from "@amalgm/chat";
17
+ import {
18
+ SqliteConversationStore,
19
+ createSqliteJournalBatchPolicy,
20
+ } from "@amalgm/chat/sqlite";
36
21
 
37
- ```js
38
- import { createChatPayloadApi } from '@amalgm/chat/api';
39
- import { ChatPayloadStore } from '@amalgm/chat/warm';
22
+ const conversations = new SqliteConversationStore({
23
+ databasePath: "/amalgm/users/user-id/conversations/conversations.sqlite",
24
+ });
25
+ const chat = new Chat({
26
+ runtime,
27
+ ownerUserId: authenticatedUser.id,
28
+ conversations,
29
+ uuid,
30
+ clockIso,
31
+ sha256Hex,
32
+ journalBatch: createSqliteJournalBatchPolicy(),
33
+ });
40
34
 
41
- const payloads = new ChatPayloadStore({
42
- store: {
43
- read: () => readPayloadMapFromYourHost(),
44
- write: (next) => writePayloadMapFromYourHost(next),
35
+ const execution = await chat.prepareExecution("composer", {
36
+ agent: {
37
+ adapterId: "codex",
38
+ installationId: "my-codex",
39
+ revisionId: "revision-7",
40
+ },
41
+ model: {
42
+ providerId: "openai",
43
+ modelId: "gpt-5.6-codex",
44
+ settings: [{ configId: "thought_level", value: "high" }],
45
+ },
46
+ auth: { mode: "subscription", bindingId: "codex-login" },
47
+ tools: { revisionId: null, toolIds: [] },
48
+ cwd: {
49
+ path: "/workspace/project",
50
+ workspaceId: null,
51
+ materializationRevision: null,
45
52
  },
46
- clockIso: () => new Date().toISOString(),
47
- clockMs: () => Date.now(),
48
- emit: (event) => publishStateEventFromYourHost(event),
53
+ permissionMode: "ask",
54
+ computerId: "computer-1",
49
55
  });
50
56
 
51
- const api = createChatPayloadApi(payloads);
52
- const response = await api(new Request('http://local/chat-payloads'));
57
+ const session = await chat.startSession({ execution });
58
+ const updates = await session.send({
59
+ execution,
60
+ prompt: { parts: [{ type: "text", text: "Hello" }] },
61
+ });
62
+
63
+ for await (const update of updates) render(update);
64
+
65
+ // Any client can reconnect from its last committed sequence.
66
+ for await (const update of session.reconnect(updates.turnId, { afterSequence: 42 })) {
67
+ render(update);
68
+ }
53
69
  ```
54
70
 
55
- The adapter preserves the engine's `/chat-payloads` list, lookup, and write
56
- contract. The host chooses persistence, clocks, and state-event delivery;
57
- record normalization, retention, and route responses stay in this package.
71
+ `prepareExecution` is the control plane. A host resolves tools, auth, agent
72
+ revision, provider/model, cwd, permissions, and placement there. Start and send
73
+ consume the prepared revision; send does not discover configuration.
74
+
75
+ ```text
76
+ src/input/ ordered official ACP prompt content
77
+ src/execution/ normalized contracts, revisions, and prepared handles
78
+ src/sessions/ public prepare/start/resume/send capability
79
+ src/conversations/ private conversation, transcript, storage, and UI laws
80
+ src/acp/ official ACP door and event projection
81
+ src/events/ canonical native event grammar
82
+ src/normalizers/ native event translators
83
+ src/transcript/ transcript reduction
84
+ src/usage/ step and turn usage normalization
85
+ host/adapters/ effectful native protocol adapters
86
+ host/sqlite/ WAL conversation store and durable turn journal
87
+ host/http.js the one HTTP/SSE protocol, platform egress, and MCP relay door
88
+ ```
89
+
90
+ ```bash
91
+ npm install
92
+ npm test
93
+ npm pack --dry-run
94
+ ```
58
95
 
59
- Rules of the house: `src/` is pure — one runtime dependency (zod, axiom 9),
60
- no `node:*`, no env, no ambient clock; hosts inject effects through
61
- `src/ports.ts`. `host/` is effectful by design but never re-implements a
62
- law it imports them. Contracts are byte-exact against the shipped engine
63
- (`amalgm-engine/runtime/scripts/chat-core/`, production truth until
64
- cutover — axiom 1); every module was differentially verified against the
65
- engine's own code. Tests are exams — each states a law, and a mutant must
66
- be convicted by at least one.
96
+ Package releases use `.github/workflows/publish-package.yml`. A reviewed main
97
+ commit is tagged exactly `v<package version>`; trusted npm publishing stages
98
+ that package, and a maintainer approves the staged artifact with 2FA. Manual
99
+ dirty-worktree publishing is not a release path.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The canonical agent/client boundary is the official ACP SDK. Re-exporting
3
+ * it here gives Chat consumers one versioned door without copying the schema.
4
+ */
5
+ export * from '@agentclientprotocol/sdk';
6
+ export { AMALGM_ACP_META_KEY, toAcpSessionNotification, toAcpSessionUpdate, toAcpToolKind, type AcpProjectionContext, type AmalgmAcpMeta, } from './projection.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/acp/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,0BAA0B,CAAC;AAEzC,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,aAAa,GACnB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The canonical agent/client boundary is the official ACP SDK. Re-exporting
3
+ * it here gives Chat consumers one versioned door without copying the schema.
4
+ */
5
+ export * from '@agentclientprotocol/sdk';
6
+ export { AMALGM_ACP_META_KEY, toAcpSessionNotification, toAcpSessionUpdate, toAcpToolKind, } from './projection.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/acp/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,cAAc,0BAA0B,CAAC;AAEzC,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,aAAa,GAGd,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,30 @@
1
+ import type { SessionNotification, SessionUpdate, ToolKind } from '@agentclientprotocol/sdk';
2
+ import type { AmalgmEvent } from '../events/schema.js';
3
+ export declare const AMALGM_ACP_META_KEY = "amalgm";
4
+ export interface AcpProjectionContext {
5
+ conversationId?: string;
6
+ turnId?: string;
7
+ sequence?: number;
8
+ messageId?: string;
9
+ }
10
+ export interface AmalgmAcpMeta {
11
+ conversationId?: string;
12
+ turnId?: string;
13
+ sequence?: number;
14
+ native: {
15
+ type: string;
16
+ timestampMs: number;
17
+ raw?: unknown;
18
+ };
19
+ }
20
+ /** Map the richer native tool vocabulary into ACP's stable categories. */
21
+ export declare function toAcpToolKind(kind: unknown): ToolKind;
22
+ /**
23
+ * Project the provider-neutral native event grammar onto an official ACP session
24
+ * update. Events without an ACP session-update meaning return `null` instead
25
+ * of being disguised as a synthetic update.
26
+ */
27
+ export declare function toAcpSessionUpdate(event: AmalgmEvent, context?: AcpProjectionContext): SessionUpdate | null;
28
+ /** Wrap a projected update in ACP's official session notification shape. */
29
+ export declare function toAcpSessionNotification(sessionId: string, event: AmalgmEvent, context?: AcpProjectionContext): SessionNotification | null;
30
+ //# sourceMappingURL=projection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projection.d.ts","sourceRoot":"","sources":["../../src/acp/projection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,aAAa,EAEb,QAAQ,EACT,MAAM,0BAA0B,CAAC;AAElC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,MAAM,WAAW,oBAAoB;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,GAAG,CAAC,EAAE,OAAO,CAAC;KACf,CAAC;CACH;AAoBD,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CA6BrD;AAgBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,oBAAyB,GACjC,aAAa,GAAG,IAAI,CA4EtB;AAED,4EAA4E;AAC5E,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,WAAW,EAClB,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,GAAG,IAAI,CAI5B"}