relaymessenger 0.1.6-staging.9 → 0.1.6

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 (142) hide show
  1. package/README.md +52 -23
  2. package/dist/acp-bridge.d.ts +129 -0
  3. package/dist/acp-bridge.d.ts.map +1 -0
  4. package/dist/acp-bridge.js +324 -0
  5. package/dist/acp-bridge.js.map +1 -0
  6. package/dist/acp-threads.d.ts +33 -0
  7. package/dist/acp-threads.d.ts.map +1 -0
  8. package/dist/acp-threads.js +64 -0
  9. package/dist/acp-threads.js.map +1 -0
  10. package/dist/agent-create.d.ts.map +1 -1
  11. package/dist/agent-create.js +13 -6
  12. package/dist/agent-create.js.map +1 -1
  13. package/dist/agent-session.js +2 -2
  14. package/dist/agent-session.js.map +1 -1
  15. package/dist/agents.d.ts +12 -3
  16. package/dist/agents.d.ts.map +1 -1
  17. package/dist/agents.js +45 -33
  18. package/dist/agents.js.map +1 -1
  19. package/dist/clack-theme.d.ts +38 -0
  20. package/dist/clack-theme.d.ts.map +1 -0
  21. package/dist/clack-theme.js +148 -0
  22. package/dist/clack-theme.js.map +1 -0
  23. package/dist/cli.js +5 -0
  24. package/dist/cli.js.map +1 -1
  25. package/dist/codex-bridge.d.ts +140 -0
  26. package/dist/codex-bridge.d.ts.map +1 -0
  27. package/dist/codex-bridge.js +454 -0
  28. package/dist/codex-bridge.js.map +1 -0
  29. package/dist/codex-threads.d.ts +31 -0
  30. package/dist/codex-threads.d.ts.map +1 -0
  31. package/dist/codex-threads.js +62 -0
  32. package/dist/codex-threads.js.map +1 -0
  33. package/dist/coding-agents/claude-code.d.ts +1 -1
  34. package/dist/coding-agents/claude-code.d.ts.map +1 -1
  35. package/dist/coding-agents/claude-code.js +6 -0
  36. package/dist/coding-agents/claude-code.js.map +1 -1
  37. package/dist/coding-agents/cline.d.ts.map +1 -1
  38. package/dist/coding-agents/cline.js +11 -4
  39. package/dist/coding-agents/cline.js.map +1 -1
  40. package/dist/coding-agents/codex-project-config.d.ts +20 -0
  41. package/dist/coding-agents/codex-project-config.d.ts.map +1 -0
  42. package/dist/coding-agents/codex-project-config.js +33 -0
  43. package/dist/coding-agents/codex-project-config.js.map +1 -0
  44. package/dist/coding-agents/codex.d.ts.map +1 -1
  45. package/dist/coding-agents/codex.js +11 -2
  46. package/dist/coding-agents/codex.js.map +1 -1
  47. package/dist/coding-agents/cursor.d.ts.map +1 -1
  48. package/dist/coding-agents/cursor.js +12 -3
  49. package/dist/coding-agents/cursor.js.map +1 -1
  50. package/dist/coding-agents/gemini-cli.d.ts.map +1 -1
  51. package/dist/coding-agents/gemini-cli.js +12 -2
  52. package/dist/coding-agents/gemini-cli.js.map +1 -1
  53. package/dist/coding-agents/hermes.d.ts.map +1 -1
  54. package/dist/coding-agents/hermes.js +6 -0
  55. package/dist/coding-agents/hermes.js.map +1 -1
  56. package/dist/coding-agents/openclaw.d.ts.map +1 -1
  57. package/dist/coding-agents/openclaw.js +9 -0
  58. package/dist/coding-agents/openclaw.js.map +1 -1
  59. package/dist/coding-agents/opencode.d.ts.map +1 -1
  60. package/dist/coding-agents/opencode.js +15 -4
  61. package/dist/coding-agents/opencode.js.map +1 -1
  62. package/dist/coding-agents/shared.d.ts +47 -1
  63. package/dist/coding-agents/shared.d.ts.map +1 -1
  64. package/dist/coding-agents/vscode.d.ts.map +1 -1
  65. package/dist/coding-agents/vscode.js +1 -0
  66. package/dist/coding-agents/vscode.js.map +1 -1
  67. package/dist/coding-agents.d.ts +24 -21
  68. package/dist/coding-agents.d.ts.map +1 -1
  69. package/dist/coding-agents.js +1 -3
  70. package/dist/coding-agents.js.map +1 -1
  71. package/dist/config.d.ts +42 -1
  72. package/dist/config.d.ts.map +1 -1
  73. package/dist/config.js +107 -5
  74. package/dist/config.js.map +1 -1
  75. package/dist/connect.d.ts +94 -16
  76. package/dist/connect.d.ts.map +1 -1
  77. package/dist/connect.js +474 -180
  78. package/dist/connect.js.map +1 -1
  79. package/dist/console-auth.d.ts +47 -0
  80. package/dist/console-auth.d.ts.map +1 -0
  81. package/dist/console-auth.js +394 -0
  82. package/dist/console-auth.js.map +1 -0
  83. package/dist/error-codes.d.ts +1 -1
  84. package/dist/error-codes.d.ts.map +1 -1
  85. package/dist/error-codes.js +3 -1
  86. package/dist/error-codes.js.map +1 -1
  87. package/dist/errors.js +1 -1
  88. package/dist/errors.js.map +1 -1
  89. package/dist/event-listen.d.ts +12 -2
  90. package/dist/event-listen.d.ts.map +1 -1
  91. package/dist/event-listen.js +13 -3
  92. package/dist/event-listen.js.map +1 -1
  93. package/dist/folder-link.d.ts +37 -0
  94. package/dist/folder-link.d.ts.map +1 -0
  95. package/dist/folder-link.js +82 -0
  96. package/dist/folder-link.js.map +1 -0
  97. package/dist/help-groups.d.ts +2 -3
  98. package/dist/help-groups.d.ts.map +1 -1
  99. package/dist/help-groups.js +39 -14
  100. package/dist/help-groups.js.map +1 -1
  101. package/dist/interactive.d.ts +18 -11
  102. package/dist/interactive.d.ts.map +1 -1
  103. package/dist/interactive.js +15 -16
  104. package/dist/interactive.js.map +1 -1
  105. package/dist/program.d.ts +10 -3
  106. package/dist/program.d.ts.map +1 -1
  107. package/dist/program.js +367 -75
  108. package/dist/program.js.map +1 -1
  109. package/dist/qr-terminal.d.ts +52 -4
  110. package/dist/qr-terminal.d.ts.map +1 -1
  111. package/dist/qr-terminal.js +85 -36
  112. package/dist/qr-terminal.js.map +1 -1
  113. package/dist/relay-brand.d.ts +18 -0
  114. package/dist/relay-brand.d.ts.map +1 -0
  115. package/dist/relay-brand.js +38 -0
  116. package/dist/relay-brand.js.map +1 -0
  117. package/dist/runtime-sniff.d.ts +2 -0
  118. package/dist/runtime-sniff.d.ts.map +1 -1
  119. package/dist/runtime-sniff.js +1 -1
  120. package/dist/runtime-sniff.js.map +1 -1
  121. package/dist/skill-offer.d.ts.map +1 -1
  122. package/dist/skill-offer.js +4 -5
  123. package/dist/skill-offer.js.map +1 -1
  124. package/dist/spawn-command.d.ts +31 -0
  125. package/dist/spawn-command.d.ts.map +1 -0
  126. package/dist/spawn-command.js +17 -0
  127. package/dist/spawn-command.js.map +1 -0
  128. package/dist/terminal-session.d.ts +2 -1
  129. package/dist/terminal-session.d.ts.map +1 -1
  130. package/dist/terminal-session.js +50 -24
  131. package/dist/terminal-session.js.map +1 -1
  132. package/dist/terminal-watch.js +1 -1
  133. package/dist/terminal-watch.js.map +1 -1
  134. package/dist/ui-colour.d.ts +38 -0
  135. package/dist/ui-colour.d.ts.map +1 -0
  136. package/dist/ui-colour.js +39 -0
  137. package/dist/ui-colour.js.map +1 -0
  138. package/package.json +5 -2
  139. package/dist/coding-agents/claude-desktop.d.ts +0 -4
  140. package/dist/coding-agents/claude-desktop.d.ts.map +0 -1
  141. package/dist/coding-agents/claude-desktop.js +0 -25
  142. package/dist/coding-agents/claude-desktop.js.map +0 -1
package/README.md CHANGED
@@ -53,9 +53,9 @@ With no terminal, a command that needs an answer prints the flags that would
53
53
  have answered it and exits 2 rather than a usage block. With `--json`, every
54
54
  error is `{ "error": …, "next_step": … }`.
55
55
 
56
- `relay --help` puts the commands in three groups: Get started (`connect`), Every
57
- day (`watch`, `doctor`, `agents`), and Everything else behind one line. Every
58
- command has a description; run `relay help <command>` for any of them.
56
+ `relay` and `relaymessenger` open with the Relay mark, version, usage, topics,
57
+ and concise commands, like the Linq CLI. Run `relay help <command>` for the
58
+ full options for any command.
59
59
 
60
60
  Before interactive creation or sign-in setup, the CLI may offer the Relay skill
61
61
  once if it is absent from the standard install locations. Declining skips skill
@@ -108,6 +108,16 @@ typed carries on afterwards.
108
108
  `docs` prints `https://docs.relayapp.im/llms.txt`: its contents when there is no
109
109
  terminal, and its address when there is one or when Relay cannot reach it.
110
110
 
111
+ ## Authentication
112
+
113
+ `relay login` signs this computer into Relay Console using a browser/device
114
+ flow. It stores the Console session locally; Relay Console names your first
115
+ organization after you. Use `organization update --name` to rename it and
116
+ optional `--website` to set its website.
117
+
118
+ For legacy integrations, `relay auth login --with-token` keeps the explicit
119
+ Agent Token import path. The token is read from stdin and is never printed.
120
+
111
121
  ## Agent Token authentication
112
122
 
113
123
  Use `agents create` for a new agent, or import an existing Agent Token. Tokens
@@ -190,7 +200,7 @@ Messages to users remain supported subject to Contacts eligibility and blocking.
190
200
  There are no add-request, phone address-book, mutual-contact, human discovery, or
191
201
  human invite-link commands.
192
202
 
193
- ## Developer-managed agents
203
+ ## Organization creation and saved agents
194
204
 
195
205
  ```sh
196
206
  relay agents create
@@ -203,7 +213,7 @@ Creation stores the one-time Agent Token in a new named profile and prints only
203
213
  public metadata, a share link, and a terminal QR. JSON output includes
204
214
  `token: "stored"`, never the secret. Use an explicit `--profile <new-name>` to
205
215
  choose a new profile name; existing profiles and the current profile selection
206
- are preserved. `--token-name` labels the token, not a machine identity.
216
+ are preserved. New agents are provisioned in the signed-in Console organization.
207
217
 
208
218
  `agents list` shows the agents saved on this computer, not every agent on your
209
219
  account. Each row is `profile`, `handle`, `display_name`, `image_url`, `api_url`
@@ -231,13 +241,14 @@ agent, and it never overwrites a token that is already there.
231
241
 
232
242
  ```sh
233
243
  relay agents create \
234
- --handle my_helper.dev --name "My Helper" \
244
+ --handle my_helper --name "My Helper" \
235
245
  --image-url https://images.example.com/helper.png
236
246
  ```
237
247
 
238
- Omit any option to keep the server's assigned handle/readable bird name/default
239
- image. Custom handles are full lowercase `.dev` handles; a collision is an error,
240
- never a request for a random replacement. Interactive creation asks `Handle (optional)`, `Name (optional)`, and `Image
248
+ A handle is one word, such as `my_helper`: 3 to 32 lowercase letters, numbers
249
+ or underscores. Dotted handles such as `my_helper.dev` are rejected, not
250
+ reinterpreted. Names are at most 30 characters. A collision is
251
+ an error, never a request for a different handle. Interactive creation asks `Handle (optional)`, `Name (optional)`, and `Image
241
252
  (optional)` with a single help line; blank answers preserve defaults. Selecting
242
253
  Create already expresses intent, so no second create confirmation is shown.
243
254
  Recipe files remain an advanced `--image-recipe` flag, not another setup question.
@@ -265,24 +276,42 @@ server's response supplies the permanent public image URL.
265
276
 
266
277
  ## Local event forwarding
267
278
 
268
- `relay events listen` is a development convenience backed only by the SDK's
269
- source-backed Agent WebSocket. It refuses Relay's production API, requires an
270
- explicit profile, and asks you to confirm that the profile belongs to a test agent.
271
- Reading events here can make Relay stop resending them elsewhere, so never point it
272
- at an agent something else is reading:
279
+ There are two ways to run an agent backend, the way Slack has Socket Mode for
280
+ local work and request URLs once deployed, and Stripe has `stripe listen
281
+ --forward-to` for local work and a registered endpoint once deployed. Deployed,
282
+ Relay POSTs events to your webhook. While you develop, `relay listen` reads the
283
+ same events over the socket and POSTs each one to a route on this computer,
284
+ signed exactly like a deployed webhook, so the same handler runs unchanged:
273
285
 
274
286
  ```sh
275
- relay --profile staging events listen --acknowledge-events
276
- relay --profile staging events listen --acknowledge-events \
277
- --forward-to http://127.0.0.1:3000/relay-events
287
+ relay listen --forward-to http://localhost:3000/relay-events
288
+ ```
289
+
290
+ It prints the address it forwards to and the local signing secret, then one
291
+ line per event, the way `relay watch` shows them:
292
+
293
+ ```
294
+ Forwarding events to http://localhost:3000/relay-events
295
+ Local signing secret whsec_… (set RELAY_WEBHOOK_SECRET to it while you develop)
296
+ Events read here count as delivered; a deployed webhook for this agent does not get them.
278
297
  ```
279
298
 
280
- `--forward-to` must be an address on your own computer, such as
281
- `http://127.0.0.1:3000`. Each copy is the original Relay event, but it is **not
282
- signed**, and it carries `x-relay-dev-forwarded: 1`. Use a real webhook to test
283
- signature checking. If your own address answers with an error, this command stops
284
- rather than let the event be lost, so Relay can send it again. Your receiver must
285
- ignore an `event_id` it has already seen.
299
+ Set `RELAY_WEBHOOK_SECRET` to that secret where your handler runs. Each POST
300
+ carries `webhook-id`, `webhook-timestamp` and `webhook-signature` (Standard
301
+ Webhooks, HMAC-SHA256 over the exact bytes sent), plus `x-relay-event-id` and
302
+ `x-relay-event-type`, so `verifyWebhookSignature` and the Chat SDK adapter accept
303
+ it as they accept Relay's own deliveries. The secret is made once per profile and
304
+ kept in the CLI's private config, the way Stripe keeps one per account, so a
305
+ restart does not make you change it. `--forward-to` must be an address on your
306
+ own computer, such as `http://127.0.0.1:3000`. If your route answers with an
307
+ error, this command stops rather than let the event be lost, so Relay can send
308
+ it again. Your receiver must ignore an `event_id` it has already seen.
309
+
310
+ Reading events here delivers them: a deployed webhook for this agent does not get
311
+ them, so run it against the agent you are developing, never one something else
312
+ is reading. The older `relay --profile <name> events listen --acknowledge-events
313
+ [--forward-to <url>]` still works for scripts, prints the raw envelope, and signs
314
+ its forwards the same way.
286
315
 
287
316
  If the agent has been away longer than Relay keeps its events, Relay wants to send
288
317
  everything it missed. This command cannot go back over old events and says so
@@ -0,0 +1,129 @@
1
+ import type Relay from "@relaymessenger/sdk";
2
+ import { ClientSideConnection, type RequestPermissionRequest, type RequestPermissionResponse, type McpServer } from "@agentclientprotocol/sdk";
3
+ import type { AcpSessionStore } from "./acp-threads.js";
4
+ /**
5
+ * What `relay connect cursor|gemini-cli|cline|opencode` leaves running so the
6
+ * agent answers Relay messages by itself.
7
+ *
8
+ * These agents reach Relay through MCP, and MCP cannot start a turn, so a
9
+ * message waits until somebody asks the agent to read it. This is the process
10
+ * that asks. It speaks the Agent Client Protocol (ACP) — the same protocol Zed
11
+ * uses to drive Cursor, Gemini CLI and OpenCode in the background — over the
12
+ * official `@agentclientprotocol/sdk`. It runs the agent's own ACP command
13
+ * (`cursor-agent acp`, `gemini --experimental-acp`, `opencode acp`), gives every
14
+ * chat its own ACP session, and sends the final answer back to the same chat.
15
+ *
16
+ * Relay's own tools travel through the session, natively: the Relay MCP server
17
+ * connect would otherwise write into the agent's `mcp.json` is handed to
18
+ * `session/new` instead, so the agent keeps Relay's send, read and react tools
19
+ * while this process drives its turns. This mirrors `codex-bridge.ts`, which
20
+ * does the same over Codex's `app-server`; the two never share a session file.
21
+ */
22
+ /** Relay takes 1 to 255 characters for an idempotency key (contracts/relay-v1-openapi.yaml). */
23
+ export declare const replyKey: (eventId: string) => string;
24
+ /** What the agent is told this client is (`clientInfo`, InitializeRequest). */
25
+ export declare const CLIENT_NAME = "relaymessenger";
26
+ /**
27
+ * One message, as the prompt the agent is given. The agent keeps its Relay
28
+ * tools during the turn, so the prompt says who answers the person: this
29
+ * process sends the final message, and the agent must not send a second one.
30
+ * Copied word for word from `codexPrompt` (codex-bridge.ts) so both bridges say
31
+ * the same thing.
32
+ */
33
+ export declare const acpPrompt: (sender: string, text: string) => string;
34
+ /** What to run for the agent: the file, and the words that put it in ACP mode. */
35
+ export interface AcpCommand {
36
+ /** The executable, e.g. `cursor-agent`, or the absolute path connect found. */
37
+ command: string;
38
+ /** The ACP sub-command and flags, e.g. `["acp"]` or `["--experimental-acp"]`. */
39
+ args: readonly string[];
40
+ }
41
+ /**
42
+ * The file to run for the agent. connect hands over the one its own sniff found
43
+ * on PATH, and the bare name when it found none (connect.ts). Windows has no
44
+ * file called `cursor-agent`: npm installs a `.cmd` shim, so a bare name is
45
+ * looked up again here and, failing that, left with the extension `cmd.exe` can
46
+ * find. The ACP words are kept as they are (runtime-sniff.ts, spawn-command.ts).
47
+ */
48
+ export declare const acpCommand: (found: string, args: readonly string[], env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform) => Promise<AcpCommand>;
49
+ /** The Relay MCP server as ACP takes it (`McpServerStdio`, schema/types.gen). */
50
+ export declare const relayMcpServer: (spec: {
51
+ command: string;
52
+ args: readonly string[];
53
+ env: Record<string, string>;
54
+ }) => McpServer;
55
+ /**
56
+ * Nobody is at the keyboard, so a tool the agent asks to run is allowed the
57
+ * same way Codex's `approvalPolicy: "never"` allows it: the agent proceeds. The
58
+ * first `allow` option is chosen; when none is offered the request is cancelled
59
+ * (`RequestPermissionOutcome`, schema/types.gen), which the reference client
60
+ * does too (openclaw/src/acp/client-helpers.ts, `resolvePermissionRequest`).
61
+ */
62
+ export declare const autoPermission: (params: RequestPermissionRequest) => RequestPermissionResponse;
63
+ /** One running ACP agent, and the calls this bridge makes to it. */
64
+ export interface AcpAgent {
65
+ client: Pick<ClientSideConnection, "initialize" | "newSession" | "loadSession" | "prompt" | "cancel">;
66
+ /**
67
+ * Collect the answer streamed for one session's running turn. Every
68
+ * `agent_message_chunk` of text is handed to the sink until the returned
69
+ * function is called. Turns are serial within a session, so at most one sink
70
+ * is registered per session at a time.
71
+ */
72
+ collect(sessionId: string, sink: (text: string) => void): () => void;
73
+ /** True once the agent advertised `session/load`; set after `initialize`. */
74
+ canLoad: boolean;
75
+ /** Resolves, with the line to show the person, when the process is gone. */
76
+ stopped: Promise<string>;
77
+ /** True once the process is gone, so the next message starts a new one. */
78
+ gone(): boolean;
79
+ }
80
+ /** Starts the agent's ACP command and drives it over the official SDK. */
81
+ export declare const startAcpAgent: (acp: AcpCommand, cwd: string, signal: AbortSignal) => AcpAgent;
82
+ /** The turn the agent is running for one chat right now. */
83
+ export interface LiveTurn {
84
+ sessionId: string;
85
+ /** A newer message for this chat replaced it, so its answer is not sent. */
86
+ dropped: boolean;
87
+ }
88
+ /** How a turn ended (`StopReason`, schema/types.gen), and what it said. */
89
+ export interface TurnOutcome {
90
+ /** `end_turn`, `cancelled`, `refusal`, `max_tokens` or `max_turn_requests`. */
91
+ stopReason: string;
92
+ answer: string;
93
+ }
94
+ /**
95
+ * Runs one turn in one session, and gives back what the agent answered.
96
+ *
97
+ * The answer is every `agent_message_chunk` of text the agent streamed before
98
+ * `session/prompt` returned its stop reason, joined in order.
99
+ */
100
+ export declare const runTurn: (agent: AcpAgent, input: {
101
+ sessionId: string;
102
+ prompt: string;
103
+ onStarted(turn: LiveTurn): void;
104
+ }) => Promise<TurnOutcome>;
105
+ export interface AcpBridgeInput {
106
+ client: Pick<Relay, "chats" | "websocket">;
107
+ /** The agent's ACP command, and the folder to run it in. */
108
+ acp: AcpCommand;
109
+ cwd: string;
110
+ /** The Relay MCP server handed to every session, so Relay's tools travel with it. */
111
+ mcpServers: readonly McpServer[];
112
+ /** The label shown to the person, e.g. "Cursor". */
113
+ label: string;
114
+ /** Which ACP session belongs to which chat, across restarts. */
115
+ sessions: AcpSessionStore;
116
+ signal: AbortSignal;
117
+ /** One line to the terminal the person is watching. */
118
+ say(line: string): void;
119
+ }
120
+ /**
121
+ * Answers every message that arrives until the signal stops it.
122
+ *
123
+ * Turns run one at a time inside a chat and at the same time across chats,
124
+ * which is what one agent with one session per chat allows. A message for a
125
+ * chat whose turn is still running cancels that turn (`session/cancel`) and
126
+ * takes its place; the cancelled turn sends nothing.
127
+ */
128
+ export declare const runAcpBridge: (input: AcpBridgeInput) => Promise<void>;
129
+ //# sourceMappingURL=acp-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp-bridge.d.ts","sourceRoot":"","sources":["../src/acp-bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,324 @@
1
+ import { ClientSideConnection, PROTOCOL_VERSION, ndJsonStream, } from "@agentclientprotocol/sdk";
2
+ import { Readable, Writable } from "node:stream";
3
+ import { isAbsolute } from "node:path";
4
+ import { bridgeTurn, MAX_RELAY_TEXT } from "./codex-bridge.js";
5
+ import { findExecutable } from "./runtime-sniff.js";
6
+ import { packageVersion } from "./config.js";
7
+ import { spawnCommand } from "./spawn-command.js";
8
+ /**
9
+ * What `relay connect cursor|gemini-cli|cline|opencode` leaves running so the
10
+ * agent answers Relay messages by itself.
11
+ *
12
+ * These agents reach Relay through MCP, and MCP cannot start a turn, so a
13
+ * message waits until somebody asks the agent to read it. This is the process
14
+ * that asks. It speaks the Agent Client Protocol (ACP) — the same protocol Zed
15
+ * uses to drive Cursor, Gemini CLI and OpenCode in the background — over the
16
+ * official `@agentclientprotocol/sdk`. It runs the agent's own ACP command
17
+ * (`cursor-agent acp`, `gemini --experimental-acp`, `opencode acp`), gives every
18
+ * chat its own ACP session, and sends the final answer back to the same chat.
19
+ *
20
+ * Relay's own tools travel through the session, natively: the Relay MCP server
21
+ * connect would otherwise write into the agent's `mcp.json` is handed to
22
+ * `session/new` instead, so the agent keeps Relay's send, read and react tools
23
+ * while this process drives its turns. This mirrors `codex-bridge.ts`, which
24
+ * does the same over Codex's `app-server`; the two never share a session file.
25
+ */
26
+ /** Relay takes 1 to 255 characters for an idempotency key (contracts/relay-v1-openapi.yaml). */
27
+ export const replyKey = (eventId) => `acp-bridge-${eventId}`;
28
+ /** What the agent is told this client is (`clientInfo`, InitializeRequest). */
29
+ export const CLIENT_NAME = "relaymessenger";
30
+ /**
31
+ * One message, as the prompt the agent is given. The agent keeps its Relay
32
+ * tools during the turn, so the prompt says who answers the person: this
33
+ * process sends the final message, and the agent must not send a second one.
34
+ * Copied word for word from `codexPrompt` (codex-bridge.ts) so both bridges say
35
+ * the same thing.
36
+ */
37
+ export const acpPrompt = (sender, text) => [
38
+ `@${sender} sent you this message on Relay:`,
39
+ "",
40
+ text.slice(0, MAX_RELAY_TEXT),
41
+ "",
42
+ "Write your answer as your final message. Relay sends that answer to the chat for you, so do not send it yourself.",
43
+ ].join("\n");
44
+ /**
45
+ * The file to run for the agent. connect hands over the one its own sniff found
46
+ * on PATH, and the bare name when it found none (connect.ts). Windows has no
47
+ * file called `cursor-agent`: npm installs a `.cmd` shim, so a bare name is
48
+ * looked up again here and, failing that, left with the extension `cmd.exe` can
49
+ * find. The ACP words are kept as they are (runtime-sniff.ts, spawn-command.ts).
50
+ */
51
+ export const acpCommand = async (found, args, env = process.env, platform = process.platform) => {
52
+ if (isAbsolute(found))
53
+ return { command: found, args };
54
+ const onPath = await findExecutable(found, env, platform);
55
+ return { command: onPath ?? (platform === "win32" ? `${found}.cmd` : found), args };
56
+ };
57
+ /** The Relay MCP server as ACP takes it (`McpServerStdio`, schema/types.gen). */
58
+ export const relayMcpServer = (spec) => ({
59
+ name: "relay",
60
+ command: spec.command,
61
+ args: [...spec.args],
62
+ env: Object.entries(spec.env).map(([name, value]) => ({ name, value })),
63
+ });
64
+ /**
65
+ * Nobody is at the keyboard, so a tool the agent asks to run is allowed the
66
+ * same way Codex's `approvalPolicy: "never"` allows it: the agent proceeds. The
67
+ * first `allow` option is chosen; when none is offered the request is cancelled
68
+ * (`RequestPermissionOutcome`, schema/types.gen), which the reference client
69
+ * does too (openclaw/src/acp/client-helpers.ts, `resolvePermissionRequest`).
70
+ */
71
+ export const autoPermission = (params) => {
72
+ const allow = params.options.find((option) => option.kind === "allow_once")
73
+ ?? params.options.find((option) => option.kind === "allow_always");
74
+ return allow
75
+ ? { outcome: { outcome: "selected", optionId: allow.optionId } }
76
+ : { outcome: { outcome: "cancelled" } };
77
+ };
78
+ /** Starts the agent's ACP command and drives it over the official SDK. */
79
+ export const startAcpAgent = (acp, cwd, signal) => {
80
+ // Started the way every other command this CLI runs is started, so the `.cmd`
81
+ // shim npm installs on Windows runs too (spawn-command.ts). Nothing a person
82
+ // wrote travels on this command line: messages go down the ACP stream.
83
+ const child = spawnCommand(acp.command, acp.args, {
84
+ cwd, stdio: ["pipe", "pipe", "pipe"], signal,
85
+ });
86
+ const sinks = new Map();
87
+ let dead = false;
88
+ let announce;
89
+ const stopped = new Promise((resolve) => { announce = resolve; });
90
+ const die = (line) => {
91
+ if (dead)
92
+ return;
93
+ dead = true;
94
+ announce(line);
95
+ };
96
+ // The agent writes its own diagnostics to stderr; the person watching this
97
+ // terminal is waiting for an answer, not for a log.
98
+ child.stderr?.resume();
99
+ child.stdin?.on("error", () => { });
100
+ child.once("error", () => die("The agent could not be started. Trying again on the next message."));
101
+ child.once("close", () => die("The agent stopped. Starting it again on the next message."));
102
+ if (!child.stdin || !child.stdout) {
103
+ die("The agent gave this bridge no way to talk to it.");
104
+ }
105
+ const input = Writable.toWeb(child.stdin);
106
+ const output = Readable.toWeb(child.stdout);
107
+ const stream = ndJsonStream(input, output);
108
+ const handlers = {
109
+ sessionUpdate: async (params) => {
110
+ const sink = sinks.get(params.sessionId);
111
+ if (!sink)
112
+ return;
113
+ const update = params.update;
114
+ if (update.sessionUpdate === "agent_message_chunk" && update.content.type === "text") {
115
+ sink(update.content.text);
116
+ }
117
+ },
118
+ requestPermission: async (params) => autoPermission(params),
119
+ };
120
+ const client = new ClientSideConnection(() => handlers, stream);
121
+ return {
122
+ client,
123
+ collect: (sessionId, sink) => {
124
+ sinks.set(sessionId, sink);
125
+ return () => { if (sinks.get(sessionId) === sink)
126
+ sinks.delete(sessionId); };
127
+ },
128
+ canLoad: false,
129
+ stopped,
130
+ gone: () => dead,
131
+ };
132
+ };
133
+ /**
134
+ * Runs one turn in one session, and gives back what the agent answered.
135
+ *
136
+ * The answer is every `agent_message_chunk` of text the agent streamed before
137
+ * `session/prompt` returned its stop reason, joined in order.
138
+ */
139
+ export const runTurn = async (agent, input) => {
140
+ const chunks = [];
141
+ const stop = agent.collect(input.sessionId, (text) => chunks.push(text));
142
+ try {
143
+ input.onStarted({ sessionId: input.sessionId, dropped: false });
144
+ const response = await Promise.race([
145
+ agent.client.prompt({ sessionId: input.sessionId, prompt: [{ type: "text", text: input.prompt }] }),
146
+ agent.stopped.then((line) => { throw new Error(line); }),
147
+ ]);
148
+ return { stopReason: response.stopReason, answer: chunks.join("").trim() };
149
+ }
150
+ finally {
151
+ stop();
152
+ }
153
+ };
154
+ /** One line of what arrived, short enough to read at a glance. */
155
+ const arrival = (turn) => `@${turn.sender} ${turn.text.replace(/\s+/gu, " ").slice(0, 160)}`;
156
+ /**
157
+ * Answers every message that arrives until the signal stops it.
158
+ *
159
+ * Turns run one at a time inside a chat and at the same time across chats,
160
+ * which is what one agent with one session per chat allows. A message for a
161
+ * chat whose turn is still running cancels that turn (`session/cancel`) and
162
+ * takes its place; the cancelled turn sends nothing.
163
+ */
164
+ export const runAcpBridge = async (input) => {
165
+ const answered = new Set();
166
+ const lanes = new Map();
167
+ let session;
168
+ let sessionGone = false;
169
+ /** The ACP session each chat is holding open in the agent running now. */
170
+ let opened = new Map();
171
+ const acpAgent = () => {
172
+ if (session && !sessionGone)
173
+ return session;
174
+ opened = new Map();
175
+ session = (async () => {
176
+ const started = startAcpAgent(input.acp, input.cwd, input.signal);
177
+ // A stop the person asked for, with Control-C, is not news.
178
+ void started.stopped.then((line) => {
179
+ sessionGone = true;
180
+ if (!input.signal.aborted)
181
+ input.say(line);
182
+ });
183
+ // `initialize` negotiates the protocol version and reads back whether the
184
+ // agent can take an old session back with `session/load` (ACP spec,
185
+ // Initialization). The reference client sends the same capabilities
186
+ // (openclaw/src/acp/client.ts).
187
+ const info = await started.client.initialize({
188
+ protocolVersion: PROTOCOL_VERSION,
189
+ clientCapabilities: { fs: { readTextFile: true, writeTextFile: true }, terminal: true },
190
+ clientInfo: { name: CLIENT_NAME, title: "Relay", version: packageVersion() },
191
+ });
192
+ started.canLoad = info.agentCapabilities?.loadSession === true;
193
+ return started;
194
+ })().catch((error) => {
195
+ sessionGone = true;
196
+ throw error instanceof Error ? error : new Error(String(error));
197
+ });
198
+ sessionGone = false;
199
+ return session;
200
+ };
201
+ /**
202
+ * The session for this chat: the saved one if the agent still has it, a new
203
+ * one if it does not. `session/load` is only tried when the agent said it can
204
+ * (`agentCapabilities.loadSession`); a load the agent refuses starts a new
205
+ * session, the same fallback the Codex bridge makes for a lost thread.
206
+ */
207
+ const openSession = async (agent, chatId) => {
208
+ const settings = { cwd: input.cwd, mcpServers: [...input.mcpServers] };
209
+ // This agent already has the session open; it is taken back by id once per
210
+ // run of the process, not once per message.
211
+ const open = opened.get(chatId);
212
+ if (open !== undefined)
213
+ return open;
214
+ const saved = input.sessions.get(chatId);
215
+ if (saved !== undefined && agent.canLoad) {
216
+ try {
217
+ await agent.client.loadSession({ ...settings, sessionId: saved });
218
+ opened.set(chatId, saved);
219
+ return saved;
220
+ }
221
+ catch { /* Named below, once, for the one case a person can act on. */ }
222
+ input.say(`${input.label} no longer has this chat's session. It starts a new one.`);
223
+ }
224
+ const created = await agent.client.newSession(settings);
225
+ const id = created.sessionId;
226
+ if (!id)
227
+ throw new Error("The agent opened a session with no id.");
228
+ opened.set(chatId, id);
229
+ await input.sessions.set(chatId, id);
230
+ return id;
231
+ };
232
+ const answerOne = async (turn, lane, started) => {
233
+ let typing = false;
234
+ try {
235
+ await input.client.chats.startTyping(turn.chatId);
236
+ typing = true;
237
+ }
238
+ catch { /* The answer matters more than the typing indicator. */ }
239
+ const stopTyping = async () => {
240
+ if (!typing)
241
+ return;
242
+ typing = false;
243
+ try {
244
+ await input.client.chats.stopTyping(turn.chatId);
245
+ }
246
+ catch { /* As above. */ }
247
+ };
248
+ let mine;
249
+ let outcome;
250
+ try {
251
+ const agent = await acpAgent();
252
+ const sessionId = await openSession(agent, turn.chatId);
253
+ outcome = await runTurn(agent, {
254
+ sessionId, prompt: acpPrompt(turn.sender, turn.text),
255
+ onStarted: (live) => { mine = live; lane.live = live; started(); },
256
+ });
257
+ }
258
+ catch { /* Named below, with everything else the agent can fail at. */ }
259
+ if (lane.live === mine)
260
+ lane.live = undefined;
261
+ if (mine?.dropped === true || outcome?.stopReason === "cancelled") {
262
+ await stopTyping();
263
+ input.say(`A newer message came in, so the answer to @${turn.sender} was dropped.`);
264
+ return;
265
+ }
266
+ const answer = (outcome?.answer ?? "").trim();
267
+ if (!answer) {
268
+ await stopTyping();
269
+ input.say(`${input.label} gave no answer to @${turn.sender}, so nothing was sent.`);
270
+ return;
271
+ }
272
+ try {
273
+ await input.client.chats.messages.send(turn.chatId, {
274
+ message: {
275
+ parts: [{ type: "text", value: answer.slice(0, MAX_RELAY_TEXT) }],
276
+ // The message that arrived is the key, so a retry after a dropped
277
+ // connection cannot answer the same person twice.
278
+ idempotency_key: replyKey(turn.eventId),
279
+ },
280
+ });
281
+ input.say(`Sent the answer to @${turn.sender}.`);
282
+ }
283
+ catch {
284
+ input.say(`The answer to @${turn.sender} did not reach Relay.`);
285
+ }
286
+ finally {
287
+ await stopTyping();
288
+ }
289
+ };
290
+ await input.client.websocket.run({
291
+ signal: input.signal,
292
+ onEvent: async (event) => {
293
+ const turn = bridgeTurn(event);
294
+ if (!turn || answered.has(turn.eventId))
295
+ return;
296
+ answered.add(turn.eventId);
297
+ input.say(arrival(turn));
298
+ const lane = lanes.get(turn.chatId) ?? { chain: Promise.resolve() };
299
+ lanes.set(turn.chatId, lane);
300
+ const live = lane.live;
301
+ if (live !== undefined) {
302
+ live.dropped = true;
303
+ try {
304
+ await (await acpAgent()).client.cancel({ sessionId: live.sessionId });
305
+ }
306
+ catch { /* The turn ended by itself, which is the same outcome. */ }
307
+ }
308
+ let ready;
309
+ const handed = new Promise((resolve) => { ready = resolve; });
310
+ // Nothing may be thrown here: a failure would close the connection, and
311
+ // the agent failing to answer one message is not a reason to stop.
312
+ lane.chain = lane.chain.then(() => answerOne(turn, lane, ready)).catch(() => undefined).finally(() => { ready(); });
313
+ // Relay is told the message is handled once the agent is working on it.
314
+ // The rest of the turn does not hold this connection, because every other
315
+ // chat's messages, and the next message in this one, arrive down it.
316
+ await handed;
317
+ },
318
+ onFullSync: async () => {
319
+ // This process keeps no copy of any chat, so there is nothing to rebuild.
320
+ input.say(`${input.label} was away longer than Relay keeps its messages. It answers the new ones from now on.`);
321
+ },
322
+ });
323
+ };
324
+ //# sourceMappingURL=acp-bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp-bridge.js","sourceRoot":"","sources":["../src/acp-bridge.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,GAMb,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAmB,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;;;;;;;;;;GAiBG;AAEH,gGAAgG;AAChG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,cAAc,OAAO,EAAE,CAAC;AAE7E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAC;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAc,EAAE,IAAY,EAAU,EAAE,CAAC;IACjE,IAAI,MAAM,kCAAkC;IAC5C,EAAE;IACF,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC;IAC7B,EAAE;IACF,mHAAmH;CACpH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAUb;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAC7B,KAAa,EACb,IAAuB,EACvB,MAAyB,OAAO,CAAC,GAAG,EACpC,WAA4B,OAAO,CAAC,QAAQ,EACvB,EAAE;IACvB,IAAI,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;AACtF,CAAC,CAAC;AAEF,iFAAiF;AACjF,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,IAA+E,EACpE,EAAE,CAAC,CAAC;IACf,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,IAAI,CAAC,OAAO;IACrB,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;CACxE,CAAC,CAAC;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAgC,EAA6B,EAAE;IAC5F,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC;WACtE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,cAAc,CAAC,CAAC;IACrE,OAAO,KAAK;QACV,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAAE;QAChE,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,CAAC;AAC5C,CAAC,CAAC;AAoBF,0EAA0E;AAC1E,MAAM,CAAC,MAAM,aAAa,GAAG,CAC3B,GAAe,EACf,GAAW,EACX,MAAmB,EACT,EAAE;IACZ,8EAA8E;IAC9E,6EAA6E;IAC7E,uEAAuE;IACvE,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE;QAChD,GAAG,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;KAC7C,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAC;IACxD,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,IAAI,QAAiC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE,GAAG,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,MAAM,GAAG,GAAG,CAAC,IAAY,EAAQ,EAAE;QACjC,IAAI,IAAI;YAAE,OAAO;QACjB,IAAI,GAAG,IAAI,CAAC;QACZ,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjB,CAAC,CAAC;IAEF,2EAA2E;IAC3E,oDAAoD;IACpD,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAA2D,CAAC,CAAC,CAAC;IAC5F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,mEAAmE,CAAC,CAAC,CAAC;IACpG,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,2DAA2D,CAAC,CAAC,CAAC;IAE5F,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClC,GAAG,CAAC,kDAAkD,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,KAAM,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,MAAO,CAA0C,CAAC;IACtF,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE3C,MAAM,QAAQ,GAAW;QACvB,aAAa,EAAE,KAAK,EAAE,MAA2B,EAAiB,EAAE;YAClE,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;YACzC,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC7B,IAAI,MAAM,CAAC,aAAa,KAAK,qBAAqB,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACrF,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QACD,iBAAiB,EAAE,KAAK,EAAE,MAAgC,EAAsC,EAAE,CAChG,cAAc,CAAC,MAAM,CAAC;KACzB,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,oBAAoB,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEhE,OAAO;QACL,MAAM;QACN,OAAO,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE;YAC3B,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YAC3B,OAAO,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI;gBAAE,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/E,CAAC;QACD,OAAO,EAAE,KAAK;QACd,OAAO;QACP,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI;KACjB,CAAC;AACJ,CAAC,CAAC;AAgBF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,EAC1B,KAAe,EACf,KAA6E,EACvD,EAAE;IACxB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,IAAI,CAAC;QACH,KAAK,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YAClC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;YACnG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAS,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;SAChE,CAAC,CAAC;QACH,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IAC7E,CAAC;YAAS,CAAC;QAAC,IAAI,EAAE,CAAC;IAAC,CAAC;AACvB,CAAC,CAAC;AAkBF,kEAAkE;AAClE,MAAM,OAAO,GAAG,CAAC,IAAgB,EAAU,EAAE,CAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AAStE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,KAAqB,EAAiB,EAAE;IACzE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC1C,IAAI,OAAsC,CAAC;IAC3C,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,0EAA0E;IAC1E,IAAI,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEvC,MAAM,QAAQ,GAAG,GAAsB,EAAE;QACvC,IAAI,OAAO,IAAI,CAAC,WAAW;YAAE,OAAO,OAAO,CAAC;QAC5C,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACnB,OAAO,GAAG,CAAC,KAAK,IAAI,EAAE;YACpB,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAClE,4DAA4D;YAC5D,KAAK,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;gBACjC,WAAW,GAAG,IAAI,CAAC;gBACnB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO;oBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC7C,CAAC,CAAC,CAAC;YACH,0EAA0E;YAC1E,oEAAoE;YACpE,oEAAoE;YACpE,gCAAgC;YAChC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC;gBAC3C,eAAe,EAAE,gBAAgB;gBACjC,kBAAkB,EAAE,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE;gBACvF,UAAU,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE;aAC7E,CAAC,CAAC;YACH,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,KAAK,IAAI,CAAC;YAC/D,OAAO,OAAO,CAAC;QACjB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC5B,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QACH,WAAW,GAAG,KAAK,CAAC;QACpB,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF;;;;;OAKG;IACH,MAAM,WAAW,GAAG,KAAK,EAAE,KAAe,EAAE,MAAc,EAAmB,EAAE;QAC7E,MAAM,QAAQ,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACvE,2EAA2E;QAC3E,4CAA4C;QAC5C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBAClE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBAC1B,OAAO,KAAK,CAAC;YACf,CAAC;YAAC,MAAM,CAAC,CAAC,8DAA8D,CAAC,CAAC;YAC1E,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,0DAA0D,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACxD,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;QAC7B,IAAI,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACnE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACvB,MAAM,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrC,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,KAAK,EAAE,IAAgB,EAAE,IAAc,EAAE,OAAmB,EAAiB,EAAE;QAC/F,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC;YAAC,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAAC,MAAM,GAAG,IAAI,CAAC;QAAC,CAAC;QACzE,MAAM,CAAC,CAAC,wDAAwD,CAAC,CAAC;QAClE,MAAM,UAAU,GAAG,KAAK,IAAmB,EAAE;YAC3C,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,GAAG,KAAK,CAAC;YACf,IAAI,CAAC;gBAAC,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;QACrF,CAAC,CAAC;QACF,IAAI,IAA0B,CAAC;QAC/B,IAAI,OAAgC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;YAC/B,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;YACxD,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE;gBAC7B,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC;gBACpD,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;aACnE,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC,CAAC,8DAA8D,CAAC,CAAC;QAC1E,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;YAAE,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;QAC9C,IAAI,IAAI,EAAE,OAAO,KAAK,IAAI,IAAI,OAAO,EAAE,UAAU,KAAK,WAAW,EAAE,CAAC;YAClE,MAAM,UAAU,EAAE,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,8CAA8C,IAAI,CAAC,MAAM,eAAe,CAAC,CAAC;YACpF,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,CAAC,OAAO,EAAE,MAAM,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,UAAU,EAAE,CAAC;YACnB,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,uBAAuB,IAAI,CAAC,MAAM,wBAAwB,CAAC,CAAC;YACpF,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;gBAClD,OAAO,EAAE;oBACP,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,EAAE,CAAC;oBACjE,kEAAkE;oBAClE,kDAAkD;oBAClD,eAAe,EAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;iBACxC;aACF,CAAC,CAAC;YACH,KAAK,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,KAAK,CAAC,GAAG,CAAC,kBAAkB,IAAI,CAAC,MAAM,uBAAuB,CAAC,CAAC;QAClE,CAAC;gBAAS,CAAC;YACT,MAAM,UAAU,EAAE,CAAC;QACrB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC;QAC/B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;YACvB,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;YAC/B,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC;gBAAE,OAAO;YAChD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3B,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACzB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;YACpE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;YACvB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpB,IAAI,CAAC;oBAAC,MAAM,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;gBAAC,CAAC;gBAC9E,MAAM,CAAC,CAAC,0DAA0D,CAAC,CAAC;YACtE,CAAC;YACD,IAAI,KAAkB,CAAC;YACvB,MAAM,MAAM,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,GAAG,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACpE,wEAAwE;YACxE,mEAAmE;YACnE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YACpH,wEAAwE;YACxE,0EAA0E;YAC1E,qEAAqE;YACrE,MAAM,MAAM,CAAC;QACf,CAAC;QACD,UAAU,EAAE,KAAK,IAAI,EAAE;YACrB,0EAA0E;YAC1E,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,sFAAsF,CAAC,CAAC;QAClH,CAAC;KACF,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type ConfigContext } from "./config.js";
2
+ /**
3
+ * Which ACP session belongs to which Relay chat, kept where the profile that
4
+ * owns the agent is kept.
5
+ *
6
+ * An ACP agent (cursor, gemini-cli, opencode) gives every `session/new` a
7
+ * session id, and takes an old one back with `session/load` when it advertises
8
+ * that it can (`agentCapabilities.loadSession`). The bridge is the only thing
9
+ * that knows which session belongs to which chat, and it would forget on every
10
+ * restart while the ids lived in memory. They live here instead, beside
11
+ * `config.json`, so a chat keeps its context across restarts of the bridge and
12
+ * of the agent itself. This is the shape `codex-threads.ts` uses for Codex,
13
+ * kept in its own file so the two never share a key.
14
+ */
15
+ export declare const acpSessionsPath: (context?: ConfigContext) => string;
16
+ /** The agent a set of sessions belongs to. */
17
+ export interface AcpSessionOwner {
18
+ /** The Relay the agent lives on, so staging and production never share a session. */
19
+ apiURL: string;
20
+ /** The agent that answers, so two agents in one folder never share a session. */
21
+ handle: string;
22
+ }
23
+ /** One chat's key in the file: the Relay, the agent, and the chat. */
24
+ export declare const acpSessionKey: (owner: AcpSessionOwner, chatId: string) => string;
25
+ /** The session ids for one agent, read once and written on every change. */
26
+ export interface AcpSessionStore {
27
+ /** The session the agent opened for this chat, if this computer still remembers one. */
28
+ get(chatId: string): string | undefined;
29
+ /** Remembers the session the agent just opened for this chat. */
30
+ set(chatId: string, sessionId: string): Promise<void>;
31
+ }
32
+ export declare const openAcpSessions: (owner: AcpSessionOwner, context?: ConfigContext) => Promise<AcpSessionStore>;
33
+ //# sourceMappingURL=acp-threads.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp-threads.d.ts","sourceRoot":"","sources":["../src/acp-threads.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAK7D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,eAAe,GAAI,UAAS,aAAkB,KAAG,MACL,CAAC;AAE1D,8CAA8C;AAC9C,MAAM,WAAW,eAAe;IAC9B,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,sEAAsE;AACtE,eAAO,MAAM,aAAa,GAAI,OAAO,eAAe,EAAE,QAAQ,MAAM,KAAG,MAC1B,CAAC;AA0B9C,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,wFAAwF;IACxF,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,iEAAiE;IACjE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,eAAe,GAC1B,OAAO,eAAe,EACtB,UAAS,aAAkB,KAC1B,OAAO,CAAC,eAAe,CAYzB,CAAC"}