agent-relay 13.4.1 → 15.0.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +45 -29
  2. package/dist/cli/agent-relay-mcp.d.ts +1 -1
  3. package/dist/cli/agent-relay-mcp.d.ts.map +1 -1
  4. package/dist/cli/agent-relay-mcp.js +204 -11
  5. package/dist/cli/agent-relay-mcp.js.map +1 -1
  6. package/dist/cli/bootstrap.d.ts.map +1 -1
  7. package/dist/cli/bootstrap.js +19 -0
  8. package/dist/cli/bootstrap.js.map +1 -1
  9. package/dist/cli/commands/agent.d.ts +5 -1
  10. package/dist/cli/commands/agent.d.ts.map +1 -1
  11. package/dist/cli/commands/agent.js +50 -18
  12. package/dist/cli/commands/agent.js.map +1 -1
  13. package/dist/cli/commands/cloud.js +4 -4
  14. package/dist/cli/commands/cloud.js.map +1 -1
  15. package/dist/cli/commands/fleet.d.ts +2 -1
  16. package/dist/cli/commands/fleet.d.ts.map +1 -1
  17. package/dist/cli/commands/fleet.js +48 -6
  18. package/dist/cli/commands/fleet.js.map +1 -1
  19. package/dist/cli/commands/init.d.ts +47 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -0
  21. package/dist/cli/commands/init.js +626 -0
  22. package/dist/cli/commands/init.js.map +1 -0
  23. package/dist/cli/commands/integration-recipient.d.ts.map +1 -1
  24. package/dist/cli/commands/integration-recipient.js +9 -5
  25. package/dist/cli/commands/integration-recipient.js.map +1 -1
  26. package/dist/cli/commands/local-workflow.d.ts.map +1 -1
  27. package/dist/cli/commands/local-workflow.js +6 -8
  28. package/dist/cli/commands/local-workflow.js.map +1 -1
  29. package/dist/cli/commands/workspace.d.ts.map +1 -1
  30. package/dist/cli/commands/workspace.js +24 -1
  31. package/dist/cli/commands/workspace.js.map +1 -1
  32. package/dist/cli/lib/agent-takeover.d.ts +40 -0
  33. package/dist/cli/lib/agent-takeover.d.ts.map +1 -0
  34. package/dist/cli/lib/agent-takeover.js +91 -0
  35. package/dist/cli/lib/agent-takeover.js.map +1 -0
  36. package/dist/cli/lib/agent-token-store.d.ts +38 -0
  37. package/dist/cli/lib/agent-token-store.d.ts.map +1 -0
  38. package/dist/cli/lib/agent-token-store.js +91 -0
  39. package/dist/cli/lib/agent-token-store.js.map +1 -0
  40. package/dist/cli/lib/agent-tools-mcp.d.ts +167 -0
  41. package/dist/cli/lib/agent-tools-mcp.d.ts.map +1 -0
  42. package/dist/cli/lib/agent-tools-mcp.js +748 -0
  43. package/dist/cli/lib/agent-tools-mcp.js.map +1 -0
  44. package/dist/cli/lib/broker-lifecycle.d.ts +1 -2
  45. package/dist/cli/lib/broker-lifecycle.d.ts.map +1 -1
  46. package/dist/cli/lib/broker-lifecycle.js +6 -52
  47. package/dist/cli/lib/broker-lifecycle.js.map +1 -1
  48. package/dist/cli/lib/init-prompts.d.ts +56 -0
  49. package/dist/cli/lib/init-prompts.d.ts.map +1 -0
  50. package/dist/cli/lib/init-prompts.js +54 -0
  51. package/dist/cli/lib/init-prompts.js.map +1 -0
  52. package/dist/cli/lib/locked-json-file.d.ts +16 -0
  53. package/dist/cli/lib/locked-json-file.d.ts.map +1 -0
  54. package/dist/cli/lib/locked-json-file.js +111 -0
  55. package/dist/cli/lib/locked-json-file.js.map +1 -0
  56. package/dist/cli/lib/mcp-session-name.d.ts +42 -0
  57. package/dist/cli/lib/mcp-session-name.d.ts.map +1 -0
  58. package/dist/cli/lib/mcp-session-name.js +91 -0
  59. package/dist/cli/lib/mcp-session-name.js.map +1 -0
  60. package/dist/cli/lib/node-definition-loader.d.ts +0 -7
  61. package/dist/cli/lib/node-definition-loader.d.ts.map +1 -1
  62. package/dist/cli/lib/node-definition-loader.js +0 -10
  63. package/dist/cli/lib/node-definition-loader.js.map +1 -1
  64. package/dist/cli/lib/node-provider-child.d.ts +3 -4
  65. package/dist/cli/lib/node-provider-child.d.ts.map +1 -1
  66. package/dist/cli/lib/node-provider-child.js +2 -2
  67. package/dist/cli/lib/node-provider-child.js.map +1 -1
  68. package/dist/cli/lib/relaycast-origin-binding.d.ts +103 -0
  69. package/dist/cli/lib/relaycast-origin-binding.d.ts.map +1 -0
  70. package/dist/cli/lib/relaycast-origin-binding.js +277 -0
  71. package/dist/cli/lib/relaycast-origin-binding.js.map +1 -0
  72. package/dist/cli/lib/sdk-client.d.ts +8 -0
  73. package/dist/cli/lib/sdk-client.d.ts.map +1 -1
  74. package/dist/cli/lib/sdk-client.js +83 -8
  75. package/dist/cli/lib/sdk-client.js.map +1 -1
  76. package/dist/cli/lib/sdk-command.d.ts.map +1 -1
  77. package/dist/cli/lib/sdk-command.js +7 -1
  78. package/dist/cli/lib/sdk-command.js.map +1 -1
  79. package/dist/cli/lib/spawn-lifecycle.d.ts.map +1 -1
  80. package/dist/cli/lib/spawn-lifecycle.js +3 -2
  81. package/dist/cli/lib/spawn-lifecycle.js.map +1 -1
  82. package/dist/cli/mcp/types.d.ts +14 -0
  83. package/dist/cli/mcp/types.d.ts.map +1 -1
  84. package/dist/cli/telemetry/events.d.ts +2 -2
  85. package/dist/cli/telemetry/events.d.ts.map +1 -1
  86. package/dist/index.cjs +4 -2
  87. package/package.json +11 -10
package/README.md CHANGED
@@ -346,19 +346,22 @@ sandbox is selected. Ambient credentials and persisted Cloud routing do not
346
346
  change the local default. Workforce reporting metadata requires remote placement.
347
347
  Automatic placement and release need only the workspace key.
348
348
 
349
- The sandbox path provisions a fresh hosted instance and makes the Relayfile
350
- mount mandatory by default. Inside a GitHub checkout, Relay infers the Git root,
351
- repository identity, exact `HEAD`, and caller-relative directory. Cloud uses the
352
- pinned workspace's connected GitHub credential to seed that revision into
353
- Relayfile, and the worker starts in the decoded source tree under
354
- `/workspace/github/repos/<owner>/<repo>/contents`. The long-running Relayfile
355
- daemon keeps the mounted source tree synchronized with the workspace while
356
- GitHub push events update the workspace's repository source. Use `--checkout`
357
- when a task needs a separate static Git clone; that mode keeps the live
358
- Relayfile mirror available separately. Use `--sandbox-provider daytona` or
359
- `--sandbox-provider e2b` to require an operator-enabled provider; omit the flag
360
- to let Cloud's sandbox router choose. Pass `--no-sandbox-relayfile` only when a
361
- deliberately bare sandbox is desired. If provisioning times out or the spawn
349
+ The sandbox path provisions a fresh hosted instance. Inside a GitHub checkout,
350
+ Relay infers the Git root, repository identity, exact pushed `HEAD`, and caller-relative
351
+ directory. Cloud makes a depth-1 clone, fetches that exact SHA, verifies `HEAD`,
352
+ and starts the worker under `/srv/agent-workforce/<repo>`. Repository tasks start
353
+ without waiting for Relayfile context. `--checkout` explicitly selects the same
354
+ static mode. Outside Git, the default still requires the workspace Relayfile mount.
355
+
356
+ Use `--sandbox-relayfile` to require live context before dispatch. Without
357
+ `--checkout`, this retains the live repository mirror under
358
+ `/workspace/github/repos/<owner>/<repo>/contents`; with `--checkout`, it mounts
359
+ context separately from the Git clone. An explicit `--sandbox-relayfile-path`
360
+ also requires its selected context before startup. `--no-sandbox-relayfile`
361
+ explicitly disables mounting (combine with `--checkout` to clone repository files).
362
+ Use `--sandbox-provider daytona` or `--sandbox-provider e2b` to require an
363
+ operator-enabled provider; omit the flag to let Cloud's sandbox router choose.
364
+ If provisioning times out or the spawn
362
365
  fails, Relay asks Cloud to delete the newly created sandbox. Runs without a
363
366
  custom name use one `sbx_<UUID>` identity and the matching
364
367
  `fleet-sandbox-<UUID>` node name. Legacy custom `--sandbox-name` values remain supported when no
@@ -380,7 +383,7 @@ reachable from a configured GitHub remote. This prevents a remote worker from
380
383
  silently starting at a different revision. Commit and push local work before
381
384
  retrying when Relay reports dirty files or an unreachable commit.
382
385
 
383
- With `--checkout`, sandbox provisioning also clones the attested `HEAD` under
386
+ In default repository mode or with `--checkout`, sandbox provisioning clones the attested `HEAD` under
384
387
  `/srv/agent-workforce/<repo>`. The checkout must have no tracked changes or
385
388
  untracked source files, and the exact commit must be reachable from an origin
386
389
  remote. Relay's generated `.agentworkforce/relay/workspace-key.json`,
@@ -403,7 +406,7 @@ release commands reuse the persisted project route; if that remote session is
403
406
  unavailable, the command reports the routing failure instead of selecting a
404
407
  same-named local worker.
405
408
 
406
- From a clean repository already pinned to a Relay workspace, the ordinary live
409
+ From a clean repository already pinned to a Relay workspace, the checkout-first
407
410
  path is:
408
411
 
409
412
  ```bash
@@ -416,31 +419,44 @@ agent-relay fleet agent list
416
419
  agent-relay fleet release cloud-zero-config
417
420
  ```
418
421
 
419
- Invoking the live command from `packages/web` starts the worker at
420
- `/workspace/github/repos/<owner>/<repo>/contents/packages/web`. The repository
421
- source metadata is available beside `contents` under `.relayfile`, `.skills`
422
- is mounted from the same workspace, and no `.git` directory is written into the
423
- Relayfile mirror.
422
+ Invoking the command from `packages/web` starts the worker at
423
+ `/srv/agent-workforce/<repo>/packages/web`, with `.git` available and `HEAD`
424
+ pinned to the requested revision.
424
425
 
425
- For a static Git checkout, opt in explicitly:
426
+ Attach optional context later using `cloudWorkspaceId` and `sandboxId` from the
427
+ spawn response (this uses Cloud sign-in):
426
428
 
427
429
  ```bash
428
- agent-relay fleet spawn codex \
429
- --name cloud-checkout \
430
- --task "Inspect this repository and report its current commit" \
431
- --sandbox \
432
- --checkout
430
+ agent-relay fleet relayfile attach \
431
+ --cloud-workspace-id <Cloud-workspace-UUID> \
432
+ --sandbox-id <sbx_UUID> \
433
+ --path '/linear' '/.skills'
433
434
  ```
434
435
 
436
+ Omit `--path` for the full workspace. For a dev sandbox, add
437
+ `--cloud-api-url https://dev.agentrelay.com/cloud`. The command waits at most
438
+ five minutes by default, reports the separate mount path, and does not restart
439
+ the worker or retry a failed attachment. Pending detached preparation is polled
440
+ against the same sandbox within that shared deadline. Tell an already running worker the
441
+ returned path when its task needs that context. The mount uses its own directory;
442
+ it never overlays the Git checkout. Attachment failure leaves the repository task
443
+ running.
444
+
445
+ For a live repository mirror that must be ready before the task starts, add
446
+ `--sandbox-relayfile` to spawn. Its decoded tree contains no `.git`; its source
447
+ metadata sits beside `contents` under `.relayfile`. Use
448
+ `--checkout --sandbox-relayfile` for a Git checkout plus required live context.
449
+
435
450
  In checkout mode, invoking spawn from `packages/web` places the worker in that
436
451
  same relative directory in the remote clone. In both modes, private repositories
437
452
  use the pinned workspace's connected GitHub access; a repository-access error
438
453
  means that connection must be granted access to the repository. No GitHub token
439
454
  or workspace key needs to be copied into the task, mount, or checkout.
440
455
 
441
- With `--checkout`, the Git checkout and Relayfile mirror are separate trees. In
456
+ With `--checkout --sandbox-relayfile`, the Git checkout and Relayfile mirror are
457
+ separate trees. Checkout-only mode has no Relayfile mirror or workspace skills. In
442
458
  the ordinary live mode, source files are decoded from Relayfile records without
443
- placing `.git` in the mirror. Workspace `.skills` are exposed through the agent
459
+ placing `.git` in the mirror. When a live mount is requested at startup, workspace `.skills` are exposed through the agent
444
460
  CLIs' usual skill directories, and the worker's task context identifies the
445
461
  mirror and exact source revision.
446
462
 
@@ -618,7 +634,7 @@ of polling `gh`. `--to self` cannot be combined with `--spawn`.
618
634
 
619
635
  `local` remains as a deprecated hidden alias of `node` (it prints a one-time warning).
620
636
 
621
- Node workflow runs use Relayflows for YAML, TypeScript, and Python workflow files.
637
+ Node workflow runs use Relayflows for YAML and TypeScript workflow files.
622
638
 
623
639
  Hosted equivalents live under `agent-relay cloud …`.
624
640
 
@@ -3,7 +3,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import type { AgentRelayMcpServerOptions, AgentType, RegistrationSession, RelayCastLike, SessionSetter } from './mcp/types.js';
4
4
  export type { AgentRelayMcpServerOptions } from './mcp/types.js';
5
5
  export declare const AGENT_RELAY_MCP_VERSION: string;
6
- export declare const AGENT_RELAY_MCP_INSTRUCTIONS = "You are an AI agent in a collaborative workspace powered by Agent Relay. You can communicate with other agents using these MCP tools:\n\n## Coordination rule\n- When the user asks you to work with, contact, coordinate with, or wait for named participants that already exist in this Agent Relay workspace, use Agent Relay tools such as \"list_agents\", \"send_dm\", \"post_message\", and \"check_inbox\".\n- Existing Relay participants are not local or built-in subagents. Do not replace them with your CLI's native subagent, team, task, or collaboration feature.\n- Do not claim to have contacted or waited for a Relay participant unless the corresponding Relay tool call succeeded.\n\n## Getting Started\n1. The current project workspace is resumed automatically when one was selected before\n2. Call \"create_workspace\" only when you explicitly want to start a new workspace session\n3. If someone shared an existing workspace key with you, call \"set_workspace_key\"\n4. When a workspace key is available at startup, this MCP server auto-registers the session as RELAY_AGENT_NAME (or \"orchestrator\" by default). Otherwise call \"register_agent\" with your agent name to join the workspace\n5. Use \"list_channels\" to see available channels\n6. Use \"join_channel\" to join channels of interest\n7. Use \"check_inbox\" to see unread messages and mentions\n\n## Communication\n- Post messages to channels with \"post_message\"\n- Send direct messages with \"send_dm\"\n- Reply to threads with \"reply_to_thread\"\n- React to messages with \"add_reaction\"\n\n## Fleet\n- Use \"query_nodes\" to find fleet nodes by capability or name\n- Use \"spawn\" with either a CLI or an AgentWorkforce persona to invoke the fleet spawn action on an eligible node\n- Persona spawns require a node exposing \"spawn:persona\" through @agentworkforce/local-surface's defineWorkforcePersonaSpawnNode\n\n## Best Practices\n- Check your inbox regularly for new messages and mentions\n- Use channels for topic-based discussions\n- Use threads for detailed discussions to keep channels organized\n- React with emoji to acknowledge messages\n- Keep messages concise and actionable";
6
+ export declare const AGENT_RELAY_MCP_INSTRUCTIONS = "You are an AI agent in a collaborative workspace powered by Agent Relay. You can communicate with other agents using these MCP tools:\n\n## Coordination rule\n- When the user asks you to work with, contact, coordinate with, or wait for named participants that already exist in this Agent Relay workspace, use Agent Relay tools such as \"list_agents\", \"send_dm\", \"post_message\", and \"check_inbox\".\n- Existing Relay participants are not local or built-in subagents. Do not replace them with your CLI's native subagent, team, task, or collaboration feature.\n- Do not claim to have contacted or waited for a Relay participant unless the corresponding Relay tool call succeeded.\n\n## Getting Started\n1. The current project workspace is resumed automatically when one was selected before\n2. Call \"create_workspace\" only when you explicitly want to start a new workspace session\n3. If someone shared an existing workspace key with you, call \"set_workspace_key\"\n4. When a workspace key is available at startup, this MCP server registers the session automatically: as RELAY_AGENT_NAME when it is set, otherwise as your tool plus the project folder (for example \"claude-myproject\", with a short suffix when another live session holds that name). Call \"register_agent\" to choose a different name, or to join when no workspace key was available\n5. Use \"list_channels\" to see available channels\n6. Use \"join_channel\" to join channels of interest\n7. Use \"check_inbox\" to see unread messages and mentions\n\n## Communication\n- Post messages to channels with \"post_message\"\n- Send direct messages with \"send_dm\"\n- Reply to threads with \"reply_to_thread\"\n- React to messages with \"add_reaction\"\n\n## Fleet\n- Use \"query_nodes\" to find fleet nodes by capability or name\n- Use \"spawn\" with either a CLI or an AgentWorkforce persona to invoke the fleet spawn action on an eligible node\n- Persona spawns require a node exposing \"spawn:persona\" through @agentworkforce/local-surface's defineWorkforcePersonaSpawnNode\n\n## Best Practices\n- Check your inbox regularly for new messages and mentions\n- Use channels for topic-based discussions\n- Use threads for detailed discussions to keep channels organized\n- React with emoji to acknowledge messages\n- Keep messages concise and actionable";
7
7
  type RegisterAgentWithRebindArgs = {
8
8
  session: RegistrationSession;
9
9
  setSession: SessionSetter;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-relay-mcp.d.ts","sourceRoot":"","sources":["../../src/cli/agent-relay-mcp.ts"],"names":[],"mappings":";AAgBA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA0DpE,OAAO,KAAK,EAEV,0BAA0B,EAC1B,SAAS,EAET,mBAAmB,EACnB,aAAa,EACb,aAAa,EAEd,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAEjE,eAAO,MAAM,uBAAuB,QACuC,CAAC;AAoU5E,eAAO,MAAM,4BAA4B,unEAgCF,CAAC;AAiBxC,KAAK,2BAA2B,GAAG;IACjC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,aAAa,CAAC;IAC1B,QAAQ,EAAE,MAAM,aAAa,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAcF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAqCD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAOjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAMnF;AAsID,wBAAsB,uBAAuB,CAAC,EAC5C,OAAO,EACP,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,qBAAqB,GACtB,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+FhE;AA22BD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CAgOxF;AASD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA0BrC;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAwChG;AAED,wBAAgB,cAAc,IAAI,0BAA0B,CAsC3D"}
1
+ {"version":3,"file":"agent-relay-mcp.d.ts","sourceRoot":"","sources":["../../src/cli/agent-relay-mcp.ts"],"names":[],"mappings":";AAgBA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA+DpE,OAAO,KAAK,EAEV,0BAA0B,EAC1B,SAAS,EAGT,mBAAmB,EACnB,aAAa,EACb,aAAa,EAEd,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAEjE,eAAO,MAAM,uBAAuB,QACuC,CAAC;AAoU5E,eAAO,MAAM,4BAA4B,uxEAgCF,CAAC;AAiBxC,KAAK,2BAA2B,GAAG;IACjC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,UAAU,EAAE,aAAa,CAAC;IAC1B,QAAQ,EAAE,MAAM,aAAa,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAcF;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAqCD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAOjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAMnF;AAsID,wBAAsB,uBAAuB,CAAC,EAC5C,OAAO,EACP,UAAU,EACV,QAAQ,EACR,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,qBAAqB,GACtB,EAAE,2BAA2B,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA+FhE;AA22BD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,CA0OxF;AAqMD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,0BAA0B,CAAC,CA0BrC;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyChG;AAED,wBAAgB,cAAc,IAAI,0BAA0B,CAkD3D"}
@@ -7,11 +7,14 @@ import { fileURLToPath } from 'node:url';
7
7
  import { isDeepStrictEqual } from 'node:util';
8
8
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
- import { ListToolsRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
10
+ import { CallToolRequestSchema, InitializeRequestSchema, ListToolsRequestSchema, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
11
  import { AgentRelay, RELAYCAST_SDK_VERSION, createAgentClient, createObserverToken, createRealtimeClient, createWorkspaceClient, isInvalidAgentTokenError, safeRelayErrorMessage, } from '@agent-relay/sdk';
12
12
  import { z } from 'zod';
13
13
  import { declaredWorkforceMetadata } from './lib/registration-metadata.js';
14
14
  import { isBundledBunEntrypointPath } from './lib/agent-relay-mcp-command.js';
15
+ import { guardCredentialOrigin } from './lib/relaycast-origin-binding.js';
16
+ import { isAgentAlreadyExists, takeOverAgent } from './lib/agent-takeover.js';
17
+ import { pickAvailableName, sessionBaseName } from './lib/mcp-session-name.js';
15
18
  import { DEFAULT_AGENT_REGISTRATION_TIMEOUT_MS, withAgentRegistrationDeadline, withDeadline, } from './lib/agent-registration.js';
16
19
  import { attributableReleaseReason } from './lib/release-reason.js';
17
20
  import { sanitizedSpawnReceipt, spawnPlacementReceipt, } from './lib/spawn-lifecycle.js';
@@ -283,7 +286,7 @@ export const AGENT_RELAY_MCP_INSTRUCTIONS = `You are an AI agent in a collaborat
283
286
  1. The current project workspace is resumed automatically when one was selected before
284
287
  2. Call "create_workspace" only when you explicitly want to start a new workspace session
285
288
  3. If someone shared an existing workspace key with you, call "set_workspace_key"
286
- 4. When a workspace key is available at startup, this MCP server auto-registers the session as RELAY_AGENT_NAME (or "orchestrator" by default). Otherwise call "register_agent" with your agent name to join the workspace
289
+ 4. When a workspace key is available at startup, this MCP server registers the session automatically: as RELAY_AGENT_NAME when it is set, otherwise as your tool plus the project folder (for example "claude-myproject", with a short suffix when another live session holds that name). Call "register_agent" to choose a different name, or to join when no workspace key was available
287
290
  5. Use "list_channels" to see available channels
288
291
  6. Use "join_channel" to join channels of interest
289
292
  7. Use "check_inbox" to see unread messages and mentions
@@ -1425,8 +1428,185 @@ export function createAgentRelayMcpServer(options) {
1425
1428
  return result;
1426
1429
  });
1427
1430
  }
1431
+ if (options.autoRegister) {
1432
+ installSessionAutoRegistration(mcpServer, handlers, options.autoRegister, {
1433
+ getSession,
1434
+ setSession,
1435
+ getRelay,
1436
+ agentType: options.agentType,
1437
+ baseUrl: options.baseUrl,
1438
+ });
1439
+ }
1428
1440
  return mcpServer;
1429
1441
  }
1442
+ const RELAYCAST_HOSTED_ORIGIN = 'https://cast.agentrelay.com';
1443
+ /** Under Relaycast's 60s presence TTL, with room for one missed beat. */
1444
+ const SESSION_HEARTBEAT_MS = 25_000;
1445
+ /** How an auto-registered session marks itself offline when it ends. */
1446
+ const sessionDisconnectors = new WeakMap();
1447
+ /**
1448
+ * Names of the sessions connected right now. The presence endpoint tracks
1449
+ * live connections (a session that exits drops out within its heartbeat TTL,
1450
+ * at once when it disconnects cleanly), where the roster's `online` filter
1451
+ * keeps a registration for minutes after its process is gone.
1452
+ */
1453
+ async function liveAgentNames(relay, workspaceKey, baseUrl, fetchImpl) {
1454
+ const live = new Set();
1455
+ try {
1456
+ const response = await fetchImpl(new URL('/v1/agents/presence', baseUrl ?? RELAYCAST_HOSTED_ORIGIN), {
1457
+ headers: { authorization: `Bearer ${workspaceKey}` },
1458
+ signal: AbortSignal.timeout(5_000),
1459
+ });
1460
+ if (response.ok) {
1461
+ const body = (await response.json());
1462
+ if (Array.isArray(body.data)) {
1463
+ for (const entry of body.data) {
1464
+ if (entry?.status === 'online' && typeof entry.agent_name === 'string')
1465
+ live.add(entry.agent_name);
1466
+ }
1467
+ return live;
1468
+ }
1469
+ }
1470
+ }
1471
+ catch {
1472
+ // Fall back to the roster below.
1473
+ }
1474
+ try {
1475
+ for (const agent of await relay.agents.list({ status: 'online' })) {
1476
+ const name = agent?.name;
1477
+ if (typeof name === 'string')
1478
+ live.add(name);
1479
+ }
1480
+ }
1481
+ catch {
1482
+ // Without either, the base name is still the best guess.
1483
+ }
1484
+ return live;
1485
+ }
1486
+ /**
1487
+ * Register a session that has no configured name as soon as the MCP client
1488
+ * says who it is. The name comes from `clientInfo.name` and the folder the
1489
+ * tool started in (`claude-myproject`); a live session already holding it
1490
+ * pushes this one to `claude-myproject-2`. Tool calls wait for the
1491
+ * registration, so the first call never races it.
1492
+ */
1493
+ function installSessionAutoRegistration(mcpServer, handlers, autoRegister, ctx) {
1494
+ const fetchImpl = autoRegister.fetch ?? globalThis.fetch;
1495
+ let pending = null;
1496
+ const register = async (clientName) => {
1497
+ const base = sessionBaseName({ clientName, hint: autoRegister.clientHint, cwd: autoRegister.cwd });
1498
+ const workspaceKey = ctx.getSession().workspaceKey;
1499
+ const live = await liveAgentNames(ctx.getRelay(), workspaceKey, ctx.baseUrl, fetchImpl);
1500
+ const name = pickAvailableName(base, live);
1501
+ let token;
1502
+ let registeredName;
1503
+ try {
1504
+ const payload = await registerAgentWithRebind({
1505
+ session: ctx.getSession(),
1506
+ setSession: ctx.setSession,
1507
+ getRelay: ctx.getRelay,
1508
+ name,
1509
+ type: ctx.agentType ?? 'agent',
1510
+ });
1511
+ token = typeof payload.token === 'string' ? payload.token : null;
1512
+ registeredName = typeof payload.registered_name === 'string' ? payload.registered_name : null;
1513
+ }
1514
+ catch (error) {
1515
+ // The name belongs to an earlier session of this tool in this folder
1516
+ // that is no longer connected (a restart): resume that identity.
1517
+ if (!isAgentAlreadyExists(error))
1518
+ throw error;
1519
+ const grant = await takeOverAgent({ workspaceKey, baseUrl: ctx.baseUrl, fetch: fetchImpl }, name, 'agent-relay mcp: resume the identity of an earlier session of this tool in this folder');
1520
+ token = grant.token;
1521
+ registeredName = grant.name;
1522
+ }
1523
+ if (token && registeredName) {
1524
+ const agents = new Map(ctx.getSession().agents);
1525
+ agents.set(registeredName, createRegisteredAgent(registeredName, token));
1526
+ ctx.setSession({ agentToken: token, agentName: registeredName, agents });
1527
+ startHeartbeat();
1528
+ }
1529
+ };
1530
+ // Keep this session present while it runs, so the next session in the same
1531
+ // folder sees the name is taken. Presence lapses about a minute after the
1532
+ // last beat, which is how a crashed session frees its name.
1533
+ let heartbeat;
1534
+ const beat = () => {
1535
+ const token = ctx.getSession().agentToken;
1536
+ if (!token)
1537
+ return;
1538
+ void fetchImpl(new URL('/v1/agents/heartbeat', ctx.baseUrl ?? RELAYCAST_HOSTED_ORIGIN), {
1539
+ method: 'POST',
1540
+ headers: { authorization: `Bearer ${token}` },
1541
+ signal: AbortSignal.timeout(5_000),
1542
+ }).catch(() => undefined);
1543
+ };
1544
+ const startHeartbeat = () => {
1545
+ if (heartbeat)
1546
+ return;
1547
+ beat();
1548
+ heartbeat = setInterval(beat, SESSION_HEARTBEAT_MS);
1549
+ heartbeat.unref?.();
1550
+ };
1551
+ // Read clientInfo from the initialize request itself, and start right after
1552
+ // answering it, so nothing depends on the order a client's messages land in.
1553
+ const initialize = handlers.get('initialize');
1554
+ if (initialize) {
1555
+ mcpServer.server.setRequestHandler(InitializeRequestSchema, async (req, extra) => {
1556
+ const result = await initialize(req, extra);
1557
+ const session = ctx.getSession();
1558
+ if (!pending && session.workspaceKey && !session.agentToken) {
1559
+ pending = register(req.params.clientInfo?.name).catch((error) => {
1560
+ process.stderr.write(`[agent-relay] Could not register this session automatically (${safeRelayErrorMessage(error)}). ` +
1561
+ 'Call "register_agent" to join the workspace.\n');
1562
+ });
1563
+ }
1564
+ return result;
1565
+ });
1566
+ }
1567
+ const callTool = handlers.get('tools/call');
1568
+ if (callTool) {
1569
+ mcpServer.server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
1570
+ if (pending)
1571
+ await pending;
1572
+ return (await callTool(req, extra));
1573
+ });
1574
+ }
1575
+ // Leave cleanly so the next session in this folder can take the name at once.
1576
+ sessionDisconnectors.set(mcpServer, async () => {
1577
+ if (heartbeat)
1578
+ clearInterval(heartbeat);
1579
+ const token = ctx.getSession().agentToken;
1580
+ if (!token)
1581
+ return;
1582
+ ctx.getSession().wsBridge?.stop();
1583
+ await fetchImpl(new URL('/v1/agents/disconnect', ctx.baseUrl ?? RELAYCAST_HOSTED_ORIGIN), {
1584
+ method: 'POST',
1585
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json' },
1586
+ body: '{}',
1587
+ signal: AbortSignal.timeout(2_000),
1588
+ }).catch(() => undefined);
1589
+ });
1590
+ }
1591
+ /**
1592
+ * Mark an auto-registered session offline when its tool stops it: stdin
1593
+ * closes (the MCP stdio shutdown) or the process is signalled.
1594
+ */
1595
+ function disconnectSessionOnExit(mcpServer) {
1596
+ const disconnect = sessionDisconnectors.get(mcpServer);
1597
+ if (!disconnect)
1598
+ return;
1599
+ let closing = false;
1600
+ const shutdown = () => {
1601
+ if (closing)
1602
+ return;
1603
+ closing = true;
1604
+ void Promise.race([disconnect(), new Promise((resolve) => setTimeout(resolve, 2_500))]).finally(() => process.exit(0));
1605
+ };
1606
+ process.stdin.once('end', shutdown);
1607
+ for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP'])
1608
+ process.once(signal, shutdown);
1609
+ }
1430
1610
  /** Relaycast agent tokens are opaque `at_live_<hex>` literals. Anything else
1431
1611
  * (for example a RelayAuth JWT carried in RELAY_AGENT_TOKEN by `relay on start`)
1432
1612
  * is not a valid Relaycast credential and must be replaced. */
@@ -1493,6 +1673,7 @@ export async function startAgentRelayMcpStdio(options) {
1493
1673
  });
1494
1674
  const transport = new StdioServerTransport();
1495
1675
  await mcpServer.connect(transport);
1676
+ disconnectSessionOnExit(mcpServer);
1496
1677
  }
1497
1678
  export function optionsFromEnv() {
1498
1679
  let workspaceKey = resolveEnv('RELAY_WORKSPACE_KEY') ??
@@ -1514,21 +1695,33 @@ export function optionsFromEnv() {
1514
1695
  // session can still be selected explicitly with set_workspace_key.
1515
1696
  }
1516
1697
  }
1517
- const agentName = resolveEnv('RELAY_AGENT_NAME') ??
1518
- resolveEnv('RELAY_CLAW_NAME') ??
1519
- (workspaceKey ? 'orchestrator' : undefined);
1698
+ // With no configured name the session is named after the MCP client and the
1699
+ // project folder once the client connects (see installSessionAutoRegistration),
1700
+ // so two sessions never share one identity.
1701
+ const agentName = resolveEnv('RELAY_AGENT_NAME') ?? resolveEnv('RELAY_CLAW_NAME');
1702
+ // An agent token has no workspace identity encoded locally. Only reuse it
1703
+ // when the workspace was selected alongside it through the environment;
1704
+ // a persisted project/store fallback must register into that workspace
1705
+ // instead of silently pairing it with a possibly stale ambient token.
1706
+ const agentToken = resumedPersistedWorkspace ? undefined : resolveEnv('RELAY_AGENT_TOKEN');
1707
+ // The same rule as the CLI: a credential bound to a self-hosted or local
1708
+ // server (by `relay init`, `workspace join`, …) only ever goes back to it,
1709
+ // so the MCP server never sends it to the hosted default.
1710
+ const credential = workspaceKey ?? agentToken;
1711
+ const baseUrl = credential
1712
+ ? guardCredentialOrigin(credential, resolveEnv('RELAY_BASE_URL'))
1713
+ : resolveEnv('RELAY_BASE_URL');
1520
1714
  return {
1521
1715
  workspaceKey,
1522
- baseUrl: resolveEnv('RELAY_BASE_URL'),
1523
- // An agent token has no workspace identity encoded locally. Only reuse it
1524
- // when the workspace was selected alongside it through the environment;
1525
- // a persisted project/store fallback must register into that workspace
1526
- // instead of silently pairing it with a possibly stale ambient token.
1527
- agentToken: resumedPersistedWorkspace ? undefined : resolveEnv('RELAY_AGENT_TOKEN'),
1716
+ baseUrl,
1717
+ agentToken,
1528
1718
  agentName,
1529
1719
  agentType: normalizeAgentType(resolveEnv('RELAY_AGENT_TYPE')),
1530
1720
  strictAgentName: envFlagEnabled(resolveEnv('RELAY_STRICT_AGENT_NAME')),
1531
1721
  skipBootstrap: envFlagEnabled(resolveEnv('RELAY_SKIP_BOOTSTRAP')),
1722
+ autoRegister: workspaceKey && !agentName && !agentToken
1723
+ ? { cwd: process.cwd(), clientHint: resolveEnv('AGENT_RELAY_CLIENT') }
1724
+ : undefined,
1532
1725
  };
1533
1726
  }
1534
1727
  if (isEntrypoint()) {