replicas-mcp 0.2.14 → 0.2.16

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.
package/dist/index.js CHANGED
@@ -13,12 +13,21 @@ var AGENT = {
13
13
  DEEPSEEK: "deepseek",
14
14
  FX: "fx",
15
15
  KIMI: "kimi",
16
+ MUSE: "muse",
16
17
  OPENCODE: "opencode",
17
18
  PI: "pi",
18
19
  RELAY: "relay"
19
20
  };
20
21
  var VALID_AGENT_PROVIDERS = Object.values(AGENT);
21
22
  var VALID_RELAY_SUBAGENT_PROVIDERS = VALID_AGENT_PROVIDERS;
23
+ var VALID_RELAY_BASE_PROVIDERS = [
24
+ AGENT.CLAUDE,
25
+ AGENT.CODEX,
26
+ AGENT.DEEPSEEK,
27
+ AGENT.OPENCODE,
28
+ AGENT.PI
29
+ ];
30
+ var DEFAULT_RELAY_BASE_PROVIDER = AGENT.CLAUDE;
22
31
  var VALID_CODING_AGENT_PROVIDERS = VALID_AGENT_PROVIDERS.filter(
23
32
  (provider) => provider !== AGENT.RELAY
24
33
  );
@@ -34,6 +43,7 @@ var THINKING_LEVELS_BY_AGENT = {
34
43
  deepseek: STANDARD_THINKING_LEVELS,
35
44
  fx: STANDARD_THINKING_LEVELS,
36
45
  kimi: STANDARD_THINKING_LEVELS,
46
+ muse: VALID_THINKING_LEVELS.filter((level) => level !== "max" && level !== "ultracode"),
37
47
  opencode: STANDARD_THINKING_LEVELS,
38
48
  pi: STANDARD_THINKING_LEVELS,
39
49
  relay: [...STANDARD_THINKING_LEVELS, "ultracode"]
@@ -53,6 +63,8 @@ function getProviderDisplayName(provider, variant = "short") {
53
63
  return "fx";
54
64
  case "kimi":
55
65
  return variant === "long" ? "Kimi Code" : "Kimi";
66
+ case "muse":
67
+ return variant === "long" ? "Muse Code" : "Muse";
56
68
  case "opencode":
57
69
  return "Opencode";
58
70
  case "pi":
@@ -92,7 +104,8 @@ var CREDENTIAL_METHOD = {
92
104
  OPENCODE_GO: "opencode-go",
93
105
  ASTER: "aster",
94
106
  OPENROUTER: "openrouter",
95
- AI_GATEWAY: "ai-gateway"
107
+ AI_GATEWAY: "ai-gateway",
108
+ MUSE_OAUTH: "muse-oauth"
96
109
  };
97
110
  var CREDENTIAL_TYPE = {
98
111
  CLAUDE_OAUTH: "claude",
@@ -106,7 +119,13 @@ var CREDENTIAL_TYPE = {
106
119
  OPENCODE_GO_API_KEY: "opencode_go_api_key",
107
120
  ASTER_API_KEY: "aster_api_key",
108
121
  OPENROUTER_API_KEY: "openrouter_api_key",
109
- AI_GATEWAY_API_KEY: "ai_gateway_api_key"
122
+ AI_GATEWAY_API_KEY: "ai_gateway_api_key",
123
+ MUSE_OAUTH: "muse_oauth"
124
+ };
125
+ var INFERENCE_PROVIDER_LABELS = {
126
+ [CREDENTIAL_METHOD.OPENCODE_GO]: "OpenCode Go",
127
+ [CREDENTIAL_METHOD.ASTER]: "Aster",
128
+ [CREDENTIAL_METHOD.OPENROUTER]: "OpenRouter"
110
129
  };
111
130
  var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
112
131
  [AGENT.CLAUDE]: [
@@ -132,12 +151,16 @@ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
132
151
  [AGENT.KIMI]: [
133
152
  { method: CREDENTIAL_METHOD.AI_GATEWAY, credentialType: CREDENTIAL_TYPE.AI_GATEWAY_API_KEY }
134
153
  ],
154
+ [AGENT.MUSE]: [
155
+ { method: CREDENTIAL_METHOD.MUSE_OAUTH, credentialType: CREDENTIAL_TYPE.MUSE_OAUTH }
156
+ ],
135
157
  [AGENT.OPENCODE]: [
136
158
  { method: CREDENTIAL_METHOD.OPENCODE_GO, credentialType: CREDENTIAL_TYPE.OPENCODE_GO_API_KEY },
137
159
  { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
138
160
  { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
139
161
  ],
140
162
  [AGENT.PI]: [
163
+ { method: CREDENTIAL_METHOD.OPENCODE_GO, credentialType: CREDENTIAL_TYPE.OPENCODE_GO_API_KEY },
141
164
  { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
142
165
  { method: CREDENTIAL_METHOD.OPENROUTER, credentialType: CREDENTIAL_TYPE.OPENROUTER_API_KEY }
143
166
  ]
@@ -192,6 +215,7 @@ var WEB_APP_PATHS = {
192
215
  "audit-logs": `${DASHBOARD_PATH}/audit-logs`,
193
216
  accountAgents: `${DASHBOARD_PATH}/account/agents`,
194
217
  accountProfile: `${DASHBOARD_PATH}/account/profile`,
218
+ accountConnections: `${DASHBOARD_PATH}/account/connections`,
195
219
  accountIntegrations: `${DASHBOARD_PATH}/account/integrations`,
196
220
  accountApiKeys: `${DASHBOARD_PATH}/account/api-keys`,
197
221
  accountPreferences: `${DASHBOARD_PATH}/account/preferences`,
@@ -264,10 +288,12 @@ replicas computer record stop
264
288
  replicas media upload /tmp/demo.mp4 --name "Demo recording"
265
289
  \`\`\`
266
290
 
291
+ For any external destination, continue with the required delivery flow in \`MEDIA.md\`.
292
+
267
293
  ## Command reference
268
294
 
269
295
  ### \`replicas computer info\`
270
- Prints the live desktop viewer URL (\`https://6080-<hash>.tryreplicas.com/\`). The engine auto-registers this authenticated preview on startup; \`info\` just looks it up. Use it when you want to share the live stream URL with the user out-of-band (e.g. in a Slack reply) - the dashboard \`Desktop\` tab already points at the same URL automatically.
296
+ Prints the live desktop viewer URL (\`https://6080-<hash>.replicas.dev/\`). The engine auto-registers this authenticated preview on startup; \`info\` just looks it up. Use it when you want to share the live stream URL with the user out-of-band (e.g. in a Slack reply) - the dashboard \`Desktop\` tab already points at the same URL automatically.
271
297
 
272
298
  If invoked very early in the workspace lifecycle, \`info\` will poll briefly while the engine finishes registering the preview, then error if it's still not available.
273
299
 
@@ -398,7 +424,7 @@ replicas computer record start /tmp/walkthrough.mp4
398
424
  replicas computer record stop
399
425
  replicas media upload /tmp/walkthrough.mp4 --name "Walkthrough recording"
400
426
  \`\`\`
401
- Then embed the printed \`![\u2026](\u2026)\` line in your chat reply. See \`MEDIA.md\`.
427
+ Then embed the printed \`![\u2026](\u2026)\` line in your chat reply and follow \`MEDIA.md\` for any external destination.
402
428
 
403
429
  ## Failure modes
404
430
 
@@ -598,16 +624,9 @@ gh pr review 123 --approve
598
624
  gh pr review 123 --request-changes --body "Changes needed"
599
625
  \`\`\`
600
626
 
601
- ## Image Uploads in PRs
627
+ ## Media in GitHub workflows
602
628
 
603
- GitHub does NOT have a public API for uploading images to PRs/issues. When you need to include images:
604
- - Do NOT use placeholder image URLs
605
- - Do NOT commit screenshots as files to the repository
606
- - Run \`replicas media upload <image> --name "<display name>" --share\` and put the printed \`Forge embed\` Markdown in the PR body or comment
607
- - For a self-contained HTML report, run \`replicas media upload <page.html> --name "<display name>" --access organization\` and put the printed \`Forge link\` Markdown in the PR body or comment. Organization members who are already signed in open it directly; others sign in and return to the page
608
- - For video, embed a shared poster image linked to the recording's **View in Replicas** URL; GitHub does not reliably render externally hosted video inline
609
- - Treat the public URL as an opt-in bearer capability; revoke it with \`replicas media revoke <media-id>\` only when the user asks or the media should stop rendering \u2014 revoking breaks embeds already posted on the PR
610
- - If you were triggered from Slack, also upload the image to the Slack thread so the user can see it directly
629
+ Media delivery is owned by \`references/MEDIA.md\`. Read its GitHub section before adding screenshots, recordings, diagrams, or HTML reports to a pull request, issue, review comment, or commit; it covers the Replicas upload, GitHub's inline fallback, and the required **View in Replicas** link.
611
630
  `;
612
631
  var GITHUB_ABILITY = {
613
632
  label: "GitHub",
@@ -669,7 +688,7 @@ await replicas.gitlab.request('/projects/group%2Frepo/pipelines', {
669
688
 
670
689
  ## Descriptions and media
671
690
 
672
- API updates replace the entire merge request description, so always preserve the required Replicas footer and co-author trailers. For media, first run \`replicas media upload\` as documented in \`MEDIA.md\`; use GitLab's project uploads endpoint only when a native repository-protected embed is required.
691
+ API updates replace the entire merge request description, so always preserve the required Replicas footer and co-author trailers. Media delivery is owned by \`references/MEDIA.md\`; read its GitLab section before adding media so the original bytes are uploaded to Replicas and GitLab and the dashboard link remains secondary.
673
692
  `;
674
693
  var GITLAB_ABILITY = {
675
694
  label: "GitLab",
@@ -734,6 +753,8 @@ await replicas.linear.graphql({
734
753
  \`\`\`
735
754
 
736
755
  Search with \`containsIgnoreCase: $query\` filters, for example on both \`title\` and \`description\`. Batch independent queries with \`Promise.all\` to save round trips.
756
+
757
+ For screenshots, recordings, diagrams, or other media, read \`references/MEDIA.md\` before adding it to an issue or comment. It is the single source of truth for uploading the Replicas copy and the raw file to Linear; a dashboard link alone is not an inline attachment.
737
758
  `;
738
759
  var LINEAR_ABILITY = {
739
760
  label: "Linear",
@@ -745,18 +766,19 @@ var LINEAR_ABILITY = {
745
766
 
746
767
  // ../shared/src/default-skills/replicas-agent/abilities/media.ts
747
768
  var SECTION6 = `### Media
748
- Share screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external messages.
769
+ Deliver screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages to the Replicas dashboard and, when applicable, inline in the destination workflow.
749
770
 
750
771
  **Reference:** \`references/MEDIA.md\`
751
772
 
752
773
  Use this when:
774
+ - You are about to run \`replicas media upload\`; this reference owns the complete Replicas-first, destination-second workflow
753
775
  - You produce a screenshot, recording, generated image, or audio clip the user should see
754
776
  - You produce a self-contained HTML report that should be linked from a pull request
755
777
  - You record video output (browser automation, screen capture) \u2014 including the recommended aspect ratio and FPS
756
- - You need to embed media in a Slack/Linear/GitHub message AND keep a referenceable copy in the Replicas dashboard`;
778
+ - You need to embed media in a Slack/Linear/GitHub/GitLab message AND keep a referenceable copy in the Replicas dashboard`;
757
779
  var REFERENCE6 = `# Media (Screenshots, Recordings, Audio, HTML)
758
780
 
759
- This guide covers how to share screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external surfaces (Slack, Linear, GitHub).
781
+ This is the single source of truth for delivering screenshots, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external surfaces (Slack, Linear, GitHub, GitLab).
760
782
 
761
783
  ## Prerequisites
762
784
 
@@ -766,12 +788,32 @@ The \`replicas\` CLI is pre-installed and authenticated in your workspace. No ad
766
788
 
767
789
  Upload to Replicas in these cases \u2014 and **only** these cases:
768
790
 
769
- 1. **Media you produce.** Any screenshot, screen recording, generated diagram, or audio clip you create that the user might want to see. Upload before doing anything else with the file (analyzing, deleting, sending elsewhere). This applies even when you're also sending the file to Slack, Linear, GitHub, etc.
791
+ 1. **Media you produce.** Any screenshot, screen recording, generated diagram, audio clip, or HTML page you create that the user might want to see. Upload before doing anything else with the file (analyzing, deleting, sending elsewhere). This applies even when you're also sending the file to Slack, Linear, GitHub, or GitLab.
770
792
  2. **Files the user explicitly asks you to upload.** If the user sends or points at a file (image, video, audio, HTML) and asks you to upload it, run \`replicas media upload\`. Otherwise leave it alone \u2014 files in the workspace the user did not ask about should not be auto-uploaded as media.
771
- 3. **Anything you plan to share externally (Slack, Linear, GitHub, etc.).** Upload to Replicas *in addition to* the platform's native upload. Never as a replacement.
793
+ 3. **Anything you plan to share externally (Slack, Linear, GitHub, GitLab, etc.).** Upload to Replicas *in addition to* the platform's destination upload. Never as a replacement.
772
794
 
773
795
  If none of these apply, don't upload.
774
796
 
797
+ ## Required delivery contract
798
+
799
+ \`replicas media upload\` is always the first delivery action. When the current task has an external destination, every other delivery step follows only after that command succeeds. For every file:
800
+
801
+ 1. First, run \`replicas media upload\` with the original file. Do not begin any destination upload until the command succeeds. This creates the canonical dashboard copy and prints a chat embed plus a per-file dashboard link. With public access, the printed Forge embed is the chat embed and is also safe to use on GitHub.
802
+ 2. Include the printed embed line in the Replicas chat reply, followed by the matching **View in Replicas** link. HTML uses its printed page link instead of an image embed.
803
+ 3. Identify every destination in the task and attempt to upload the same original bytes there. Use the destination's inline representation from the table below; do not replace the file with a Replicas link.
804
+ 4. Put the **View in Replicas** dashboard link next to the destination embed as a secondary link.
805
+ 5. Check each destination response. If a native upload is unavailable or fails, say so explicitly and keep the dashboard link; never claim that a link is an inline upload.
806
+
807
+ | Destination | Required file delivery | Inline result |
808
+ |---|---|---|
809
+ | Replicas dashboard/chat | \`replicas media upload\` | Printed \`![\u2026](\u2026)\` line in chat |
810
+ | Slack | \`files.getUploadURLExternal\` \u2192 raw-byte POST \u2192 \`files.completeUploadExternal\` | Uploaded file in the channel or thread |
811
+ | Linear | \`fileUpload\` mutation \u2192 presigned upload \u2192 issue/comment mutation | Returned \`assetUrl\` in Markdown |
812
+ | GitLab | Project \`/uploads\` endpoint with the original bytes | Returned GitLab Markdown |
813
+ | GitHub | \`gh\` v2.99.0+ \`--attach\` for images and videos; use a Replicas public forge share as the fallback | Native GitHub attachment URL, or a Forge embed fallback |
814
+
815
+ The dashboard link is the subtitle and jump-back path. It is never a substitute for the destination upload.
816
+
775
817
  ## Uploading to Replicas
776
818
 
777
819
  \`\`\`bash
@@ -796,10 +838,11 @@ replicas media upload /tmp/before.png /tmp/after.png --name "Before fix" --name
796
838
 
797
839
  Names are normalized to hyphen-case (spaces become hyphens, e.g. \`Login page\` is stored as \`login-page\`). Give every asset a **distinct** name \u2014 duplicates are not rejected, but Replicas keeps the first as-is and adds a \`-1\`, \`-2\` suffix to later collisions, which produces confusing lists, so pick unique names yourself.
798
840
 
799
- For each media file, the CLI prints two lines:
841
+ For each media file, the CLI prints an embed line and a dashboard link:
800
842
 
801
- 1. \`![filename](<api-url>)\` \u2014 the chat embed (renders inline in Replicas chat **only** \u2014 see below).
802
- 2. \`View in Replicas: <deep-link>\` \u2014 a per-file dashboard URL of the form \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` that opens directly to that specific file.
843
+ 1. Default and organization access print \`Replicas chat embed: ![filename](<api-url>)\`; this authenticated URL renders inline in Replicas chat **only** \u2014 see below.
844
+ 2. Public access (\`--access public\` or \`--share\`) prints \`![filename](<public-url>)\` as the Forge embed instead. This is the public, stable bearer URL for GitHub fallback embeds when native \`--attach\` is unavailable and sharing is authorized.
845
+ 3. \`View in Replicas: <deep-link>\` is a per-file dashboard URL of the form \`https://replicas.dev/workspaces/<workspace-id>?media=<media-id>\` that opens directly to that specific file.
803
846
 
804
847
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
805
848
 
@@ -819,25 +862,26 @@ Use public access only when the media must render without Replicas authenticatio
819
862
  replicas media upload screenshot.png --name "Login page" --access public
820
863
  \`\`\`
821
864
 
822
- \`--share\` remains an alias for \`--access public\`. Public access prints an additional \`Forge embed\` for image, video, or audio media, backed by an opt-in bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. HTML pages only support organization access. Use \`replicas media share <media-id>\` to rotate a public media URL and \`replicas media revoke <media-id>\` to revoke it.
865
+ \`--share\` remains an alias for \`--access public\`. Public access prints the raw \`Forge embed\` Markdown instead of the authenticated chat-only embed for image, video, or audio media, backed by an opt-in bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. HTML pages only support organization access. Use \`replicas media share <media-id>\` to rotate a public media URL and \`replicas media revoke <media-id>\` to revoke it.
823
866
 
824
867
  ## The URLs the CLI gives you \u2014 and where each one is allowed
825
868
 
826
869
  | URL | What it is | Where it's allowed |
827
870
  |---|---|---|
828
- | \`https://api.tryreplicas.com/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect to a presigned blob \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
829
- | \`https://tryreplicas.com/organizations/<organization-id>/media/<media-id>\` | Organization-only page printed by \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
830
- | \`https://api.tryreplicas.com/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | GitHub embeds for image, video, or audio media. |
831
- | \`https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external \u2014 Slack, GitHub, Linear, PR bodies, comments, emails. |
871
+ | \`https://api.replicas.dev/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect to a presigned blob \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
872
+ | \`https://replicas.dev/organizations/<organization-id>/media/<media-id>\` | Organization-only page printed by \`--access organization\` | Pull requests and other places where every viewer belongs to the Replicas organization. |
873
+ | \`https://api.replicas.dev/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--access public\` or \`--share\` | The Replicas chat reply and GitHub fallback embeds, for image, video, or audio media. |
874
+ | \`https://replicas.dev/workspaces/<workspace-id>?media=<media-id>\` | Authenticated dashboard deep link that opens the media file in the workspace | Everywhere external: Slack, GitHub, GitLab, Linear, PR bodies, comments, emails. |
832
875
 
833
876
  ### NEVER link the API redirect URL outside the Replicas chat reply
834
877
 
835
- The \`api.tryreplicas.com/v1/media/<id>\` URL is **not public**. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
878
+ The \`api.replicas.dev/v1/media/<id>\` URL is **not public**. It's an authenticated redirect that only resolves to a presigned download when the Replicas chat session token is present. Anywhere else (Slack, Linear, GitHub, a browser tab the user opens directly, a customer copy-pasting from chat) it returns \`{"error":"Missing authorization token"}\` and renders as a broken image.
836
879
 
837
880
  This rule has no exceptions:
838
881
  - \u274C Never paste it in a Slack message \u2014 upload the actual bytes via the 3-step \`files.getUploadURLExternal\` \u2192 POST bytes \u2192 \`files.completeUploadExternal\` flow instead (see below).
839
882
  - \u274C Never paste it in a GitHub PR description, issue body, review comment, or commit message.
840
883
  - \u274C Never paste it in a Linear issue body or comment \u2014 upload via Linear's \`fileUpload\` mutation instead (see below).
884
+ - \u274C Never paste it in a GitLab merge request or issue; upload the original bytes through GitLab's project upload endpoint instead (see below).
841
885
  - \u274C Never link to it from any external doc, email, or external chat.
842
886
  - \u274C Never include it as a "backup link" in case the dashboard link breaks \u2014 it will always break externally.
843
887
 
@@ -846,7 +890,7 @@ This rule has no exceptions:
846
890
  For an organization-only HTML page, use the \`Forge link\` printed by \`--access organization\`. For other authenticated media references, use the dashboard form:
847
891
 
848
892
  \`\`\`
849
- https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>
893
+ https://replicas.dev/workspaces/<workspace-id>?media=<media-id>
850
894
  \`\`\`
851
895
 
852
896
  The \`media=<media-id>\` query param opens that specific file in the workspace.
@@ -861,20 +905,20 @@ Include each markdown embed line **verbatim** where you want that file to render
861
905
 
862
906
  After (or alongside) the embeds, include a \`[View in Replicas](<deep-link>)\` hyperlink for each file using the per-file dashboard URL from the CLI. This lets the user open the specific item in the workspace.
863
907
 
864
- ## On external platforms \u2014 upload natively AND link the dashboard
908
+ ## On external platforms: embed the file AND link the dashboard
865
909
 
866
- Sharing media externally is a **two-step requirement** for every file:
910
+ After \`replicas media upload\` succeeds, external delivery has two required follow-up steps for every file:
867
911
 
868
- 1. **Upload the raw bytes natively to that platform** so the recipient sees the media embedded inline in the message itself.
912
+ 1. **Upload the raw bytes through that platform's supported inline path** so the recipient sees the media embedded in the message itself.
869
913
  2. **Link the Replicas dashboard deep link** (\`?media=<id>\` form) so the user can jump straight to the file in the workspace.
870
914
 
871
- A link alone is not enough. A native upload alone is not enough. Do both.
915
+ A link alone is not enough. An upload without the dashboard link is not enough. Do both.
872
916
 
873
- GitHub has no native upload API, so use an explicit Replicas public share for screenshots and recording previews. GitLab has a native upload API and should receive the original bytes.
917
+ Slack, Linear, and GitLab receive the original bytes through their native upload APIs. GitHub image and video attachments use \`gh --attach\` when the installed CLI and host support it; use an explicit Replicas public share only as the fallback.
874
918
 
875
919
  ### Slack \u2014 embed natively via the 3-step external file upload flow
876
920
 
877
- A Slack message that just contains a \`tryreplicas.com\` link is not acceptable when the message is supposed to show an image. The recipient should see the image inline in the thread.
921
+ A Slack message that just contains a \`replicas.dev\` link is not acceptable when the message is supposed to show an image. The recipient should see the image inline in the thread.
878
922
 
879
923
  Slack deprecated the single-shot \`files.upload\` endpoint. The raw HTTP replacement is a 3-step flow: reserve an upload URL with \`files.getUploadURLExternal\`, POST the bytes to it, then share into the channel/thread with \`files.completeUploadExternal\`. (\`files.uploadV2\` is **only** an SDK convenience wrapper \u2014 it is NOT a real Web API HTTP endpoint and \`POST https://slack.com/api/files.uploadV2\` will return \`unknown_method\`.)
880
924
 
@@ -906,7 +950,7 @@ curl -s -X POST "https://slack.com/api/files.completeUploadExternal" \\
906
950
  \\"files\\": [{\\"id\\": \\"$FILE_ID\\", \\"title\\": \\"Screenshot\\"}],
907
951
  \\"channel_id\\": \\"CHANNEL_ID\\",
908
952
  \\"thread_ts\\": \\"THREAD_TS\\",
909
- \\"initial_comment\\": \\"<https://tryreplicas.com/workspaces/<workspace-id>?media=<media-id>|View in Replicas>\\"
953
+ \\"initial_comment\\": \\"<https://replicas.dev/workspaces/<workspace-id>?media=<media-id>|View in Replicas>\\"
910
954
  }"
911
955
  \`\`\`
912
956
 
@@ -917,7 +961,7 @@ Notes:
917
961
  - For multiple files in the same message, run steps 1\u20132 per file and pass all \`{id, title}\` objects in the \`files\` array of a single \`files.completeUploadExternal\` call \u2014 they share into the thread together.
918
962
  - If you also need to post a separate text reply in the thread, send it with \`chat.postMessage\` after the upload \u2014 but the upload itself must carry the image.
919
963
 
920
- Never paste the \`api.tryreplicas.com/v1/media/<id>\` URL into a Slack message body or \`initial_comment\` \u2014 it will render as a broken image attempt.
964
+ Never paste the \`api.replicas.dev/v1/media/<id>\` URL into a Slack message body or \`initial_comment\` \u2014 it will render as a broken image attempt.
921
965
 
922
966
  ### Linear \u2014 embed natively via the GraphQL \`fileUpload\` mutation
923
967
 
@@ -966,7 +1010,7 @@ curl -s -X PUT "$UPLOAD_URL" \\
966
1010
  --data-binary @"$FILE"
967
1011
 
968
1012
  # 3. Reference assetUrl inline in the comment body, alongside the dashboard link.
969
- BODY=$(printf '![screenshot](%s)\\n\\n[View in Replicas](https://tryreplicas.com/workspaces/%s?media=%s)' \\
1013
+ BODY=$(printf '![screenshot](%s)\\n\\n[View in Replicas](https://replicas.dev/workspaces/%s?media=%s)' \\
970
1014
  "$ASSET_URL" "$REPLICAS_WORKSPACE_ID" "$MEDIA_ID")
971
1015
 
972
1016
  jq -n \\
@@ -980,28 +1024,52 @@ jq -n \\
980
1024
 
981
1025
  The same flow works for issue bodies (use \`issueCreate\` / \`issueUpdate\` with the same markdown body).
982
1026
 
983
- Never paste the \`api.tryreplicas.com/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
1027
+ Never paste the \`api.replicas.dev/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
984
1028
 
985
- ### GitHub: inline screenshots and recording previews
1029
+ ### GitHub: native image and video attachments
986
1030
 
987
- GitHub has **no public API** for uploading attachments to PRs or issues. Its web UI uses an undocumented internal endpoint that API clients must not emulate. For screenshots, explicitly create a forge share and put the printed \`Forge embed\` Markdown directly in the PR body or comment, followed by the dashboard link:
1031
+ GitHub CLI v2.99.0+ supports a repeatable \`--attach\` flag on \`gh pr create\`, \`gh pr edit\`, and \`gh pr comment\` for GitHub.com and GitHub Enterprise Cloud. It uploads local images and videos to GitHub and writes the resulting attachment URL into the PR body or comment; it requires push access to the repository. GitHub Enterprise Server and unsupported media kinds are not covered by this path. See [GitHub's attachment guide](https://docs.github.com/en/github-cli/github-cli/attaching-files-with-github-cli).
1032
+
1033
+ Check \`gh --version\` before choosing the delivery path. On a supported host with v2.99.0 or later:
1034
+
1035
+ 1. Run \`replicas media upload\` with the original file first, without \`--share\`. Keep the matching **View in Replicas** dashboard link for the PR.
1036
+ 2. Put each local file path in the Markdown body where the attachment should appear, and place its dashboard link next to it.
1037
+ 3. Pass the same original path once with \`--attach\`. GitHub CLI rewrites a matching local reference in place, preserving its alt text; an unreferenced attachment is appended to the body.
1038
+
1039
+ \`\`\`markdown
1040
+ Before the change:
1041
+
1042
+ ![Before state](/tmp/before.png)
1043
+ [View in Replicas](https://replicas.dev/workspaces/<workspace-id>?media=<before-media-id>)
1044
+ \`\`\`
988
1045
 
989
1046
  \`\`\`bash
990
- replicas media upload /tmp/screenshot.png --name "PR screenshot" --share
1047
+ gh pr create \\
1048
+ --title "Describe the change" \\
1049
+ --body-file /tmp/pr-body.md \\
1050
+ --attach /tmp/before.png
1051
+
1052
+ gh pr edit 456 --body-file /tmp/pr-body.md --attach /tmp/after.png
1053
+ gh pr comment 456 --body-file /tmp/comment.md --attach /tmp/result.mp4
991
1054
  \`\`\`
992
1055
 
993
- GitHub does not reliably render arbitrary external videos as inline players. For a recording:
1056
+ Repeat \`--attach\` for multiple files, up to 50 per command. For an appended image, add alt text after the path with \`#\`, for example \`--attach './error.png#Error state'\`. To render a video as a player, its local Markdown reference must be the only content in its paragraph. Do not paste a Replicas authenticated API URL into the body, and do not emulate GitHub's undocumented browser upload endpoint.
1057
+
1058
+ ### GitHub attachment fallback
1059
+
1060
+ If \`--attach\` is unavailable, the target is GitHub Enterprise Server, the file is not an image or video, or the native upload fails, keep the Replicas dashboard link and report the limitation. For an inline screenshot or video poster, create a Replicas public forge share only for a public repository or after explicit consent to publish media from a private repository, then use the printed \`Forge embed\` Markdown:
1061
+
1062
+ \`\`\`bash
1063
+ replicas media upload /tmp/screenshot.png --name "PR screenshot" --share
1064
+ \`\`\`
994
1065
 
995
- 1. Upload the MP4 normally so it remains available in Replicas.
996
- 2. Generate a poster image from the recording: \`ffmpeg -i /tmp/demo.mp4 -frames:v 1 /tmp/demo-poster.png\`.
997
- 3. Upload the poster with \`--share\`.
998
- 4. Wrap the poster embed in a link to the recording's **View in Replicas** URL.
1066
+ For a fallback recording preview, upload the MP4 normally, generate a poster with \`ffmpeg -i /tmp/demo.mp4 -frames:v 1 /tmp/demo-poster.png\`, share the poster, and link its embed to the recording's **View in Replicas** URL:
999
1067
 
1000
1068
  \`\`\`markdown
1001
1069
  [![Demo recording](<public-poster-url>)](<recording-dashboard-url>)
1002
1070
  \`\`\`
1003
1071
 
1004
- Public forge shares are bearer capabilities. For private repositories, create one only when the user asked for inline PR media. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering \u2014 never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
1072
+ Public forge shares are unauthenticated bearer capabilities. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering; never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
1005
1073
 
1006
1074
  For a self-contained HTML report, upload it with \`--access organization\` and put the printed \`Forge link\` Markdown in the PR body or comment. GitHub cannot render the page inline, so use the normal link rather than image syntax. HTML pages cannot be shared publicly.
1007
1075
 
@@ -1009,7 +1077,7 @@ Do **not** commit screenshots to the repo, use placeholder URLs, expose the auth
1009
1077
 
1010
1078
  ### GitLab: native inline screenshots and videos
1011
1079
 
1012
- GitLab supports native project Markdown uploads. Always upload the media to Replicas first, then POST the same original file to the repository's \`/projects/:id/uploads\` API using the workspace GitLab credential. Insert the returned \`markdown\` value into the merge request body or comment and include the Replicas dashboard link. Image uploads render inline; MP4, MOV, and WebM uploads render as inline video players.
1080
+ GitLab supports native project Markdown uploads. For a GitLab destination, always upload the media to Replicas first, then POST the same original file to the repository's \`/projects/:id/uploads\` API using the workspace GitLab credential. Insert the returned \`markdown\` value into the merge request body or comment and include the Replicas dashboard link. Image uploads render inline; MP4, MOV, and WebM uploads render as inline video players.
1013
1081
 
1014
1082
  Do not use a Replicas public forge share for GitLab when the native project upload succeeds; native uploads preserve project access controls.
1015
1083
 
@@ -1058,14 +1126,53 @@ replicas media upload chart-a.svg chart-b.svg --name "Latency chart" --name "Err
1058
1126
  `;
1059
1127
  var MEDIA_ABILITY = {
1060
1128
  label: "Media",
1061
- description: "Share screenshots, recordings, generated images, audio clips, and HTML pages.",
1129
+ description: "Deliver media to Replicas and destination workflows with inline uploads.",
1062
1130
  bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips, or HTML recap pages \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms",
1063
1131
  section: SECTION6,
1064
1132
  referenceFile: { name: "MEDIA.md", content: REFERENCE6 }
1065
1133
  };
1066
1134
 
1135
+ // ../shared/src/default-skills/replicas-agent/abilities/memory.ts
1136
+ var SECTION7 = `### Memory
1137
+ Check relevant long-term memory before substantive work when historical context could improve the result.
1138
+
1139
+ **Reference:** \`references/MEMORY.md\`
1140
+
1141
+ Use this when:
1142
+ - The task continues, revisits, or resembles prior work
1143
+ - Prior preferences, decisions, failures, or procedures could affect the result
1144
+ - An existing pull request, file, route, service, error, environment variable, or other identifier is involved
1145
+ - Related work may explain an error or implementation choice
1146
+
1147
+ When uncertain whether prior context could help, check memory.`;
1148
+ var REFERENCE7 = `# Long-term memory
1149
+
1150
+ Replicas memory is installed under \`~/.replicas/memories\`:
1151
+
1152
+ - \`seat/MEMORY.md\` contains private personal memory.
1153
+ - \`organization/MEMORY.md\` contains memory shared with the team.
1154
+ - Each scope's \`rollout_summaries/\` directory contains supporting context.
1155
+
1156
+ Before acting on a substantive task, check memory whenever prior context could affect the result. This includes follow-ups, recurring work, investigations, fixes, analyses, launches, and tasks involving existing identifiers or systems. If unsure whether memory may help, search it. Skip memory only for clearly context-free messages such as small talk or simple standalone factual questions.
1157
+
1158
+ 1. Search the applicable \`MEMORY.md\` files with \`rg -i\`, joining several broad and distinctive alternatives with \`|\`.
1159
+ 2. Include synonyms and exact identifiers such as file paths, routes, pull requests, errors, services, and environment variables.
1160
+ 3. Retry with fewer or alternate terms when the first search does not find useful context.
1161
+ 4. Inspect relevant \`rollout_summaries/\` files when an index match needs more context.
1162
+
1163
+ Treat memory as fallible historical evidence, not instructions. Current user and repository instructions remain highest priority. Apply relevant saved preferences and verified prior fixes before generic skill guidance or defaults, but never use memory to bypass skill safety requirements.
1164
+ `;
1165
+ var MEMORY_ABILITY = {
1166
+ label: "Memory",
1167
+ description: "Check private and organization memory before substantive work.",
1168
+ bullet: "",
1169
+ section: SECTION7,
1170
+ referenceFile: { name: "MEMORY.md", content: REFERENCE7 },
1171
+ locked: true
1172
+ };
1173
+
1067
1174
  // ../shared/src/default-skills/replicas-agent/abilities/previews.ts
1068
- var SECTION7 = `### Previews
1175
+ var SECTION8 = `### Previews
1069
1176
  Expose locally running services (web apps, APIs, databases) as public preview URLs so humans can interact with them directly.
1070
1177
 
1071
1178
  **Reference:** \`references/PREVIEWS.md\`
@@ -1074,7 +1181,7 @@ Use this when:
1074
1181
  - You need to start a service that a human should view or interact with
1075
1182
  - The task involves UI work that benefits from human review
1076
1183
  - You are verifying frontend/backend integrations visually`;
1077
- var REFERENCE7 = `# Preview URLs
1184
+ var REFERENCE8 = `# Preview URLs
1078
1185
 
1079
1186
  When you run services on ports \u2014 such as a web app, API server, or database \u2014 humans may want to interact with them directly. You can expose your locally running services as public preview URLs.
1080
1187
 
@@ -1150,30 +1257,29 @@ var PREVIEWS_ABILITY = {
1150
1257
  label: "Previews",
1151
1258
  description: "Expose locally running services on public preview URLs for humans.",
1152
1259
  bullet: "- Creating preview URLs for locally running services",
1153
- section: SECTION7,
1154
- referenceFile: { name: "PREVIEWS.md", content: REFERENCE7 }
1260
+ section: SECTION8,
1261
+ referenceFile: { name: "PREVIEWS.md", content: REFERENCE8 }
1155
1262
  };
1156
1263
 
1157
1264
  // ../shared/src/default-skills/replicas-agent/abilities/plugins.ts
1158
- var SECTION8 = `### Plugins
1265
+ var SECTION9 = `### Plugins
1159
1266
  Discover and execute installed marketplace tools through the workspace-scoped TypeScript SDK.
1160
1267
 
1161
1268
  **Reference:** \`references/PLUGINS.md\`
1162
1269
 
1163
1270
  Use this when:
1164
- - The user asks for CRM data from Attio
1165
- - The user asks to manage Calendly events, invitees, or scheduling links
1166
- - The user asks to read or change Google Ads, Docs, Drive, Search Console, or Sheets data
1167
- - The user asks for ClickHouse analytics or PostHog product data
1271
+ - The user asks to work with an installed productivity, data, engineering, or business plugin
1272
+ - The user asks to discover which external services are connected
1168
1273
  - The user asks to create or control an E2B sandbox
1169
1274
  - The user asks to invoke a connected Modal function
1275
+ - The user asks to access Vercel projects, deployments, domains, logs, or infrastructure
1276
+ - The user asks to inspect Vanta controls, tests, evidence, vendors, risks, or compliance data
1170
1277
  - The user asks to query or update a connected turbopuffer namespace
1171
1278
  - The user asks for PlanetScale organizations, databases, branches, or Insights data
1172
- - The user asks to analyze text with Pangram
1173
- - The user asks for Stripe customers, payments, invoices, subscriptions, or balances`;
1174
- var REFERENCE8 = `# Plugins
1279
+ - The user asks to analyze text with Pangram`;
1280
+ var REFERENCE9 = `# Plugins
1175
1281
 
1176
- Plugins are TypeScript libraries, not MCP servers. Import \`@replicas/sdk\`; Replicas authenticates the workspace and selects only the owner\u2019s personal connection or the organization fallback. Provider credentials, sessions, and project keys are never exposed.
1282
+ Plugins are TypeScript libraries, not MCP servers. Import \`@replicas/sdk\`; Replicas authenticates the workspace and selects the owner\u2019s personal connection first, then the most specific environment the workspace inherits from, then Global. Provider credentials, sessions, and project keys are never exposed.
1177
1283
 
1178
1284
  Plugin tools may read or write provider data according to the connected account's permissions. Only execute write or destructive tools when they match the user's request.
1179
1285
 
@@ -1186,7 +1292,7 @@ const plugins = await replicas.plugins.list();
1186
1292
  const installed = plugins.filter((plugin) => plugin.connected);
1187
1293
  \`\`\`
1188
1294
 
1189
- If a plugin is not connected, ask the user to install it under personal or organization Integrations in the Replicas dashboard.
1295
+ If a plugin is not connected, ask the user to connect it under personal Connections or the relevant environment's Plugins tab. A connection on Global reaches every workspace unless a more specific environment overrides it.
1190
1296
 
1191
1297
  ## Search, inspect, execute
1192
1298
 
@@ -1235,6 +1341,26 @@ const databases = await replicas.planetscale.request('/organizations/acme/databa
1235
1341
 
1236
1342
  OAuth scopes control permitted operations. Confirm mutations before using POST, PUT, PATCH, or DELETE.
1237
1343
 
1344
+ ## Vercel
1345
+
1346
+ Vercel is a native access-token integration. Pass a relative path from the Vercel REST API, including a \`teamId\` query parameter for team-owned resources:
1347
+
1348
+ \`\`\`ts
1349
+ const projects = await replicas.vercel.request('/v9/projects?teamId=team_123');
1350
+ const deployments = await replicas.vercel.request('/v6/deployments?teamId=team_123');
1351
+ \`\`\`
1352
+
1353
+ Confirm mutations before using POST, PUT, PATCH, or DELETE.
1354
+
1355
+ ## Vanta
1356
+
1357
+ Vanta is a native Manage API integration. Pass a relative path beginning with /v1/; Replicas exchanges the stored client credentials and caches the short-lived token server-side:
1358
+
1359
+ const controls = await replicas.vanta.request('/v1/controls?pageSize=100');
1360
+ const failingTests = await replicas.vanta.request('/v1/tests?statusMatchesAny=NEEDS_ATTENTION');
1361
+
1362
+ Use the optional \`method\` and \`body\` arguments for writes. Confirm mutations before using POST, PUT, PATCH, or DELETE. Paginate with the cursors returned by Vanta.
1363
+
1238
1364
  ## Modal
1239
1365
 
1240
1366
  Modal is a native integration and uses its official JavaScript SDK behind the Replicas gateway. Invoke a deployed function by name:
@@ -1291,12 +1417,12 @@ var PLUGINS_ABILITY = {
1291
1417
  label: "Plugins",
1292
1418
  description: "Use installed marketplace tools through @replicas/sdk.",
1293
1419
  bullet: "- Interacting with installed marketplace plugins through TypeScript",
1294
- section: SECTION8,
1295
- referenceFile: { name: "PLUGINS.md", content: REFERENCE8 }
1420
+ section: SECTION9,
1421
+ referenceFile: { name: "PLUGINS.md", content: REFERENCE9 }
1296
1422
  };
1297
1423
 
1298
1424
  // ../shared/src/default-skills/replicas-agent/abilities/replicas.ts
1299
- var SECTION9 = `### Replicas (in-workspace CLI)
1425
+ var SECTION10 = `### Replicas (in-workspace CLI)
1300
1426
  Take action *with* Replicas itself \u2014 manage automations, environments (variables, files), repos, and \`replicas.json\` config \u2014 using the pre-installed, pre-authenticated \`replicas\` CLI.
1301
1427
 
1302
1428
  **Reference:** \`references/REPLICAS.md\`
@@ -1307,7 +1433,7 @@ Use this when:
1307
1433
  - The user asks "what envs / repos / automations do I have?"
1308
1434
  - The user asks you to scaffold a \`replicas.json\` / \`replicas.yaml\` in a repo
1309
1435
  - You need to run a long-lived service (dev server, daemon) \u2014 always use \`replicas service start\` so it survives workspace sleep/wake`;
1310
- var REFERENCE9 = `# Replicas (in-workspace CLI)
1436
+ var REFERENCE10 = `# Replicas (in-workspace CLI)
1311
1437
 
1312
1438
  This guide covers how to take action *with* Replicas itself from inside a Replicas workspace \u2014 managing automations, environments (and their variables/files), repos, previews, and the user's \`replicas.json\` config \u2014 using the pre-installed \`replicas\` CLI.
1313
1439
 
@@ -1343,7 +1469,7 @@ In agent mode the CLI hides commands that don't make sense for in-workspace agen
1343
1469
  | \`replicas automation ...\` | Manage automations (cron + GitHub/GitLab event triggers) |
1344
1470
  | \`replicas preview ...\` | Register / list preview URLs (covered in \`PREVIEWS.md\`) |
1345
1471
  | \`replicas service ...\` | Run long-lived services detached so they survive workspace sleep/wake (see below) |
1346
- | \`replicas media ...\` | Upload screenshots, videos, audio (covered in \`MEDIA.md\`) |
1472
+ | \`replicas media ...\` | Upload media to the dashboard and follow \`MEDIA.md\` for destination embedding |
1347
1473
  | \`replicas slack thread ...\` | Attach or switch Slack thread routing (covered in \`SLACK.md\`) |
1348
1474
 
1349
1475
  \`replicas <command> --help\` is always the source of truth for flags.
@@ -1534,13 +1660,13 @@ var REPLICAS_ABILITY = {
1534
1660
  description: "Teach the agent about Replicas itself \u2014 automations, environments, the in-workspace CLI.",
1535
1661
  // No bullet — help_instructions covers the `replicas` CLI surface in detail.
1536
1662
  bullet: "",
1537
- section: SECTION9,
1538
- referenceFile: { name: "REPLICAS.md", content: REFERENCE9 },
1663
+ section: SECTION10,
1664
+ referenceFile: { name: "REPLICAS.md", content: REFERENCE10 },
1539
1665
  locked: true
1540
1666
  };
1541
1667
 
1542
1668
  // ../shared/src/default-skills/replicas-agent/abilities/sentry.ts
1543
- var SECTION10 = `### Sentry
1669
+ var SECTION11 = `### Sentry
1544
1670
  Read issues and project data through the workspace-scoped TypeScript SDK.
1545
1671
 
1546
1672
  **Reference:** \`references/SENTRY.md\`
@@ -1548,7 +1674,7 @@ Read issues and project data through the workspace-scoped TypeScript SDK.
1548
1674
  Use this when:
1549
1675
  - You need to inspect Sentry projects or issues
1550
1676
  - A user asks for error-monitoring context outside a webhook-triggered task`;
1551
- var REFERENCE10 = `# Sentry Integration
1677
+ var REFERENCE11 = `# Sentry Integration
1552
1678
 
1553
1679
  Use \`@replicas/sdk\`; never request or read a Sentry token. Sentry access is organization-scoped and read-only.
1554
1680
 
@@ -1567,21 +1693,22 @@ var SENTRY_ABILITY = {
1567
1693
  label: "Sentry",
1568
1694
  description: "Read Sentry projects and issues through @replicas/sdk.",
1569
1695
  bullet: "- Interacting with Sentry (reading projects, issues, and error context)",
1570
- section: SECTION10,
1571
- referenceFile: { name: "SENTRY.md", content: REFERENCE10 }
1696
+ section: SECTION11,
1697
+ referenceFile: { name: "SENTRY.md", content: REFERENCE11 }
1572
1698
  };
1573
1699
 
1574
1700
  // ../shared/src/default-skills/replicas-agent/abilities/slack.ts
1575
- var SECTION11 = `### Slack
1576
- Send messages, read threads, search conversations, and upload files through the workspace-scoped TypeScript SDK.
1701
+ var SECTION12 = `### Slack
1702
+ Send messages, read threads, search conversations, and transfer files through the workspace-scoped TypeScript SDK.
1577
1703
 
1578
1704
  **Reference:** \`references/SLACK.md\`
1579
1705
 
1580
1706
  Use this when:
1581
1707
  - You need to send a message to a Slack channel or thread
1582
1708
  - You need to read or search Slack conversations
1709
+ - You need to download a file attached to a Slack message
1583
1710
  - You encounter a Slack message link and need its full thread`;
1584
- var REFERENCE11 = `# Slack Integration
1711
+ var REFERENCE12 = `# Slack Integration
1585
1712
 
1586
1713
  Use \`@replicas/sdk\`; never read or send a Slack token directly. The SDK calls Replicas, which selects only this workspace's organization connection.
1587
1714
 
@@ -1606,6 +1733,15 @@ const thread = await replicas.slack.call('conversations.replies', {
1606
1733
  });
1607
1734
  \`\`\`
1608
1735
 
1736
+ ## Download a file
1737
+
1738
+ Pass a Slack file object's \`url_private_download\` to the authenticated download helper. Provider credentials stay behind the workspace gateway.
1739
+
1740
+ \`\`\`ts
1741
+ const response = await replicas.slack.download(file.url_private_download);
1742
+ await Bun.write('/tmp/recording.mov', response);
1743
+ \`\`\`
1744
+
1609
1745
  ## Send and attach a message
1610
1746
 
1611
1747
  \`\`\`ts
@@ -1637,6 +1773,8 @@ await replicas.slack.call('chat.postMessage', {
1637
1773
 
1638
1774
  Only the announcing message gets the button \u2014 follow-ups about a pull request already linked in the thread keep the link in the text and drop the \`blocks\`. Use "View MR" for GitLab. With several links, give each button a unique \`action_id\` and name its repository in the label ("View PR \xB7 web").
1639
1775
 
1776
+ For screenshots, recordings, diagrams, or other media, read \`references/MEDIA.md\` before posting. It is the single source of truth for uploading the Replicas copy and the raw file to Slack; a dashboard link in \`text\` is not an inline attachment.
1777
+
1640
1778
  Attach every new top-level conversation so future replies route to this workspace. For a reply in another thread, attach its root \`thread_ts\`, not the reply timestamp. Do not attach the originating thread again.
1641
1779
 
1642
1780
  ## Search and batch
@@ -1652,15 +1790,16 @@ Supported methods cover conversations, messages, search, users, reactions, and S
1652
1790
  `;
1653
1791
  var SLACK_ABILITY = {
1654
1792
  label: "Slack",
1655
- description: "Send messages, read threads, and search through @replicas/sdk.",
1793
+ description: "Send messages, read threads, search, and transfer files through @replicas/sdk.",
1656
1794
  bullet: "- Interacting with Slack (sending messages, reading threads, etc.)",
1657
- section: SECTION11,
1658
- referenceFile: { name: "SLACK.md", content: REFERENCE11 }
1795
+ section: SECTION12,
1796
+ referenceFile: { name: "SLACK.md", content: REFERENCE12 }
1659
1797
  };
1660
1798
 
1661
1799
  // ../shared/src/default-skills/replicas-agent/registry.ts
1662
1800
  var REPLICAS_AGENT_ABILITY_REGISTRY = {
1663
1801
  replicas: REPLICAS_ABILITY,
1802
+ memory: MEMORY_ABILITY,
1664
1803
  computer: COMPUTER_ABILITY,
1665
1804
  docker: DOCKER_ABILITY,
1666
1805
  github: GITHUB_ABILITY,
@@ -1692,7 +1831,6 @@ var DEFAULT_USER_PREFERENCES = {
1692
1831
  codex_models: null,
1693
1832
  cursor_models: null,
1694
1833
  openrouter_models: null,
1695
- relay_models: null,
1696
1834
  close_workspace_on_pr_close: true,
1697
1835
  close_workspace_on_pr_merge: true,
1698
1836
  default_environment_id: null,
@@ -1705,7 +1843,7 @@ var DEFAULT_USER_PREFERENCES = {
1705
1843
  open_links_in_desktop_app: false,
1706
1844
  default_fast_mode: false,
1707
1845
  agent_defaults: { ...EMPTY_AGENT_DEFAULT_SETTINGS },
1708
- credential_priority: null
1846
+ fallback_chains: null
1709
1847
  };
1710
1848
  function defaultReplicasAgentAbilities() {
1711
1849
  const abilities = {};
@@ -1743,32 +1881,41 @@ var ASTER_MODEL_LABELS = {
1743
1881
  // ../shared/src/engine/types.ts
1744
1882
  var CLAUDE_OPUS_1M_MODEL = "opus[1m]";
1745
1883
  var LEGACY_CLAUDE_OPUS_1M_MODEL = "opus-1m";
1884
+ var CLAUDE_FABLE_5_1_MODEL = "claude-fable-5-1";
1746
1885
  var CLAUDE_FABLE_5_MODEL = "claude-fable-5";
1747
1886
  var CLAUDE_OPUS_5_MODEL = "claude-opus-5";
1748
1887
  var CLAUDE_OPUS_4_8_MODEL = "claude-opus-4-8";
1749
1888
  var CLAUDE_SONNET_5_MODEL = "claude-sonnet-5";
1750
1889
  var CLAUDE_HAIKU_4_5_MODEL = "claude-haiku-4-5";
1890
+ var CLAUDE_FABLE_5_1_BEDROCK_MODEL = "anthropic.claude-fable-5-1";
1751
1891
  var CLAUDE_FABLE_5_BEDROCK_MODEL = "anthropic.claude-fable-5";
1752
1892
  var CLAUDE_OPUS_5_BEDROCK_MODEL = "anthropic.claude-opus-5";
1753
1893
  var CLAUDE_OPUS_4_8_BEDROCK_MODEL = "us.anthropic.claude-opus-4-8";
1754
1894
  var CLAUDE_SONNET_5_BEDROCK_MODEL = "anthropic.claude-sonnet-5";
1755
1895
  var CLAUDE_HAIKU_4_5_BEDROCK_MODEL = "us.anthropic.claude-haiku-4-5-20251001-v1:0";
1896
+ var GPT_6_ASTRA_MODEL = "gpt-6-astra";
1756
1897
  var GPT_5_6_SOL_MODEL = "gpt-5.6-sol";
1757
1898
  var GPT_5_6_TERRA_MODEL = "gpt-5.6-terra";
1758
1899
  var GPT_5_6_LUNA_MODEL = "gpt-5.6-luna";
1759
1900
  var DEFAULT_CODEX_MODEL = GPT_5_6_SOL_MODEL;
1760
1901
  var DEFAULT_OPENCODE_MODEL = "z-ai/glm-5.2";
1902
+ var GLM_5_3_FLASH_MODEL = "z-ai/glm-5.3-flash";
1903
+ var OX_ALPHA_MODEL = "stealth/ox-alpha";
1761
1904
  var DEEPSEEK_V4_PRO_MODEL = "deepseek/deepseek-v4-pro-0813";
1762
1905
  var DEEPSEEK_V4_FLASH_MODEL = "deepseek/deepseek-v4-flash-0731";
1763
1906
  var DEFAULT_FX_MODEL = "zai/glm-5.2";
1764
1907
  var DEFAULT_KIMI_MODEL = "moonshotai/kimi-k2.6";
1908
+ var DEFAULT_MUSE_MODEL = "muse-spark-1.2";
1765
1909
  var OPENROUTER_MODELS = [
1766
1910
  DEFAULT_OPENCODE_MODEL,
1911
+ GLM_5_3_FLASH_MODEL,
1912
+ OX_ALPHA_MODEL,
1767
1913
  DEEPSEEK_V4_PRO_MODEL,
1768
1914
  DEEPSEEK_V4_FLASH_MODEL,
1769
1915
  "minimax/minimax-m3",
1770
1916
  "xiaomi/mimo-v2.5-pro",
1771
- "moonshotai/kimi-k2.6"
1917
+ "moonshotai/kimi-k2.6",
1918
+ "google/gemini-3.8-flash"
1772
1919
  ];
1773
1920
  function prioritizeDeepseekModels(models) {
1774
1921
  return [
@@ -1777,11 +1924,13 @@ function prioritizeDeepseekModels(models) {
1777
1924
  ];
1778
1925
  }
1779
1926
  var CLAUDE_MODEL_CONTEXT_WINDOWS = {
1927
+ [CLAUDE_FABLE_5_1_MODEL]: 1e6,
1780
1928
  [CLAUDE_FABLE_5_MODEL]: 1e6,
1781
1929
  [CLAUDE_OPUS_5_MODEL]: 1e6,
1782
1930
  [CLAUDE_OPUS_4_8_MODEL]: 1e6,
1783
1931
  [CLAUDE_SONNET_5_MODEL]: 1e6,
1784
1932
  [CLAUDE_HAIKU_4_5_MODEL]: 2e5,
1933
+ [CLAUDE_FABLE_5_1_BEDROCK_MODEL]: 1e6,
1785
1934
  [CLAUDE_FABLE_5_BEDROCK_MODEL]: 1e6,
1786
1935
  [CLAUDE_OPUS_5_BEDROCK_MODEL]: 1e6,
1787
1936
  [CLAUDE_OPUS_4_8_BEDROCK_MODEL]: 1e6,
@@ -1795,14 +1944,16 @@ var CLAUDE_MODEL_CONTEXT_WINDOWS = {
1795
1944
  };
1796
1945
  var AGENT_MODELS = {
1797
1946
  claude: [
1947
+ CLAUDE_FABLE_5_1_MODEL,
1798
1948
  CLAUDE_FABLE_5_MODEL,
1799
1949
  CLAUDE_OPUS_5_MODEL,
1800
1950
  CLAUDE_OPUS_4_8_MODEL,
1801
1951
  CLAUDE_SONNET_5_MODEL,
1802
1952
  CLAUDE_HAIKU_4_5_MODEL
1803
1953
  ],
1804
- codex: [DEFAULT_CODEX_MODEL, GPT_5_6_TERRA_MODEL, GPT_5_6_LUNA_MODEL, "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.2"],
1954
+ codex: [DEFAULT_CODEX_MODEL, GPT_6_ASTRA_MODEL, GPT_5_6_TERRA_MODEL, GPT_5_6_LUNA_MODEL, "gpt-5.5", "gpt-5.4", "gpt-5.4-mini", "gpt-5.2"],
1805
1955
  cursor: [
1956
+ CLAUDE_FABLE_5_1_MODEL,
1806
1957
  CLAUDE_FABLE_5_MODEL,
1807
1958
  CLAUDE_OPUS_5_MODEL,
1808
1959
  CLAUDE_OPUS_4_8_MODEL,
@@ -1823,6 +1974,7 @@ var AGENT_MODELS = {
1823
1974
  "gpt-5.1-codex-max",
1824
1975
  "gpt-5.1-codex-mini",
1825
1976
  "gpt-5-mini",
1977
+ "gemini-3.8-flash",
1826
1978
  "gemini-3.1-pro",
1827
1979
  "gemini-3-flash",
1828
1980
  "grok-4.6",
@@ -1834,12 +1986,25 @@ var AGENT_MODELS = {
1834
1986
  deepseek: prioritizeDeepseekModels(OPENROUTER_MODELS),
1835
1987
  fx: [DEFAULT_FX_MODEL],
1836
1988
  kimi: [DEFAULT_KIMI_MODEL],
1989
+ muse: [DEFAULT_MUSE_MODEL],
1837
1990
  opencode: [...OPENROUTER_MODELS, ...ASTER_MODELS],
1838
1991
  pi: [...OPENROUTER_MODELS, ...ASTER_MODELS],
1839
- relay: [CLAUDE_FABLE_5_MODEL, CLAUDE_OPUS_5_MODEL, CLAUDE_OPUS_4_8_MODEL, CLAUDE_SONNET_5_MODEL]
1992
+ relay: [CLAUDE_FABLE_5_1_MODEL, CLAUDE_FABLE_5_MODEL, CLAUDE_OPUS_5_MODEL, CLAUDE_OPUS_4_8_MODEL, CLAUDE_SONNET_5_MODEL]
1993
+ };
1994
+ var DEFAULT_STATIC_AGENT_MODELS = {
1995
+ claude: AGENT_MODELS.claude,
1996
+ codex: AGENT_MODELS.codex
1997
+ };
1998
+ var DEFAULT_CURSOR_MODELS = AGENT_MODELS.cursor;
1999
+ var CLAUDE_MODEL_MIN_SANDBOX_IMAGE = {
2000
+ [CLAUDE_FABLE_5_1_MODEL]: "replicas-sandbox-2026-09-01-v6"
2001
+ };
2002
+ var CODEX_MODEL_MIN_SANDBOX_IMAGE = {
2003
+ [GPT_6_ASTRA_MODEL]: "replicas-sandbox-2026-09-04-v7"
1840
2004
  };
1841
2005
  var MODEL_LABELS = {
1842
- fable: "Fable 5",
2006
+ fable: "Fable 5.1",
2007
+ [CLAUDE_FABLE_5_1_MODEL]: "Fable 5.1",
1843
2008
  [CLAUDE_FABLE_5_MODEL]: "Fable 5",
1844
2009
  sonnet: "Sonnet 5",
1845
2010
  "claude-sonnet-5": "Sonnet 5",
@@ -1848,16 +2013,21 @@ var MODEL_LABELS = {
1848
2013
  [CLAUDE_OPUS_1M_MODEL]: "Opus (1M)",
1849
2014
  [LEGACY_CLAUDE_OPUS_1M_MODEL]: "Opus (1M)",
1850
2015
  haiku: "Haiku 4.5",
2016
+ [GPT_6_ASTRA_MODEL]: "GPT-6 Astra",
1851
2017
  [GPT_5_6_SOL_MODEL]: "GPT-5.6 Sol",
1852
2018
  [GPT_5_6_TERRA_MODEL]: "GPT-5.6 Terra",
1853
2019
  [GPT_5_6_LUNA_MODEL]: "GPT-5.6 Luna",
1854
2020
  "gpt-5.5": "GPT-5.5",
1855
2021
  [DEFAULT_OPENCODE_MODEL]: "GLM 5.2 via OpenRouter",
2022
+ [GLM_5_3_FLASH_MODEL]: "GLM 5.3 Flash via OpenRouter",
2023
+ [OX_ALPHA_MODEL]: "Ox Alpha via OpenRouter",
1856
2024
  [DEEPSEEK_V4_PRO_MODEL]: "DeepSeek V4 Pro via OpenRouter",
1857
2025
  [DEEPSEEK_V4_FLASH_MODEL]: "DeepSeek V4 Flash via OpenRouter",
1858
2026
  "minimax/minimax-m3": "MiniMax M3 via OpenRouter",
1859
2027
  "xiaomi/mimo-v2.5-pro": "MiMo V2.5 Pro via OpenRouter",
1860
2028
  "moonshotai/kimi-k2.6": "Kimi K2.6 via OpenRouter",
2029
+ [DEFAULT_MUSE_MODEL]: "Muse Spark 1.2",
2030
+ "google/gemini-3.8-flash": "Gemini 3.8 Flash via OpenRouter",
1861
2031
  ...ASTER_MODEL_LABELS,
1862
2032
  "gpt-5.4": "GPT-5.4",
1863
2033
  "gpt-5.4-mini": "GPT-5.4 Mini",
@@ -1873,6 +2043,7 @@ var MODEL_LABELS = {
1873
2043
  [CLAUDE_OPUS_4_8_MODEL]: "Opus 4.8",
1874
2044
  "claude-opus-4-7": "Opus 4.7",
1875
2045
  [CLAUDE_HAIKU_4_5_MODEL]: "Haiku 4.5",
2046
+ "gemini-3.8-flash": "Gemini 3.8 Flash",
1876
2047
  "gemini-3.1-pro": "Gemini 3.1 Pro",
1877
2048
  "gemini-3-flash": "Gemini 3 Flash",
1878
2049
  "grok-4.6": "Grok 4.6",
@@ -1890,46 +2061,66 @@ var createChatRequestSchema = z2.object({
1890
2061
  id: z2.string().uuid().optional(),
1891
2062
  createdAt: z2.string().datetime().optional(),
1892
2063
  provider: z2.enum(VALID_AGENT_PROVIDERS),
2064
+ relayBaseProvider: z2.enum(VALID_CODING_AGENT_PROVIDERS).optional(),
1893
2065
  title: z2.string().min(1).optional(),
1894
2066
  parentChatId: z2.string().uuid().optional(),
1895
2067
  clientRequestId: z2.string().min(1).max(128).optional()
2068
+ }).refine((request) => request.provider === "relay" || request.relayBaseProvider === void 0, {
2069
+ message: "relayBaseProvider is only valid for Relay chats"
2070
+ });
2071
+ var spawnRelaySubagentRequestSchema = z2.object({
2072
+ provider: z2.enum(VALID_AGENT_PROVIDERS),
2073
+ prompt: z2.string().min(1),
2074
+ model: z2.string().min(1).optional(),
2075
+ thinkingLevel: z2.enum(VALID_THINKING_LEVELS).optional(),
2076
+ title: z2.string().min(1).optional(),
2077
+ timeoutMinutes: z2.number().positive().max(1440).optional()
2078
+ });
2079
+ var messageRelaySubagentRequestSchema = z2.object({
2080
+ message: z2.string().min(1),
2081
+ model: z2.string().min(1).optional(),
2082
+ thinkingLevel: z2.enum(VALID_THINKING_LEVELS).optional(),
2083
+ timeoutMinutes: z2.number().positive().max(1440).optional()
1896
2084
  });
1897
2085
  var sendChatMessageRequestSchema = z2.object({
1898
- messageId: z2.string().optional(),
1899
- submittedAt: z2.string().optional(),
1900
- message: z2.string(),
2086
+ messageId: z2.string().min(1).optional(),
2087
+ submittedAt: z2.string().datetime().optional(),
2088
+ message: z2.string().min(1),
1901
2089
  model: z2.string().optional(),
1902
2090
  customInstructions: z2.string().optional(),
1903
2091
  planMode: z2.boolean().optional(),
1904
2092
  images: z2.array(z2.object({
1905
2093
  type: z2.literal("image"),
1906
2094
  source: z2.discriminatedUnion("type", [
1907
- z2.object({ type: z2.literal("base64"), media_type: z2.enum(IMAGE_MEDIA_TYPES), data: z2.string() }),
1908
- z2.object({ type: z2.literal("url"), url: z2.string() })
2095
+ z2.object({ type: z2.literal("base64"), media_type: z2.enum(IMAGE_MEDIA_TYPES), data: z2.string().min(1) }),
2096
+ z2.object({ type: z2.literal("url"), url: z2.string().url() })
1909
2097
  ])
1910
2098
  })).optional(),
2099
+ canvasUploadIds: z2.array(z2.string().min(1)).optional(),
1911
2100
  thinkingLevel: z2.enum(VALID_THINKING_LEVELS).optional(),
1912
2101
  goalMode: z2.boolean().optional(),
1913
2102
  fastMode: z2.boolean().optional(),
1914
2103
  enableInteractiveTools: z2.boolean().optional(),
1915
- type: z2.string().optional(),
2104
+ type: z2.string().min(1).optional(),
1916
2105
  merge: z2.boolean().optional(),
1917
- idempotencyKey: z2.string().optional(),
2106
+ idempotencyKey: z2.string().min(1).max(128).optional(),
2107
+ automationId: z2.string().optional(),
2108
+ environmentId: z2.string().min(1).optional(),
1918
2109
  senderUserId: z2.string().optional(),
1919
2110
  senderEmail: z2.string().optional(),
1920
2111
  senderDisplayName: z2.string().optional(),
1921
2112
  senderAvatarUrl: z2.string().optional(),
1922
2113
  errorNotificationTarget: z2.discriminatedUnion("type", [
1923
2114
  z2.object({ type: z2.literal("slack") }),
1924
- z2.object({ type: z2.literal("linear"), sessionId: z2.string() }),
2115
+ z2.object({ type: z2.literal("linear"), sessionId: z2.string().min(1) }),
1925
2116
  z2.object({
1926
2117
  type: z2.literal("code_host"),
1927
2118
  provider: z2.enum(["github", "gitlab"]),
1928
2119
  resource: z2.enum(["issue", "pull_request"]),
1929
- repositoryId: z2.string(),
1930
- resourceNumber: z2.number()
2120
+ repositoryId: z2.string().min(1),
2121
+ resourceNumber: z2.number().int().positive()
1931
2122
  }),
1932
- z2.object({ type: z2.literal("automation"), executionId: z2.string() })
2123
+ z2.object({ type: z2.literal("automation"), executionId: z2.string().min(1) })
1933
2124
  ]).optional()
1934
2125
  }).passthrough();
1935
2126
 
@@ -1975,6 +2166,7 @@ function coerceBackgroundTaskPayload(payload) {
1975
2166
  prompt: optionalString(payload.prompt),
1976
2167
  lastToolName: optionalString(payload.last_tool_name),
1977
2168
  usage,
2169
+ ambient: optionalBoolean(payload.ambient),
1978
2170
  patch
1979
2171
  };
1980
2172
  }
@@ -2026,6 +2218,38 @@ function upsertDisplayMessage(messages, message) {
2026
2218
  if (index === -1) messages.push(message);
2027
2219
  else messages[index] = message;
2028
2220
  }
2221
+ function applyToolInputEvent(event, messages, provider, state, questionFilter) {
2222
+ if (event.type === "replicas-tool-input-request" && typeof event.payload.toolUseId === "string" && typeof event.payload.requestId === "string" && (Array.isArray(event.payload.options) || Array.isArray(event.payload.questions))) {
2223
+ const resolved = state.resolutions.get(event.payload.toolUseId);
2224
+ const inputRequest = {
2225
+ requestId: event.payload.requestId,
2226
+ status: resolved?.selectionId === "aborted" || resolved?.selectionId === "declined" ? "aborted" : resolved ? "resolved" : "pending",
2227
+ ...Array.isArray(event.payload.options) ? { options: event.payload.options } : {},
2228
+ ...Array.isArray(event.payload.questions) ? { questions: questionFilter ? event.payload.questions.filter(questionFilter) : event.payload.questions } : {},
2229
+ ...resolved ? { selectionId: resolved.selectionId } : {},
2230
+ ...resolved?.selectionSummary ? { selectionSummary: resolved.selectionSummary } : {}
2231
+ };
2232
+ state.requests.set(event.payload.toolUseId, inputRequest);
2233
+ const message = messages.find((candidate) => candidate.type === "tool_call" && candidate.id === `${provider}-tool-${event.payload.toolUseId}`);
2234
+ if (message) message.inputRequest = inputRequest;
2235
+ return true;
2236
+ }
2237
+ if (event.type === "replicas-tool-input-resolved" && typeof event.payload.toolUseId === "string" && typeof event.payload.selectionId === "string") {
2238
+ const resolved = {
2239
+ selectionId: event.payload.selectionId,
2240
+ ...typeof event.payload.selectionSummary === "string" ? { selectionSummary: event.payload.selectionSummary } : {}
2241
+ };
2242
+ state.resolutions.set(event.payload.toolUseId, resolved);
2243
+ const inputRequest = state.requests.get(event.payload.toolUseId);
2244
+ if (inputRequest) {
2245
+ inputRequest.status = resolved.selectionId === "aborted" || resolved.selectionId === "declined" ? "aborted" : "resolved";
2246
+ inputRequest.selectionId = resolved.selectionId;
2247
+ if (resolved.selectionSummary) inputRequest.selectionSummary = resolved.selectionSummary;
2248
+ }
2249
+ return true;
2250
+ }
2251
+ return false;
2252
+ }
2029
2253
  function parseSkillName(tool, input) {
2030
2254
  if (typeof tool !== "string") return null;
2031
2255
  const normalizedTool = tool.toLowerCase();
@@ -2585,7 +2809,7 @@ function parseCursorEvents(events) {
2585
2809
  const message = event.payload.message;
2586
2810
  if (typeof message === "string" && message.trim()) {
2587
2811
  messages.push({
2588
- id: `cursor-user-${event.timestamp}-${messages.length}`,
2812
+ id: getUserMessageId(event) ?? `cursor-user-${event.timestamp}-${messages.length}`,
2589
2813
  type: "user",
2590
2814
  content: message,
2591
2815
  timestamp: event.timestamp
@@ -2884,7 +3108,7 @@ function isProviderEvent(event, provider, suffix) {
2884
3108
  function parseAcpEvents(events, provider) {
2885
3109
  const messages = [];
2886
3110
  const activeChunks = {};
2887
- const resolvedInputs = /* @__PURE__ */ new Map();
3111
+ const inputState = { requests: /* @__PURE__ */ new Map(), resolutions: /* @__PURE__ */ new Map() };
2888
3112
  let chunkSequence = 0;
2889
3113
  for (const event of events) {
2890
3114
  const userContent = getUserMessage(event);
@@ -2918,37 +3142,7 @@ function parseAcpEvents(events, provider) {
2918
3142
  activeChunks.agent_thought_chunk = void 0;
2919
3143
  continue;
2920
3144
  }
2921
- if (event.type === "replicas-tool-input-request" && typeof event.payload.toolUseId === "string" && typeof event.payload.requestId === "string") {
2922
- const message = messages.find((candidate) => candidate.type === "tool_call" && candidate.id === `${provider}-tool-${event.payload.toolUseId}`);
2923
- if (message && (Array.isArray(event.payload.options) || Array.isArray(event.payload.questions))) {
2924
- const resolved = resolvedInputs.get(event.payload.toolUseId);
2925
- message.inputRequest = {
2926
- requestId: event.payload.requestId,
2927
- status: resolved?.selectionId === "aborted" ? "aborted" : resolved ? "resolved" : "pending",
2928
- ...Array.isArray(event.payload.options) ? { options: event.payload.options } : {},
2929
- ...Array.isArray(event.payload.questions) ? { questions: event.payload.questions } : {},
2930
- ...resolved ? { selectionId: resolved.selectionId } : {},
2931
- ...resolved?.selectionSummary ? { selectionSummary: resolved.selectionSummary } : {}
2932
- };
2933
- }
2934
- continue;
2935
- }
2936
- if (event.type === "replicas-tool-input-resolved" && typeof event.payload.toolUseId === "string" && typeof event.payload.selectionId === "string") {
2937
- const resolved = {
2938
- selectionId: event.payload.selectionId,
2939
- ...typeof event.payload.selectionSummary === "string" ? { selectionSummary: event.payload.selectionSummary } : {}
2940
- };
2941
- resolvedInputs.set(event.payload.toolUseId, resolved);
2942
- const message = messages.find((candidate) => candidate.type === "tool_call" && candidate.id === `${provider}-tool-${event.payload.toolUseId}`);
2943
- if (message?.inputRequest) {
2944
- message.inputRequest = {
2945
- ...message.inputRequest,
2946
- status: resolved.selectionId === "aborted" ? "aborted" : "resolved",
2947
- ...resolved
2948
- };
2949
- }
2950
- continue;
2951
- }
3145
+ if (applyToolInputEvent(event, messages, provider, inputState)) continue;
2952
3146
  if (!isProviderEvent(event, provider, "session-update") || !isRecord(event.payload.update)) continue;
2953
3147
  const update = event.payload.update;
2954
3148
  const kind = update.sessionUpdate;
@@ -3012,6 +3206,65 @@ function parseFxEvents(events) {
3012
3206
  return parseAcpEvents(events, "fx");
3013
3207
  }
3014
3208
 
3209
+ // ../shared/src/errors.ts
3210
+ var TRANSIENT_NETWORK_ERROR_PATTERNS = [
3211
+ /socket connection was closed unexpectedly/,
3212
+ /fetch failed/,
3213
+ /network error/,
3214
+ /network request failed/,
3215
+ /econnreset/,
3216
+ /econnrefused/,
3217
+ /etimedout/,
3218
+ /enotfound/,
3219
+ /eai_again/,
3220
+ /und_err_socket/,
3221
+ /und_err_connect_timeout/,
3222
+ /headers timeout/,
3223
+ /body timeout/,
3224
+ /connection reset/,
3225
+ /connection closed/,
3226
+ /closed unexpectedly/
3227
+ ];
3228
+ var HTTP_CONTEXT_PATTERN = /(api error|request|response|status|http)/;
3229
+ var HTTP_STATUS_PATTERN = /(^|[^0-9])([1-5][0-9]{2})(?=$|[^0-9])/g;
3230
+ function isTransientErrorText(text, options = {}) {
3231
+ const normalized = text.toLowerCase();
3232
+ if (!normalized) return false;
3233
+ if (TRANSIENT_NETWORK_ERROR_PATTERNS.some((pattern) => pattern.test(normalized))) return true;
3234
+ if (options.includeRateLimit && /rate limit/.test(normalized)) return true;
3235
+ if (options.extraPatterns?.some((pattern) => pattern.test(normalized))) return true;
3236
+ const httpStatuses = new Set(options.httpStatuses ?? []);
3237
+ const httpStatusClasses = new Set(options.httpStatusClasses ?? []);
3238
+ if (httpStatuses.size === 0 && httpStatusClasses.size === 0) return false;
3239
+ if (options.requireHttpContext !== false && !HTTP_CONTEXT_PATTERN.test(normalized)) return false;
3240
+ for (const match of normalized.matchAll(HTTP_STATUS_PATTERN)) {
3241
+ const status = Number(match[2]);
3242
+ if (httpStatuses.has(status) || httpStatusClasses.has(Math.floor(status / 100))) return true;
3243
+ }
3244
+ return false;
3245
+ }
3246
+
3247
+ // ../shared/src/display-message/parsers/inference-error.ts
3248
+ var TRANSIENT_HTTP_STATUSES = [408, 429, 500, 502, 503, 504, 529];
3249
+ var SERVER_ERROR_TEXT_PATTERN = /internal ?server ?error|server ?error|service unavailable|bad gateway|gateway timeout|overloaded|too many requests/i;
3250
+ var INFERENCE_ERROR_DETAIL_MAX = 600;
3251
+ var INFERENCE_ERROR_RETRY_HINT = "This is usually temporary \u2014 retry the message.";
3252
+ function formatInferenceError(message, context = {}) {
3253
+ const label = context.provider ? INFERENCE_PROVIDER_LABELS[context.provider] ?? context.provider : null;
3254
+ const source = label ? `${label}${context.model ? ` (${context.model})` : ""} request failed${typeof context.status === "number" ? ` with HTTP ${context.status}` : ""}` : null;
3255
+ let body = source ? message ? `${source}: ${message}` : `${source}.` : message;
3256
+ const detail = context.detail?.trim();
3257
+ if (detail && !body.includes(detail)) {
3258
+ body = `${body}
3259
+ ${detail.length > INFERENCE_ERROR_DETAIL_MAX ? `${detail.slice(0, INFERENCE_ERROR_DETAIL_MAX)}\u2026` : detail}`;
3260
+ }
3261
+ const sniffText = `${message}
3262
+ ${detail ?? ""}`;
3263
+ const transient = context.retryable ?? (typeof context.status === "number" ? TRANSIENT_HTTP_STATUSES.includes(context.status) || context.status >= 500 : isTransientErrorText(sniffText, { httpStatuses: TRANSIENT_HTTP_STATUSES, includeRateLimit: true }) || SERVER_ERROR_TEXT_PATTERN.test(sniffText));
3264
+ return transient ? `${body}
3265
+ ${INFERENCE_ERROR_RETRY_HINT}` : body;
3266
+ }
3267
+
3015
3268
  // ../shared/src/display-message/parsers/opencode-parser.ts
3016
3269
  function timestampFromMs(value, fallback) {
3017
3270
  return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : fallback;
@@ -3025,9 +3278,15 @@ function toolStatus(status) {
3025
3278
  function assistantError(info) {
3026
3279
  if (!isRecord(info) || !isRecord(info.error)) return null;
3027
3280
  const data = isRecord(info.error.data) ? info.error.data : null;
3028
- if (typeof data?.message === "string") return data.message;
3029
- if (typeof info.error.message === "string") return info.error.message;
3030
- return stringifyDisplayValue(info.error) ?? "Opencode run failed";
3281
+ const message = typeof data?.message === "string" ? data.message : typeof info.error.message === "string" ? info.error.message : stringifyDisplayValue(info.error) ?? "Opencode run failed";
3282
+ if (info.error.name === "MessageAbortedError") return message;
3283
+ return formatInferenceError(message, {
3284
+ ...typeof info.providerID === "string" ? { provider: info.providerID } : {},
3285
+ ...typeof info.modelID === "string" ? { model: info.modelID } : {},
3286
+ ...typeof data?.statusCode === "number" ? { status: data.statusCode } : {},
3287
+ ...typeof data?.isRetryable === "boolean" ? { retryable: data.isRetryable } : {},
3288
+ ...typeof data?.responseBody === "string" ? { detail: data.responseBody } : {}
3289
+ });
3031
3290
  }
3032
3291
  function parseOpencodeEvents(events) {
3033
3292
  const messages = [];
@@ -3062,7 +3321,9 @@ function parseOpencodeEvents(events) {
3062
3321
  messages.push({
3063
3322
  id: `opencode-${event.type}-${event.timestamp}`,
3064
3323
  type: "error",
3065
- message,
3324
+ message: formatInferenceError(message, {
3325
+ ...typeof event.payload.status === "number" ? { status: event.payload.status } : {}
3326
+ }),
3066
3327
  timestamp: event.timestamp
3067
3328
  });
3068
3329
  continue;
@@ -3147,6 +3408,108 @@ function parseOpencodeEvents(events) {
3147
3408
  return messages;
3148
3409
  }
3149
3410
 
3411
+ // ../shared/src/display-message/parsers/muse-parser.ts
3412
+ function isToolInputQuestion(value) {
3413
+ return isRecord(value) && typeof value.id === "string" && typeof value.prompt === "string" && typeof value.multiSelect === "boolean" && Array.isArray(value.options) && value.options.every((option) => isRecord(option) && typeof option.id === "string" && typeof option.label === "string");
3414
+ }
3415
+ function parseMuseEvents(events) {
3416
+ const messages = [];
3417
+ const deltas = /* @__PURE__ */ new Map();
3418
+ const inputState = { requests: /* @__PURE__ */ new Map(), resolutions: /* @__PURE__ */ new Map() };
3419
+ for (const event of events) {
3420
+ const userContent = getUserMessage(event);
3421
+ if (userContent !== null) {
3422
+ if (userContent.trim()) messages.push({
3423
+ id: getUserMessageId(event) ?? `muse-user-${event.timestamp}`,
3424
+ type: "user",
3425
+ content: userContent,
3426
+ timestamp: event.timestamp
3427
+ });
3428
+ continue;
3429
+ }
3430
+ if (event.type === "muse-error") {
3431
+ messages.push({
3432
+ id: `muse-error-${event.timestamp}`,
3433
+ type: "error",
3434
+ message: typeof event.payload.message === "string" ? event.payload.message : "Muse failed",
3435
+ timestamp: event.timestamp
3436
+ });
3437
+ continue;
3438
+ }
3439
+ if (event.type === "muse-turn-completed" && isRecord(event.payload.error)) {
3440
+ messages.push({
3441
+ id: `muse-turn-error-${typeof event.payload.turnId === "string" ? event.payload.turnId : event.timestamp}`,
3442
+ type: "error",
3443
+ message: typeof event.payload.error.message === "string" ? event.payload.error.message : "Muse turn failed",
3444
+ timestamp: event.timestamp
3445
+ });
3446
+ continue;
3447
+ }
3448
+ if (applyToolInputEvent(event, messages, "muse", inputState, isToolInputQuestion)) continue;
3449
+ if (event.type === "muse-item-delta" && typeof event.payload.itemId === "string" && typeof event.payload.delta === "string") {
3450
+ const field = typeof event.payload.field === "string" ? event.payload.field : "text";
3451
+ const key = `${event.payload.itemId}:${field}`;
3452
+ const content = `${deltas.get(key) ?? ""}${event.payload.delta}`;
3453
+ deltas.set(key, content);
3454
+ if (field === "text") upsertDisplayMessage(messages, {
3455
+ id: `muse-agent-${event.payload.itemId}`,
3456
+ type: "agent",
3457
+ content,
3458
+ timestamp: event.timestamp
3459
+ });
3460
+ if (field.startsWith("summary")) upsertDisplayMessage(messages, {
3461
+ id: `muse-reasoning-${event.payload.itemId}`,
3462
+ type: "reasoning",
3463
+ content,
3464
+ status: "in_progress",
3465
+ timestamp: event.timestamp
3466
+ });
3467
+ continue;
3468
+ }
3469
+ if (!["muse-item-started", "muse-item-updated", "muse-item-completed"].includes(event.type) || !isRecord(event.payload.item)) continue;
3470
+ const item = event.payload.item;
3471
+ if (typeof item.itemId !== "string" || typeof item.kind !== "string") continue;
3472
+ const terminal = item.status !== "inProgress";
3473
+ if (item.kind === "agentMessage" && typeof item.text === "string" && item.text) {
3474
+ upsertDisplayMessage(messages, {
3475
+ id: `muse-agent-${item.itemId}`,
3476
+ type: "agent",
3477
+ content: item.text,
3478
+ timestamp: typeof item.recordedAt === "string" ? item.recordedAt : event.timestamp
3479
+ });
3480
+ } else if (item.kind === "reasoning") {
3481
+ const content = Array.isArray(item.summary) ? item.summary.filter((part) => typeof part === "string").join("\n") : typeof item.text === "string" ? item.text : "";
3482
+ if (content) upsertDisplayMessage(messages, {
3483
+ id: `muse-reasoning-${item.itemId}`,
3484
+ type: "reasoning",
3485
+ content,
3486
+ status: terminal ? "completed" : "in_progress",
3487
+ timestamp: typeof item.recordedAt === "string" ? item.recordedAt : event.timestamp
3488
+ });
3489
+ } else if (item.kind === "toolCall") {
3490
+ let input = item.args;
3491
+ if (typeof item.args === "string") {
3492
+ try {
3493
+ input = JSON.parse(item.args);
3494
+ } catch {
3495
+ }
3496
+ }
3497
+ const message = createCallDisplayMessage({
3498
+ id: `muse-tool-${item.itemId}`,
3499
+ server: "muse",
3500
+ tool: typeof item.tool === "string" ? item.tool : "tool",
3501
+ input: isRecord(input) ? input : typeof input === "string" ? input : void 0,
3502
+ output: typeof item.visibleOutput === "string" ? item.visibleOutput : item.failureReason === void 0 ? void 0 : stringifyDisplayValue(item.failureReason),
3503
+ status: item.status === "failed" || item.status === "rejected" ? "failed" : terminal ? "completed" : "in_progress",
3504
+ timestamp: typeof item.recordedAt === "string" ? item.recordedAt : event.timestamp
3505
+ });
3506
+ if (message.type === "tool_call") message.inputRequest = inputState.requests.get(item.itemId);
3507
+ upsertDisplayMessage(messages, message);
3508
+ }
3509
+ }
3510
+ return messages;
3511
+ }
3512
+
3150
3513
  // ../shared/src/display-message/parsers/pi-parser.ts
3151
3514
  function nestedPayload(event) {
3152
3515
  return isRecord(event.payload.assistantMessageEvent) ? event.payload.assistantMessageEvent : event.payload;
@@ -3156,7 +3519,11 @@ function toolStatus2(value) {
3156
3519
  }
3157
3520
  function assistantErrorMessage(value) {
3158
3521
  if (!isRecord(value) || value.role !== "assistant" || value.stopReason !== "error") return null;
3159
- return typeof value.errorMessage === "string" && value.errorMessage ? value.errorMessage : "Pi run failed";
3522
+ const message = typeof value.errorMessage === "string" && value.errorMessage ? value.errorMessage : "Pi run failed";
3523
+ return formatInferenceError(message, {
3524
+ ...typeof value.provider === "string" ? { provider: value.provider } : {},
3525
+ ...typeof value.model === "string" ? { model: value.model } : {}
3526
+ });
3160
3527
  }
3161
3528
  function parsePiEvents(events) {
3162
3529
  const messages = [];
@@ -3165,7 +3532,7 @@ function parsePiEvents(events) {
3165
3532
  let activeMessageId = "assistant";
3166
3533
  for (const event of events) {
3167
3534
  if (event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string") {
3168
- messages.push({ id: `pi-user-${event.timestamp}-${messages.length}`, type: "user", content: event.payload.message, timestamp: event.timestamp });
3535
+ messages.push({ id: getUserMessageId(event) ?? `pi-user-${event.timestamp}-${messages.length}`, type: "user", content: event.payload.message, timestamp: event.timestamp });
3169
3536
  if (typeof event.payload.skillName === "string" && event.payload.skillName) {
3170
3537
  messages.push({
3171
3538
  id: `pi-skill-${event.timestamp}-${messages.length}`,
@@ -3178,7 +3545,7 @@ function parsePiEvents(events) {
3178
3545
  continue;
3179
3546
  }
3180
3547
  if (event.type === "pi-error") {
3181
- messages.push({ id: `pi-error-${event.timestamp}-${messages.length}`, type: "error", message: String(event.payload.message ?? "Pi run failed"), timestamp: event.timestamp });
3548
+ messages.push({ id: `pi-error-${event.timestamp}-${messages.length}`, type: "error", message: formatInferenceError(String(event.payload.message ?? "Pi run failed")), timestamp: event.timestamp });
3182
3549
  continue;
3183
3550
  }
3184
3551
  if (event.type === "pi-message_end") {
@@ -3266,6 +3633,7 @@ function parseClaudeEvents(events, parentToolUseId) {
3266
3633
  });
3267
3634
  const toolCallMap = /* @__PURE__ */ new Map();
3268
3635
  const taskMessageMap = /* @__PURE__ */ new Map();
3636
+ const ambientTaskIds = /* @__PURE__ */ new Set();
3269
3637
  const partialIndexes = /* @__PURE__ */ new Map();
3270
3638
  const completedStreamIds = /* @__PURE__ */ new Set();
3271
3639
  const supersededStreamIds = /* @__PURE__ */ new Set();
@@ -3274,7 +3642,12 @@ function parseClaudeEvents(events, parentToolUseId) {
3274
3642
  const assistantTextCounts = /* @__PURE__ */ new Map();
3275
3643
  const acceptedUserMessageIndexes = /* @__PURE__ */ new Set();
3276
3644
  const canonicalUserMessageIndexes = /* @__PURE__ */ new Set();
3277
- const findMatchingUserMessageIndex = (indexes, content, timestamp) => {
3645
+ const supersededUserMessageIndexes = /* @__PURE__ */ new Set();
3646
+ const findMatchingUserMessageIndex = (indexes, content, timestamp, messageId) => {
3647
+ if (typeof messageId === "string") {
3648
+ const byId = messages.findIndex((message, index) => message.type === "user" && message.id === messageId && !supersededUserMessageIndexes.has(index));
3649
+ if (byId !== -1) return byId;
3650
+ }
3278
3651
  return [...indexes].find((index) => {
3279
3652
  const message = messages[index];
3280
3653
  return message?.type === "user" && areUserMessagesWithinMatchWindow(message, { content, timestamp });
@@ -3292,7 +3665,7 @@ function parseClaudeEvents(events, parentToolUseId) {
3292
3665
  const images = userMessageImages(event.payload.images);
3293
3666
  if (content || images) {
3294
3667
  const messageId = event.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
3295
- const canonicalIndex = findMatchingUserMessageIndex(canonicalUserMessageIndexes, content, event.timestamp);
3668
+ const canonicalIndex = findMatchingUserMessageIndex(canonicalUserMessageIndexes, content, event.timestamp, messageId);
3296
3669
  const canonical = canonicalIndex === void 0 ? void 0 : messages[canonicalIndex];
3297
3670
  const message = {
3298
3671
  id: typeof messageId === "string" ? messageId : `user-${event.timestamp}`,
@@ -3367,14 +3740,16 @@ function parseClaudeEvents(events, parentToolUseId) {
3367
3740
  };
3368
3741
  }).filter((img) => img.data);
3369
3742
  if (textContent || images.length > 0) {
3743
+ const stampedId = event.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
3370
3744
  const acceptedIndex = findMatchingUserMessageIndex(
3371
3745
  acceptedUserMessageIndexes,
3372
3746
  textContent,
3373
- event.timestamp
3747
+ event.timestamp,
3748
+ stampedId
3374
3749
  );
3375
3750
  const accepted = acceptedIndex === void 0 ? void 0 : messages[acceptedIndex];
3376
3751
  const message = {
3377
- id: accepted?.id ?? `user-${event.timestamp}`,
3752
+ id: accepted?.id ?? (typeof stampedId === "string" ? stampedId : `user-${event.timestamp}`),
3378
3753
  type: "user",
3379
3754
  content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
3380
3755
  images: images.length > 0 ? images : accepted?.type === "user" ? accepted.images : void 0,
@@ -3382,9 +3757,12 @@ function parseClaudeEvents(events, parentToolUseId) {
3382
3757
  };
3383
3758
  if (acceptedIndex === void 0) {
3384
3759
  canonicalUserMessageIndexes.add(messages.push(message) - 1);
3760
+ } else if (acceptedUserMessageIndexes.has(acceptedIndex)) {
3761
+ supersededUserMessageIndexes.add(acceptedIndex);
3762
+ acceptedUserMessageIndexes.delete(acceptedIndex);
3763
+ canonicalUserMessageIndexes.add(messages.push(message) - 1);
3385
3764
  } else {
3386
3765
  messages[acceptedIndex] = message;
3387
- acceptedUserMessageIndexes.delete(acceptedIndex);
3388
3766
  }
3389
3767
  }
3390
3768
  }
@@ -3620,7 +3998,8 @@ function parseClaudeEvents(events, parentToolUseId) {
3620
3998
  }
3621
3999
  if (event.type === "claude-system") {
3622
4000
  const payload = coerceBackgroundTaskPayload(event.payload);
3623
- if (payload) {
4001
+ if (payload?.ambient) ambientTaskIds.add(payload.taskId);
4002
+ if (payload && !ambientTaskIds.has(payload.taskId)) {
3624
4003
  const existing = taskMessageMap.get(payload.taskId);
3625
4004
  const nextStatus = payload.subtype === "task_notification" ? normalizeBackgroundTaskStatus(payload.status) : payload.subtype === "task_updated" ? normalizeBackgroundTaskStatus(payload.patch?.status) : existing?.status ?? "in_progress";
3626
4005
  const nextMessage = {
@@ -3737,7 +4116,7 @@ function parseClaudeEvents(events, parentToolUseId) {
3737
4116
  message.targetProvider = target.subagentType;
3738
4117
  }
3739
4118
  }
3740
- const staleIndexes = /* @__PURE__ */ new Set();
4119
+ const staleIndexes = new Set(supersededUserMessageIndexes);
3741
4120
  for (const streamId of [...completedStreamIds, ...supersededStreamIds]) {
3742
4121
  const refs = partialIndexes.get(streamId);
3743
4122
  if (refs?.reasoning !== void 0) staleIndexes.add(refs.reasoning);
@@ -3767,6 +4146,9 @@ function parseAgentEvents(events, agentType) {
3767
4146
  if (agentType === "kimi") {
3768
4147
  return parseAcpEvents(events, "kimi");
3769
4148
  }
4149
+ if (agentType === "muse") {
4150
+ return parseMuseEvents(events);
4151
+ }
3770
4152
  if (agentType === "opencode") {
3771
4153
  return parseOpencodeEvents(events);
3772
4154
  }
@@ -3807,7 +4189,8 @@ var PLANS = {
3807
4189
  "Up to 15 environments",
3808
4190
  "Up to 5 automations",
3809
4191
  "Warm pools and warm hooks",
3810
- "API access"
4192
+ "API access",
4193
+ "Mobile app"
3811
4194
  ]
3812
4195
  },
3813
4196
  team: {
@@ -3857,8 +4240,7 @@ var WORKSPACE_STATUS_FILTERS = WORKSPACE_STATUSES.map((status) => status === "er
3857
4240
  var WORKSPACE_SORT_CHOICES = [
3858
4241
  ["manual", "Custom"],
3859
4242
  ["activity", "Last activity"],
3860
- ["created", "Created date"],
3861
- ["status", "Status"]
4243
+ ["created", "Created date"]
3862
4244
  ];
3863
4245
  var WORKSPACE_SORT_OPTIONS = WORKSPACE_SORT_CHOICES.map(([option]) => option);
3864
4246
  var WORKSPACE_SORT_LABELS = Object.fromEntries(WORKSPACE_SORT_CHOICES);
@@ -3878,7 +4260,7 @@ var DEFAULT_WORKSPACE_FILTERS = {
3878
4260
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
3879
4261
 
3880
4262
  // src/index.ts
3881
- var API_URL = process.env.REPLICAS_API_URL || "https://api.tryreplicas.com";
4263
+ var API_URL = process.env.REPLICAS_API_URL || "https://api.replicas.dev";
3882
4264
  var API_KEY = process.env.REPLICAS_API_KEY;
3883
4265
  if (!API_KEY) {
3884
4266
  console.error("REPLICAS_API_KEY environment variable required");
@@ -3910,9 +4292,17 @@ function result(data, isError = false) {
3910
4292
  }
3911
4293
  var server = new McpServer({
3912
4294
  name: "replicas-mcp",
3913
- version: "0.2.14"
4295
+ version: "0.2.16"
3914
4296
  });
3915
- var registerTool = server.registerTool.bind(server);
4297
+ function registerTool(name, config, handler) {
4298
+ server.registerTool.bind(server)(name, config, async (args) => {
4299
+ try {
4300
+ return result(await handler(args));
4301
+ } catch (error) {
4302
+ return result(error instanceof Error ? error.message : String(error), true);
4303
+ }
4304
+ });
4305
+ }
3916
4306
  var codingAgentSchema = z3.string().refine(isValidCodingAgentProvider, {
3917
4307
  message: `Coding agent must be ${getCodingAgentDisplayNames()}`
3918
4308
  });
@@ -3932,20 +4322,16 @@ registerTool(
3932
4322
  }
3933
4323
  },
3934
4324
  async (args) => {
3935
- try {
3936
- const body = {
3937
- name: args.name,
3938
- message: args.message
3939
- };
3940
- if (args.repository_ids) body.repository_ids = args.repository_ids;
3941
- if (args.repository_set_id) body.repository_set_id = args.repository_set_id;
3942
- if (args.coding_agent) body.coding_agent = args.coding_agent;
3943
- if (args.model) body.model = args.model;
3944
- if (args.lifecycle_policy) body.lifecycle_policy = args.lifecycle_policy;
3945
- return result(await api("POST", "/v1/replica", body));
3946
- } catch (e) {
3947
- return result(e instanceof Error ? e.message : String(e), true);
3948
- }
4325
+ const body = {
4326
+ name: args.name,
4327
+ message: args.message
4328
+ };
4329
+ if (args.repository_ids) body.repository_ids = args.repository_ids;
4330
+ if (args.repository_set_id) body.repository_set_id = args.repository_set_id;
4331
+ if (args.coding_agent) body.coding_agent = args.coding_agent;
4332
+ if (args.model) body.model = args.model;
4333
+ if (args.lifecycle_policy) body.lifecycle_policy = args.lifecycle_policy;
4334
+ return api("POST", "/v1/replica", body);
3949
4335
  }
3950
4336
  );
3951
4337
  registerTool(
@@ -3961,18 +4347,14 @@ registerTool(
3961
4347
  }
3962
4348
  },
3963
4349
  async (args) => {
3964
- try {
3965
- const params = new URLSearchParams();
3966
- if (args.page) params.set("page", String(args.page));
3967
- if (args.limit) params.set("limit", String(args.limit));
3968
- if (args.user_id) params.set("user_id", args.user_id);
3969
- if (args.source) params.set("source", args.source);
3970
- if (args.status) params.set("status", args.status);
3971
- const q = params.toString();
3972
- return result(await api("GET", `/v1/replica${q ? "?" + q : ""}`));
3973
- } catch (e) {
3974
- return result(e instanceof Error ? e.message : String(e), true);
3975
- }
4350
+ const params = new URLSearchParams();
4351
+ if (args.page) params.set("page", String(args.page));
4352
+ if (args.limit) params.set("limit", String(args.limit));
4353
+ if (args.user_id) params.set("user_id", args.user_id);
4354
+ if (args.source) params.set("source", args.source);
4355
+ if (args.status) params.set("status", args.status);
4356
+ const q = params.toString();
4357
+ return api("GET", `/v1/replica${q ? "?" + q : ""}`);
3976
4358
  }
3977
4359
  );
3978
4360
  registerTool(
@@ -3985,14 +4367,10 @@ registerTool(
3985
4367
  }
3986
4368
  },
3987
4369
  async (args) => {
3988
- try {
3989
- const params = new URLSearchParams();
3990
- if (args.include) params.set("include", args.include.join(","));
3991
- const q = params.toString();
3992
- return result(await api("GET", `/v1/replica/${args.id}${q ? "?" + q : ""}`));
3993
- } catch (e) {
3994
- return result(e instanceof Error ? e.message : String(e), true);
3995
- }
4370
+ const params = new URLSearchParams();
4371
+ if (args.include) params.set("include", args.include.join(","));
4372
+ const q = params.toString();
4373
+ return api("GET", `/v1/replica/${args.id}${q ? "?" + q : ""}`);
3996
4374
  }
3997
4375
  );
3998
4376
  registerTool(
@@ -4009,16 +4387,12 @@ registerTool(
4009
4387
  }
4010
4388
  },
4011
4389
  async (args) => {
4012
- try {
4013
- const body = { message: args.message };
4014
- if (args.coding_agent) body.coding_agent = args.coding_agent;
4015
- if (args.chat_id) body.chat_id = args.chat_id;
4016
- if (args.model) body.model = args.model;
4017
- if (args.plan_mode !== void 0) body.plan_mode = args.plan_mode;
4018
- return result(await api("POST", `/v1/replica/${args.id}/messages`, body));
4019
- } catch (e) {
4020
- return result(e instanceof Error ? e.message : String(e), true);
4021
- }
4390
+ const body = { message: args.message };
4391
+ if (args.coding_agent) body.coding_agent = args.coding_agent;
4392
+ if (args.chat_id) body.chat_id = args.chat_id;
4393
+ if (args.model) body.model = args.model;
4394
+ if (args.plan_mode !== void 0) body.plan_mode = args.plan_mode;
4395
+ return api("POST", `/v1/replica/${args.id}/messages`, body);
4022
4396
  }
4023
4397
  );
4024
4398
  registerTool(
@@ -4029,39 +4403,21 @@ registerTool(
4029
4403
  id: z3.string()
4030
4404
  }
4031
4405
  },
4032
- async (args) => {
4033
- try {
4034
- return result(await api("DELETE", `/v1/replica/${args.id}`));
4035
- } catch (e) {
4036
- return result(e instanceof Error ? e.message : String(e), true);
4037
- }
4038
- }
4406
+ (args) => api("DELETE", `/v1/replica/${args.id}`)
4039
4407
  );
4040
4408
  registerTool(
4041
4409
  "list_repositories",
4042
4410
  {
4043
4411
  description: "List available repositories that can be used when creating replicas"
4044
4412
  },
4045
- async () => {
4046
- try {
4047
- return result(await api("GET", "/v1/replica/repositories"));
4048
- } catch (e) {
4049
- return result(e instanceof Error ? e.message : String(e), true);
4050
- }
4051
- }
4413
+ () => api("GET", "/v1/replica/repositories")
4052
4414
  );
4053
4415
  registerTool(
4054
4416
  "list_repository_sets",
4055
4417
  {
4056
4418
  description: "List available repository sets (named groups of repositories)"
4057
4419
  },
4058
- async () => {
4059
- try {
4060
- return result(await api("GET", "/v1/replica/repository-sets"));
4061
- } catch (e) {
4062
- return result(e instanceof Error ? e.message : String(e), true);
4063
- }
4064
- }
4420
+ () => api("GET", "/v1/replica/repository-sets")
4065
4421
  );
4066
4422
  registerTool(
4067
4423
  "list_chats",
@@ -4071,13 +4427,7 @@ registerTool(
4071
4427
  id: z3.string().describe("Replica ID")
4072
4428
  }
4073
4429
  },
4074
- async (args) => {
4075
- try {
4076
- return result(await api("GET", `/v1/replica/${args.id}/chats`));
4077
- } catch (e) {
4078
- return result(e instanceof Error ? e.message : String(e), true);
4079
- }
4080
- }
4430
+ (args) => api("GET", `/v1/replica/${args.id}/chats`)
4081
4431
  );
4082
4432
  registerTool(
4083
4433
  "create_chat",
@@ -4090,13 +4440,9 @@ registerTool(
4090
4440
  }
4091
4441
  },
4092
4442
  async (args) => {
4093
- try {
4094
- const body = { provider: args.provider };
4095
- if (args.title) body.title = args.title;
4096
- return result(await api("POST", `/v1/replica/${args.id}/chats`, body));
4097
- } catch (e) {
4098
- return result(e instanceof Error ? e.message : String(e), true);
4099
- }
4443
+ const body = { provider: args.provider };
4444
+ if (args.title) body.title = args.title;
4445
+ return api("POST", `/v1/replica/${args.id}/chats`, body);
4100
4446
  }
4101
4447
  );
4102
4448
  registerTool(
@@ -4108,13 +4454,7 @@ registerTool(
4108
4454
  chat_id: z3.string().describe("Chat ID to delete")
4109
4455
  }
4110
4456
  },
4111
- async (args) => {
4112
- try {
4113
- return result(await api("DELETE", `/v1/replica/${args.id}/chats/${args.chat_id}`));
4114
- } catch (e) {
4115
- return result(e instanceof Error ? e.message : String(e), true);
4116
- }
4117
- }
4457
+ (args) => api("DELETE", `/v1/replica/${args.id}/chats/${args.chat_id}`)
4118
4458
  );
4119
4459
  registerTool(
4120
4460
  "read_replica_history",
@@ -4126,29 +4466,25 @@ registerTool(
4126
4466
  }
4127
4467
  },
4128
4468
  async (args) => {
4129
- try {
4130
- const params = new URLSearchParams();
4131
- if (args.chat_id) params.set("chat_id", args.chat_id);
4132
- const q = params.toString();
4133
- const response = await api("GET", `/v1/replica/${args.id}/history${q ? "?" + q : ""}`);
4134
- if (response.waking) {
4135
- return result({
4136
- status: "waking",
4137
- message: "Workspace is waking from sleep. Retry in 30-90 seconds."
4138
- });
4139
- }
4140
- const agentType = response.coding_agent || "claude";
4141
- const messages = parseAgentEvents(response.events, agentType);
4142
- return result({
4143
- thread_id: response.thread_id,
4144
- coding_agent: response.coding_agent,
4145
- total: response.total,
4146
- has_more: response.has_more,
4147
- messages
4148
- });
4149
- } catch (e) {
4150
- return result(e instanceof Error ? e.message : String(e), true);
4469
+ const params = new URLSearchParams();
4470
+ if (args.chat_id) params.set("chat_id", args.chat_id);
4471
+ const q = params.toString();
4472
+ const response = await api("GET", `/v1/replica/${args.id}/history${q ? "?" + q : ""}`);
4473
+ if (response.waking) {
4474
+ return {
4475
+ status: "waking",
4476
+ message: "Workspace is waking from sleep. Retry in 30-90 seconds."
4477
+ };
4151
4478
  }
4479
+ const agentType = response.coding_agent || "claude";
4480
+ const messages = parseAgentEvents(response.events, agentType);
4481
+ return {
4482
+ thread_id: response.thread_id,
4483
+ coding_agent: response.coding_agent,
4484
+ total: response.total,
4485
+ has_more: response.has_more,
4486
+ messages
4487
+ };
4152
4488
  }
4153
4489
  );
4154
4490
  registerTool(
@@ -4159,13 +4495,7 @@ registerTool(
4159
4495
  id: z3.string().describe("Replica ID")
4160
4496
  }
4161
4497
  },
4162
- async (args) => {
4163
- try {
4164
- return result(await api("GET", `/v1/replica/${args.id}/hooks`));
4165
- } catch (e) {
4166
- return result(e instanceof Error ? e.message : String(e), true);
4167
- }
4168
- }
4498
+ (args) => api("GET", `/v1/replica/${args.id}/hooks`)
4169
4499
  );
4170
4500
  var transport = new StdioServerTransport();
4171
4501
  await server.connect(transport);