replicas-engine 0.1.595 → 0.1.597

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/src/index.js CHANGED
@@ -17,8 +17,8 @@ async function raceWithTimeout(promise, ms) {
17
17
  try {
18
18
  return await Promise.race([
19
19
  promise,
20
- new Promise((resolve4) => {
21
- timer = setTimeout(() => resolve4(TIMEOUT), ms);
20
+ new Promise((resolve5) => {
21
+ timer = setTimeout(() => resolve5(TIMEOUT), ms);
22
22
  })
23
23
  ]);
24
24
  } finally {
@@ -1327,7 +1327,7 @@ var DOCKER_ABILITY = {
1327
1327
 
1328
1328
  // ../shared/src/default-skills/replicas-agent/abilities/github.ts
1329
1329
  var SECTION3 = `### GitHub
1330
- Use the pre-authenticated \`gh\` CLI for pull requests, issues, actions, and API calls.
1330
+ Use the pre-authenticated \`gh\` CLI for repository workflows and the workspace-scoped TypeScript SDK for GitHub API calls.
1331
1331
 
1332
1332
  **Reference:** \`references/GITHUB.md\`
1333
1333
 
@@ -1338,7 +1338,7 @@ Use this when:
1338
1338
  - You need to include images in PR descriptions`;
1339
1339
  var REFERENCE3 = `# GitHub Integration
1340
1340
 
1341
- This guide covers how to interact with GitHub from within your Replicas workspace.
1341
+ This guide covers how to interact with GitHub from within your Replicas workspace. Prefer \`@replicas/sdk\` for API calls so batching and data processing stay in one TypeScript program. The \`gh\` CLI remains useful for ergonomic pull request and git-adjacent workflows.
1342
1342
 
1343
1343
  ## Prerequisites
1344
1344
 
@@ -1423,17 +1423,21 @@ gh run rerun RUN_ID --failed
1423
1423
 
1424
1424
  ### GitHub API (Advanced)
1425
1425
 
1426
- For operations not covered by \`gh\` subcommands, use the API directly:
1426
+ For operations not covered by \`gh\` subcommands, use the scoped SDK. Pass only a relative GitHub API path:
1427
1427
 
1428
- \`\`\`bash
1429
- # GET request
1430
- gh api repos/owner/repo/pulls/123/comments
1428
+ \`\`\`ts
1429
+ import { replicas } from '@replicas/sdk';
1431
1430
 
1432
- # POST request
1433
- gh api repos/owner/repo/issues/123/comments -f body="Comment text"
1431
+ const comments = await replicas.github.request('/repos/owner/repo/pulls/123/comments');
1432
+ await replicas.github.request('/repos/owner/repo/issues/123/comments', {
1433
+ method: 'POST',
1434
+ body: { body: 'Comment text' },
1435
+ });
1434
1436
 
1435
- # GraphQL query
1436
- gh api graphql -f query='{ repository(owner: "owner", name: "repo") { issues(first: 10) { nodes { title number } } } }'
1437
+ const result = await replicas.github.request('/graphql', {
1438
+ method: 'POST',
1439
+ body: { query: '{ viewer { login } }' },
1440
+ });
1437
1441
  \`\`\`
1438
1442
 
1439
1443
  ### Working with PR Reviews
@@ -1463,7 +1467,7 @@ GitHub does NOT have a public API for uploading images to PRs/issues. When you n
1463
1467
  `;
1464
1468
  var GITHUB_ABILITY = {
1465
1469
  label: "GitHub",
1466
- description: "Pre-authenticated `gh` CLI for PRs, issues, releases, and GraphQL.",
1470
+ description: "GitHub workflows through gh and API calls through @replicas/sdk.",
1467
1471
  bullet: "- Interacting with GitHub (creating PRs, managing issues, using the API, etc.)",
1468
1472
  section: SECTION3,
1469
1473
  referenceFile: { name: "GITHUB.md", content: REFERENCE3 }
@@ -1471,124 +1475,61 @@ var GITHUB_ABILITY = {
1471
1475
 
1472
1476
  // ../shared/src/default-skills/replicas-agent/abilities/gitlab.ts
1473
1477
  var SECTION4 = `### GitLab
1474
- Workspace repos hosted on GitLab use pre-configured git credentials \u2014 push, pull, and open merge requests with plain \`git\`.
1478
+ Use plain git for repository traffic and the workspace-scoped TypeScript SDK for GitLab API operations.
1475
1479
 
1476
1480
  **Reference:** \`references/GITLAB.md\`
1477
1481
 
1478
1482
  Use this when:
1479
- - A repo's remote is on gitlab.com or a self-managed GitLab instance
1480
- - You need to open or update a merge request`;
1483
+ - A repository remote is on gitlab.com or a self-managed GitLab instance
1484
+ - You need to open or update a merge request
1485
+ - You need GitLab issues, pipelines, comments, or other API data`;
1481
1486
  var REFERENCE4 = `# GitLab Integration
1482
1487
 
1483
- This guide covers how to work with GitLab-hosted repositories from within your Replicas workspace.
1484
-
1485
- ## Prerequisites
1486
-
1487
- Git credentials for the workspace's GitLab hosts are pre-configured in \`~/.git-credentials\` and refreshed automatically. Plain \`git fetch\` / \`git pull\` / \`git push\` over HTTPS work with no additional setup.
1488
-
1489
- - Never ask the user for a GitLab token or PAT \u2014 credentials are already wired. If a push fails with an authentication error, report it to the user instead of working around it.
1490
- - A workspace can hold credentials for several GitLab hosts, and \`origin\` is whatever the repo says it is. Always select the token by the host you are about to call, as the snippets below do. Grabbing the first entry in \`~/.git-credentials\` can send one host's token to another.
1491
- - There is no \`glab\` CLI in the workspace, and \`gh\` only works for GitHub remotes. Check a repo's host with \`git remote get-url origin\` before choosing the GitHub or GitLab workflow.
1492
-
1493
- ### Reading the credential for a repo
1488
+ Git credentials are preconfigured for fetch, pull, and push. Use \`@replicas/sdk\` for API traffic; never inspect \`~/.git-credentials\`, request a token, or send a credential with curl.
1494
1489
 
1495
- A workspace can hold tokens for several GitLab hosts, so always ask git for the
1496
- credential belonging to the host you are about to call. Never grep
1497
- \`~/.git-credentials\` yourself: taking the first line sends one instance's token to
1498
- another, and interpolating a host into a grep pattern makes it a regular
1499
- expression. \`git credential fill\` matches the host exactly and returns nothing
1500
- when there is no credential for it:
1490
+ ## Merge requests
1501
1491
 
1502
- \`\`\`bash
1503
- HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
1504
- TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
1505
- [ -n "$TOKEN" ] || { echo "no credential for $HOST"; exit 1; }
1506
- \`\`\`
1507
-
1508
- Every recipe below repeats these two lines so each block runs on its own, and
1509
- every one calls \`https://$HOST\`, so the token only ever reaches the host it
1510
- belongs to. Run them inside the repo whose API you are calling. An empty
1511
- \`$TOKEN\` means the workspace has no credential for that host. Report that rather
1512
- than reaching for another one.
1513
-
1514
- ## Merge Requests
1515
-
1516
- Create a merge request directly from a push using push options:
1492
+ Create a merge request directly from a push:
1517
1493
 
1518
1494
  \`\`\`bash
1519
1495
  git push -o merge_request.create -o merge_request.target=<default-branch> -o merge_request.title="Title" origin HEAD
1520
1496
  \`\`\`
1521
1497
 
1522
- Useful options:
1498
+ Useful options are \`-o merge_request.draft\` and \`-o merge_request.remove_source_branch\`. GitLab prints the merge request URL.
1523
1499
 
1524
- \`\`\`bash
1525
- -o merge_request.draft # open as draft
1526
- -o merge_request.remove_source_branch # delete branch on merge
1527
- \`\`\`
1500
+ ## API requests
1528
1501
 
1529
- GitLab prints the MR URL in the push output \u2014 include it in your reply to the user.
1502
+ Pass a relative path below \`/api/v4\`. Replicas selects the credential only from GitLab hosts attached to the workspace repositories.
1530
1503
 
1531
- ### Setting the description
1504
+ \`\`\`ts
1505
+ import { replicas } from '@replicas/sdk';
1532
1506
 
1533
- Do **not** pass the description as a push option. Push option values are shell
1534
- arguments on one line, so a real description \u2014 headings, lists, the Replicas
1535
- footer HTML \u2014 gets mangled or truncated. Write the body to a file and PUT it as
1536
- JSON instead, which preserves it exactly:
1507
+ const project = encodeURIComponent('group/project');
1508
+ const mergeRequests = await replicas.gitlab.request(
1509
+ \`/projects/\${project}/merge_requests?state=opened\`,
1510
+ );
1537
1511
 
1538
- \`\`\`bash
1539
- HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
1540
- TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
1541
- PROJECT=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("group/project", safe=""))')
1542
- cat > /tmp/mr-body.md <<'EOF'
1543
- ## Summary
1544
- ...your full description, ending with the Replicas footer...
1545
- EOF
1546
- jq -Rs '{description: .}' /tmp/mr-body.md > /tmp/mr-body.json
1547
- curl -sS --request PUT \\
1548
- --header "Authorization: Bearer $TOKEN" \\
1549
- --header "Content-Type: application/json" \\
1550
- --data @/tmp/mr-body.json \\
1551
- "https://$HOST/api/v4/projects/$PROJECT/merge_requests/<iid>"
1512
+ await replicas.gitlab.request(\`/projects/\${project}/issues/42/notes\`, {
1513
+ method: 'POST',
1514
+ body: { body: 'Update from Replicas' },
1515
+ });
1552
1516
  \`\`\`
1553
1517
 
1554
- Use the same command to edit a description later. Editing replaces the whole
1555
- body, so re-include the Replicas footer every time \u2014 it is how merge requests
1556
- opened through Replicas are counted.
1557
-
1558
- ## GitLab API (advanced)
1559
-
1560
- For operations with no git equivalent (commenting on MRs, reading pipelines), call the REST API with the workspace credential:
1518
+ When a workspace contains repositories from more than one GitLab host, pass the exact repository host:
1561
1519
 
1562
- \`\`\`bash
1563
- HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
1564
- TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
1565
- curl -s -H "Authorization: Bearer $TOKEN" "https://$HOST/api/v4/projects/<url-encoded-path>/merge_requests"
1520
+ \`\`\`ts
1521
+ await replicas.gitlab.request('/projects/group%2Frepo/pipelines', {
1522
+ host: 'gitlab.example.com',
1523
+ });
1566
1524
  \`\`\`
1567
1525
 
1568
- This works for gitlab.com and self-managed instances alike, because the base URL
1569
- and the token both come from the repo's own host.
1570
-
1571
- ## Inline media in merge requests
1572
-
1573
- GitLab provides a supported project Markdown uploads API. Upload media to Replicas first, then upload the same bytes natively so repository permissions protect the asset:
1526
+ ## Descriptions and media
1574
1527
 
1575
- \`\`\`bash
1576
- FILE=/abs/path/to/screenshot.png
1577
- HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
1578
- PROJECT=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("group/project", safe=""))')
1579
- TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
1580
- UPLOAD=$(curl -sS --request POST \\
1581
- --header "Authorization: Bearer $TOKEN" \\
1582
- --form "file=@$FILE" \\
1583
- "https://$HOST/api/v4/projects/$PROJECT/uploads")
1584
- echo "$UPLOAD" | jq -r .markdown
1585
- \`\`\`
1586
-
1587
- Insert the returned \`markdown\` into the merge request description or comment and add the media's **View in Replicas** dashboard link. Images render inline; MP4, MOV, and WebM render as inline video players. Do not create a public Replicas forge share when this native upload succeeds.
1528
+ 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.
1588
1529
  `;
1589
1530
  var GITLAB_ABILITY = {
1590
1531
  label: "GitLab",
1591
- description: "Pre-configured git credentials for GitLab repos; merge requests via push options.",
1532
+ description: "Git for repository traffic and @replicas/sdk for the GitLab API.",
1592
1533
  bullet: "- Interacting with GitLab (pushing to GitLab repos, opening merge requests)",
1593
1534
  section: SECTION4,
1594
1535
  referenceFile: { name: "GITLAB.md", content: REFERENCE4 }
@@ -1596,332 +1537,67 @@ var GITLAB_ABILITY = {
1596
1537
 
1597
1538
  // ../shared/src/default-skills/replicas-agent/abilities/google.ts
1598
1539
  var SECTION5 = `### Google Workspace and Search Console
1599
- Create and edit Google Docs, Sheets, and Forms, and read Google Search Console data via the Replicas gateway. Drive access is limited to files created by Replicas.
1540
+ Create and edit Docs, Sheets, and Forms, manage Replicas-created Drive files, and read Search Console through the workspace-scoped TypeScript SDK.
1600
1541
 
1601
1542
  **Reference:** \`references/GOOGLE.md\`
1602
1543
 
1603
1544
  Use this when:
1604
1545
  - You need to create or edit a Google Doc, Sheet, or Form
1605
- - You need to share, rename, move, or delete a Replicas-created Google file
1606
- - You need to read responses from a Replicas-created Google Form
1607
- - You need to list Search Console properties, query search performance, read sitemaps, or inspect an indexed URL`;
1546
+ - You need to manage a file Replicas created
1547
+ - You need Google Search Console data`;
1608
1548
  var REFERENCE5 = `# Google Workspace and Search Console
1609
1549
 
1610
- This guide covers how to create and edit Google Docs, Sheets, and Forms, perform basic Drive file operations, and read Google Search Console data from inside a Replicas workspace using the monolith as a gateway to Google's APIs.
1611
-
1612
- ## Prerequisites
1613
-
1614
- The integration is configured at the org or user level by the Replicas admin. From inside a workspace you don't have a Google access token directly; instead you call the monolith's \`/v1/gdrive/*\` endpoints, authenticated with your workspace's engine secret. The monolith refreshes the org's (or user's) Google access token and proxies the call.
1615
-
1616
- Quick check that the integration is connected:
1617
-
1618
- \`\`\`bash
1619
- curl -s -X GET "$REPLICAS_MONOLITH_URL/v1/gdrive/credentials" \\
1620
- -H "Authorization: Bearer $REPLICAS_ENGINE_SECRET" \\
1621
- -H "X-Workspace-Id: $REPLICAS_WORKSPACE_ID"
1622
- \`\`\`
1623
-
1624
- - If \`hasCredentials\` is \`true\`: you're good to go.
1625
- - If \`hasCredentials\` is \`false\`: Google has not been connected for this org. Ask the user to go to **Settings \u2192 Integrations \u2192 Google** in the [Replicas dashboard](https://replicas.dev) and connect a Google account. Do not attempt Google operations until it's connected.
1626
-
1627
- Standard auth headers used by every call below:
1628
-
1629
- \`\`\`
1630
- Authorization: Bearer $REPLICAS_ENGINE_SECRET
1631
- X-Workspace-Id: $REPLICAS_WORKSPACE_ID
1632
- \`\`\`
1633
-
1634
- For brevity the examples below use a shell variable:
1635
-
1636
- \`\`\`bash
1637
- GDRIVE_AUTH=(-H "Authorization: Bearer $REPLICAS_ENGINE_SECRET" -H "X-Workspace-Id: $REPLICAS_WORKSPACE_ID")
1638
- \`\`\`
1639
-
1640
- ## Important constraint: drive.file scope
1641
-
1642
- The integration uses the **sensitive-tier \`drive.file\` scope**. That means Replicas can only read and edit Google files **it created itself**. It **cannot**:
1643
-
1644
- - Read or edit a user's pre-existing Google Docs, Sheets, or Forms \u2014 even ones that were shared with the connected Google account.
1645
- - List or search the user's broader Drive.
1646
- - Touch any file that was not created via these gateway endpoints.
1647
-
1648
- If the user asks you to edit an existing doc that Replicas didn't create, tell them this constraint and offer to create a new doc that mirrors what they want.
1649
-
1650
- ## Google Docs
1651
-
1652
- ### Create a new doc
1653
-
1654
- \`\`\`bash
1655
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/docs" "\${GDRIVE_AUTH[@]}" \\
1656
- -H "Content-Type: application/json" \\
1657
- -d '{"title":"Meeting notes 2026-05-14"}'
1658
- \`\`\`
1659
-
1660
- Returns the full Doc object; grab \`.documentId\` for follow-up calls.
1661
-
1662
- ### Read a doc
1663
-
1664
- \`\`\`bash
1665
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/docs/$DOC_ID" "\${GDRIVE_AUTH[@]}"
1666
- \`\`\`
1667
-
1668
- Returns the full document structure \u2014 \`body.content\` is an ordered list of structural elements (paragraphs, tables, etc.) with character indexes you can target for edits.
1669
-
1670
- ### Edit a doc (batchUpdate)
1671
-
1672
- The Docs API edits use a list of [structural requests](https://developers.google.com/workspace/docs/api/reference/rest/v1/documents/request). Insert text, then style it; or insert tables, images, page breaks, etc.
1673
-
1674
- \`\`\`bash
1675
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/docs/$DOC_ID/batchUpdate" "\${GDRIVE_AUTH[@]}" \\
1676
- -H "Content-Type: application/json" \\
1677
- -d '{
1678
- "requests": [
1679
- { "insertText": { "location": { "index": 1 }, "text": "Hello, world!\\n" } }
1680
- ]
1681
- }'
1682
- \`\`\`
1683
-
1684
- Common request types:
1685
-
1686
- - \`insertText\` \u2014 insert plain text at a given index
1687
- - \`deleteContentRange\` \u2014 delete a range
1688
- - \`replaceAllText\` \u2014 find-and-replace
1689
- - \`updateTextStyle\` \u2014 bold/italic/colors/fonts/sizes for a range
1690
- - \`updateParagraphStyle\` \u2014 headings (\`HEADING_1\`..\`HEADING_6\`), alignment, spacing
1691
- - \`createParagraphBullets\` \u2014 turn paragraphs into bulleted/numbered lists
1692
- - \`insertTable\` \u2014 insert a table
1693
- - \`insertInlineImage\` \u2014 insert an image from a URL
1694
-
1695
- Edits are verbose but powerful. Prefer batching many requests into a single \`batchUpdate\` call rather than making many round trips \u2014 it's faster and keeps the doc state consistent.
1696
-
1697
- ## Google Sheets
1698
-
1699
- ### Create a new spreadsheet
1700
-
1701
- \`\`\`bash
1702
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/sheets" "\${GDRIVE_AUTH[@]}" \\
1703
- -H "Content-Type: application/json" \\
1704
- -d '{"title":"Q2 metrics"}'
1705
- \`\`\`
1706
-
1707
- Returns the full Spreadsheet object; grab \`.spreadsheetId\`.
1550
+ Use \`@replicas/sdk\`; the workspace never receives Google OAuth credentials. Personal credentials are preferred for the workspace owner, then Replicas falls back to the organization connection.
1708
1551
 
1709
- ### Read a range of cells
1710
-
1711
- \`\`\`bash
1712
- # Range is in A1 notation, e.g. "Sheet1!A1:C10"
1713
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/sheets/$SHEET_ID/values/$(printf %s 'Sheet1!A1:C10' | jq -sRr @uri)" \\
1714
- "\${GDRIVE_AUTH[@]}"
1715
- \`\`\`
1716
-
1717
- ### Write a range of cells
1718
-
1719
- \`\`\`bash
1720
- curl -s -X PUT \\
1721
- "$REPLICAS_MONOLITH_URL/v1/gdrive/sheets/$SHEET_ID/values/$(printf %s 'Sheet1!A1:B2' | jq -sRr @uri)?valueInputOption=USER_ENTERED" \\
1722
- "\${GDRIVE_AUTH[@]}" \\
1723
- -H "Content-Type: application/json" \\
1724
- -d '{
1725
- "range": "Sheet1!A1:B2",
1726
- "majorDimension": "ROWS",
1727
- "values": [
1728
- ["Name", "Revenue"],
1729
- ["Q1", "=SUM(B3:B100)"]
1730
- ]
1731
- }'
1732
- \`\`\`
1733
-
1734
- \`valueInputOption=USER_ENTERED\` makes formulas evaluate as if a human typed them. Use \`RAW\` to write literal strings.
1735
-
1736
- ### Bulk operations (formatting, charts, new tabs, etc.)
1737
-
1738
- \`\`\`bash
1739
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/sheets/$SHEET_ID/batchUpdate" "\${GDRIVE_AUTH[@]}" \\
1740
- -H "Content-Type: application/json" \\
1741
- -d '{ "requests": [ { "addSheet": { "properties": { "title": "Raw data" } } } ] }'
1742
- \`\`\`
1743
-
1744
- \`batchUpdate\` accepts an array of [Sheets API requests](https://developers.google.com/workspace/sheets/api/reference/rest/v4/spreadsheets/request) \u2014 addSheet, updateSheetProperties, repeatCell, addChart, autoResizeDimensions, etc.
1745
-
1746
- ## Google Forms
1747
-
1748
- ### Create a new form
1749
-
1750
- \`\`\`bash
1751
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/forms" "\${GDRIVE_AUTH[@]}" \\
1752
- -H "Content-Type: application/json" \\
1753
- -d '{"title":"Customer feedback"}'
1754
- \`\`\`
1755
-
1756
- Returns the form. Grab \`.formId\`.
1757
-
1758
- ### Add questions / edit form structure
1759
-
1760
- The Forms API uses its own \`batchUpdate\`:
1761
-
1762
- \`\`\`bash
1763
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/forms/$FORM_ID/batchUpdate" "\${GDRIVE_AUTH[@]}" \\
1764
- -H "Content-Type: application/json" \\
1765
- -d '{
1766
- "requests": [
1767
- {
1768
- "createItem": {
1769
- "item": {
1770
- "title": "How likely are you to recommend us?",
1771
- "questionItem": {
1772
- "question": {
1773
- "required": true,
1774
- "scaleQuestion": { "low": 0, "high": 10, "lowLabel": "Not at all", "highLabel": "Very likely" }
1775
- }
1776
- }
1777
- },
1778
- "location": { "index": 0 }
1779
- }
1780
- }
1781
- ]
1782
- }'
1783
- \`\`\`
1784
-
1785
- See the [Forms API Request reference](https://developers.google.com/workspace/forms/api/reference/rest/v1/forms/request) for all supported request types \u2014 text questions, multiple choice, checkboxes, scale, grid, date, time, file upload, section breaks, branching logic, quiz settings, etc.
1786
-
1787
- ### Read responses
1788
-
1789
- \`\`\`bash
1790
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/forms/$FORM_ID/responses" "\${GDRIVE_AUTH[@]}"
1791
- \`\`\`
1792
-
1793
- Pagination: pass \`?pageToken=...&pageSize=...\` to walk through responses.
1794
-
1795
- ## Drive operations (only on Replicas-created files)
1796
-
1797
- ### Share a file with a person
1798
-
1799
- \`\`\`bash
1800
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID/permissions" "\${GDRIVE_AUTH[@]}" \\
1801
- -H "Content-Type: application/json" \\
1802
- -d '{
1803
- "type": "user",
1804
- "role": "writer",
1805
- "emailAddress": "alice@example.com",
1806
- "sendNotificationEmail": true
1807
- }'
1808
- \`\`\`
1809
-
1810
- Roles: \`reader\`, \`commenter\`, \`writer\`. Types: \`user\`, \`group\`, \`domain\`, \`anyone\`.
1811
-
1812
- ### Make a file viewable by anyone with the link
1813
-
1814
- \`\`\`bash
1815
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID/permissions" "\${GDRIVE_AUTH[@]}" \\
1816
- -H "Content-Type: application/json" \\
1817
- -d '{ "type": "anyone", "role": "reader" }'
1818
- \`\`\`
1819
-
1820
- ### List existing permissions
1821
-
1822
- \`\`\`bash
1823
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID/permissions" "\${GDRIVE_AUTH[@]}"
1824
- \`\`\`
1825
-
1826
- ### Rename / move a file
1827
-
1828
- \`\`\`bash
1829
- curl -s -X PATCH "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID" "\${GDRIVE_AUTH[@]}" \\
1830
- -H "Content-Type: application/json" \\
1831
- -d '{"name":"Final report.docx"}'
1832
- \`\`\`
1833
-
1834
- To move into a folder, also pass \`addParents\` and \`removeParents\` as query parameters per [Drive API docs](https://developers.google.com/workspace/drive/api/reference/rest/v3/files/update).
1835
-
1836
- ### Get file metadata
1837
-
1838
- \`\`\`bash
1839
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID?fields=id,name,mimeType,webViewLink,parents,modifiedTime" \\
1840
- "\${GDRIVE_AUTH[@]}"
1841
- \`\`\`
1842
-
1843
- \`webViewLink\` is the human-shareable URL that opens in Google Docs/Sheets/Forms.
1844
-
1845
- ### Delete a file
1846
-
1847
- \`\`\`bash
1848
- curl -s -X DELETE "$REPLICAS_MONOLITH_URL/v1/gdrive/files/$FILE_ID" "\${GDRIVE_AUTH[@]}"
1849
- \`\`\`
1850
-
1851
- ### List Replicas-created files
1552
+ \`\`\`ts
1553
+ import { replicas } from '@replicas/sdk';
1852
1554
 
1853
- \`\`\`bash
1854
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/files?pageSize=50" "\${GDRIVE_AUTH[@]}"
1555
+ const { native } = await replicas.integrations.list();
1556
+ if (!native.google) throw new Error('Connect Google in Replicas integrations first.');
1855
1557
  \`\`\`
1856
1558
 
1857
- Pass \`?q=...\` to filter using [Drive API search syntax](https://developers.google.com/workspace/drive/api/guides/search-files). Only files the app created or was granted access to will appear.
1559
+ Google Drive uses \`drive.file\`: Replicas can access only files it created, not pre-existing files in the account.
1858
1560
 
1859
- ## Google Search Console (read-only)
1561
+ ## Docs
1860
1562
 
1861
- Search Console access uses the \`webmasters.readonly\` scope. The connected Google account must have access to the requested property. If the account was connected before this scope was added, reconnect it in **Settings \u2192 Integrations \u2192 Google** first.
1862
-
1863
- Property identifiers must be URL-encoded as query parameters. URL-prefix properties include the trailing slash (for example, \`https://example.com/\`); Domain properties use \`sc-domain:example.com\`.
1864
-
1865
- ### List accessible properties
1866
-
1867
- \`\`\`bash
1868
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/search-console/sites" "\${GDRIVE_AUTH[@]}"
1869
- \`\`\`
1870
-
1871
- ### Get a property
1872
-
1873
- \`\`\`bash
1874
- SITE_URL=$(printf %s 'sc-domain:example.com' | jq -sRr @uri)
1875
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/search-console/site?siteUrl=$SITE_URL" "\${GDRIVE_AUTH[@]}"
1876
- \`\`\`
1877
-
1878
- ### Query search performance
1879
-
1880
- \`\`\`bash
1881
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/search-console/search-analytics/query?siteUrl=$SITE_URL" "\${GDRIVE_AUTH[@]}" \\
1882
- -H "Content-Type: application/json" \\
1883
- -d '{
1884
- "startDate": "2026-06-01",
1885
- "endDate": "2026-06-30",
1886
- "dimensions": ["query", "page"],
1887
- "rowLimit": 1000
1888
- }'
1563
+ \`\`\`ts
1564
+ const doc = await replicas.google.request<{ documentId: string }>('docs', {
1565
+ method: 'POST',
1566
+ body: { title: 'Meeting notes' },
1567
+ });
1568
+ await replicas.google.request(\`docs/\${doc.documentId}/batchUpdate\`, {
1569
+ method: 'POST',
1570
+ body: { requests: [{ insertText: { location: { index: 1 }, text: 'Hello\\n' } }] },
1571
+ });
1889
1572
  \`\`\`
1890
1573
 
1891
- Results contain clicks, impressions, CTR, and average position. Use \`startRow\` with \`rowLimit\` to paginate; Google returns at most 25,000 rows per request.
1892
-
1893
- ### List sitemaps
1574
+ ## Sheets
1894
1575
 
1895
- \`\`\`bash
1896
- curl -s "$REPLICAS_MONOLITH_URL/v1/gdrive/search-console/sitemaps?siteUrl=$SITE_URL" "\${GDRIVE_AUTH[@]}"
1576
+ \`\`\`ts
1577
+ const sheet = await replicas.google.request<{ spreadsheetId: string }>('sheets', {
1578
+ method: 'POST',
1579
+ body: { title: 'Q2 metrics' },
1580
+ });
1581
+ await replicas.google.request(
1582
+ \`sheets/\${sheet.spreadsheetId}/values/\${encodeURIComponent('Sheet1!A1:B2')}?valueInputOption=USER_ENTERED\`,
1583
+ { method: 'PUT', body: { values: [['Name', 'Revenue'], ['Q1', 100]] } },
1584
+ );
1897
1585
  \`\`\`
1898
1586
 
1899
- Pass a URL-encoded \`sitemapIndex\` query parameter to list sitemaps referenced by a sitemap index. To fetch one sitemap, call \`/v1/gdrive/search-console/sitemap\` with URL-encoded \`siteUrl\` and \`feedpath\` query parameters.
1900
-
1901
- ### Inspect an indexed URL
1902
-
1903
- \`\`\`bash
1904
- curl -s -X POST "$REPLICAS_MONOLITH_URL/v1/gdrive/search-console/url-inspection" "\${GDRIVE_AUTH[@]}" \\
1905
- -H "Content-Type: application/json" \\
1906
- -d '{
1907
- "inspectionUrl": "https://example.com/page",
1908
- "siteUrl": "sc-domain:example.com",
1909
- "languageCode": "en-US"
1910
- }'
1911
- \`\`\`
1587
+ ## Forms, Drive, and Search Console
1912
1588
 
1913
- URL Inspection reports the version currently in Google's index; it does not run a live URL test.
1589
+ The same method maps directly to the guarded \`/v1/gdrive/*\` routes:
1914
1590
 
1915
- ## Tips
1591
+ - \`forms\`, \`forms/:id\`, \`forms/:id/batchUpdate\`, \`forms/:id/responses\`
1592
+ - \`files\`, \`files/:id\`, \`files/:id/permissions\`
1593
+ - \`search-console/sites\`, \`search-console/search-analytics/query\`, \`search-console/sitemaps\`, \`search-console/url-inspection\`
1916
1594
 
1917
- - **Always return the \`webViewLink\`** to the user when you create or edit a file so they can open it. Get it from \`/v1/gdrive/files/$FILE_ID?fields=webViewLink\` or from \`documents.documentId\` \u2192 \`https://docs.google.com/document/d/$ID/edit\`.
1918
- - **Batch your edits.** A single \`batchUpdate\` with 20 requests beats 20 round-trips.
1919
- - **Watch error responses.** A 412 from the gateway means the org has no Google credentials connected \u2014 tell the user to connect Google in the dashboard. Other 4xx responses come straight from Google and usually explain the issue clearly.
1595
+ Batch independent reads with \`Promise.all\`; use each API's batch-update endpoint for dependent writes.
1920
1596
  `;
1921
1597
  var GOOGLE_ABILITY = {
1922
- label: "Google",
1923
- description: "Use Google Workspace and read Search Console data via the Replicas gateway.",
1924
- bullet: "- Interacting with Google Workspace or Search Console (Docs, Sheets, Forms, Drive files, search performance, sitemaps, and URL inspection)",
1598
+ label: "Google Workspace",
1599
+ description: "Use Docs, Sheets, Forms, Drive, and Search Console through @replicas/sdk.",
1600
+ bullet: "- Interacting with Google Workspace or Search Console",
1925
1601
  section: SECTION5,
1926
1602
  referenceFile: { name: "GOOGLE.md", content: REFERENCE5 }
1927
1603
  };
@@ -1955,102 +1631,63 @@ var LEARNINGS_ABILITY = {
1955
1631
 
1956
1632
  // ../shared/src/default-skills/replicas-agent/abilities/linear.ts
1957
1633
  var SECTION7 = `### Linear
1958
- Fetch issues, update state, add comments, and search via the Linear GraphQL API.
1634
+ Fetch issues, update state, add comments, and search through the workspace-scoped TypeScript SDK.
1959
1635
 
1960
1636
  **Reference:** \`references/LINEAR.md\`
1961
1637
 
1962
1638
  Use this when:
1963
1639
  - You encounter a Linear issue link and need to understand the task
1964
- - You need to update an issue's state (e.g. mark as done)
1965
- - You need to comment on or search for Linear issues`;
1640
+ - You need to update an issue's state or add a comment
1641
+ - You need to search Linear issues`;
1966
1642
  var REFERENCE7 = `# Linear Integration
1967
1643
 
1968
- This guide covers how to interact with Linear from within your Replicas workspace.
1969
-
1970
- ## Prerequisites
1971
-
1972
- Check that the workspace has the Replicas engine credentials needed to proxy Linear requests:
1973
-
1974
- \`\`\`bash
1975
- test -n "$REPLICAS_MONOLITH_URL" && test -n "$REPLICAS_ENGINE_SECRET" && test -n "$REPLICAS_WORKSPACE_ID" && echo set
1976
- \`\`\`
1977
-
1978
- - If **set**: Use the Replicas Linear proxy below. It refreshes Linear OAuth tokens for you.
1979
- - If **not set**: The workspace cannot authenticate to Replicas. Let the user know and do not attempt Linear operations.
1980
-
1981
- Then verify Linear is connected:
1982
-
1983
- \`\`\`bash
1984
- curl -fsS -X POST "$REPLICAS_MONOLITH_URL/v1/engine/linear/refresh-token" \\
1985
- -H "Authorization: Bearer $REPLICAS_ENGINE_SECRET" \\
1986
- -H "X-Workspace-Id: $REPLICAS_WORKSPACE_ID" \\
1987
- -H "Content-Type: application/json" \\
1988
- -d '{}' >/dev/null
1989
- \`\`\`
1990
-
1991
- If this fails because no Linear credential is connected, the user needs to connect Linear in the [Replicas dashboard](https://replicas.dev) under their organization's integration settings.
1992
-
1993
- ## Using the Linear API
1994
-
1995
- Linear uses a GraphQL API. Always call it through Replicas so the access token stays fresh:
1996
-
1997
- \`\`\`bash
1998
- linear_graphql() {
1999
- curl -sS -X POST "$REPLICAS_MONOLITH_URL/v1/engine/linear/graphql" \\
2000
- -H "Authorization: Bearer $REPLICAS_ENGINE_SECRET" \\
2001
- -H "X-Workspace-Id: $REPLICAS_WORKSPACE_ID" \\
2002
- -H "Content-Type: application/json" \\
2003
- --data-binary @-
2004
- }
2005
- \`\`\`
1644
+ Use \`@replicas/sdk\`; never request or handle a Linear OAuth token. Replicas refreshes the organization credential behind the gateway.
2006
1645
 
2007
- ### Fetching an Issue
1646
+ ## Check access
2008
1647
 
2009
- If you encounter a Linear issue link (e.g. \`https://linear.app/team/issue/ENG-123\`), the identifier is the last path segment (\`ENG-123\`).
2010
-
2011
- \`\`\`bash
2012
- jq -n --arg identifier "ENG-123" '{
2013
- query: "query($identifier: String!) { issue(id: $identifier) { id identifier title description state { name } assignee { name } parent { identifier title description } } }",
2014
- variables: { identifier: $identifier }
2015
- }' | linear_graphql
2016
- \`\`\`
2017
-
2018
- ### Updating Issue State
1648
+ \`\`\`ts
1649
+ import { replicas } from '@replicas/sdk';
2019
1650
 
2020
- \`\`\`bash
2021
- jq -n --arg issueId "ISSUE_UUID" --arg stateId "STATE_UUID" '{
2022
- query: "mutation($issueId: String!, $stateId: String!) { issueUpdate(id: $issueId, input: { stateId: $stateId }) { success issue { identifier state { name } } } }",
2023
- variables: { issueId: $issueId, stateId: $stateId }
2024
- }' | linear_graphql
1651
+ const { native } = await replicas.integrations.list();
1652
+ if (!native.linear) throw new Error('Connect Linear in Replicas organization integrations first.');
2025
1653
  \`\`\`
2026
1654
 
2027
- To find available states, query: \`query { workflowStates { nodes { id name } } }\`
2028
-
2029
- ### Adding a Comment
1655
+ ## Fetch an issue
2030
1656
 
2031
- \`\`\`bash
2032
- jq -n --arg issueId "ISSUE_UUID" --arg body "Your comment here" '{
2033
- query: "mutation($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success comment { id } } }",
2034
- variables: { issueId: $issueId, body: $body }
2035
- }' | linear_graphql
1657
+ \`\`\`ts
1658
+ const issue = await replicas.linear.graphql({
1659
+ query: \`query($identifier: String!) {
1660
+ issue(id: $identifier) {
1661
+ id identifier title description state { id name } assignee { name }
1662
+ parent { identifier title description }
1663
+ }
1664
+ }\`,
1665
+ variables: { identifier: 'ENG-123' },
1666
+ });
2036
1667
  \`\`\`
2037
1668
 
2038
- ### Searching Issues
1669
+ ## Update and comment in one script
2039
1670
 
2040
- \`\`\`bash
2041
- jq -n --arg query "search terms" '{
2042
- query: "query($query: String!) { issues(filter: { or: [{ title: { containsIgnoreCase: $query } }, { description: { containsIgnoreCase: $query } }] }, first: 10) { nodes { identifier title state { name } } } }",
2043
- variables: { query: $query }
2044
- }' | linear_graphql
1671
+ \`\`\`ts
1672
+ await replicas.linear.graphql({
1673
+ query: \`mutation($issueId: String!, $stateId: String!) {
1674
+ issueUpdate(id: $issueId, input: { stateId: $stateId }) { success }
1675
+ }\`,
1676
+ variables: { issueId, stateId },
1677
+ });
1678
+ await replicas.linear.graphql({
1679
+ query: \`mutation($issueId: String!, $body: String!) {
1680
+ commentCreate(input: { issueId: $issueId, body: $body }) { success comment { id } }
1681
+ }\`,
1682
+ variables: { issueId, body: 'Implemented in PR #123.' },
1683
+ });
2045
1684
  \`\`\`
2046
1685
 
2047
- ### Other Operations
2048
-
2049
- The Linear GraphQL API supports creating issues, managing projects, labels, cycles, and more. For the full schema and documentation, see: https://developers.linear.app/docs/graphql/working-with-the-graphql-api
1686
+ Search with \`containsIgnoreCase: $query\` filters, for example on both \`title\` and \`description\`. Batch independent queries with \`Promise.all\` to save round trips.
2050
1687
  `;
2051
1688
  var LINEAR_ABILITY = {
2052
1689
  label: "Linear",
2053
- description: "Fetch issues, post comments, update states via the Linear GraphQL API.",
1690
+ description: "Fetch issues, comment, and update state through @replicas/sdk.",
2054
1691
  bullet: "- Interacting with Linear (fetching issues, updating state, commenting, etc.)",
2055
1692
  section: SECTION7,
2056
1693
  referenceFile: { name: "LINEAR.md", content: REFERENCE7 }
@@ -2467,8 +2104,80 @@ var PREVIEWS_ABILITY = {
2467
2104
  referenceFile: { name: "PREVIEWS.md", content: REFERENCE9 }
2468
2105
  };
2469
2106
 
2107
+ // ../shared/src/default-skills/replicas-agent/abilities/plugins.ts
2108
+ var SECTION10 = `### Plugins
2109
+ Discover and execute installed marketplace tools through the workspace-scoped TypeScript SDK.
2110
+
2111
+ **Reference:** \`references/PLUGINS.md\`
2112
+
2113
+ Use this when:
2114
+ - The user asks for CRM data from Attio
2115
+ - The user asks for Google Ads, Docs, Drive, Search Console, or Sheets data
2116
+ - The user asks for ClickHouse analytics or PostHog product data
2117
+ - The user asks for Stripe customers, payments, invoices, subscriptions, or balances`;
2118
+ var REFERENCE10 = `# Plugins
2119
+
2120
+ 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. Composio credentials, sessions, and project keys are never exposed.
2121
+
2122
+ The initial plugin library is read-only. Tool discovery and execution both reject tools that are not tagged read-only.
2123
+
2124
+ ## Discover installed plugins
2125
+
2126
+ \`\`\`ts
2127
+ import { replicas } from '@replicas/sdk';
2128
+
2129
+ const plugins = await replicas.plugins.list();
2130
+ const installed = plugins.filter((plugin) => plugin.connected);
2131
+ \`\`\`
2132
+
2133
+ If a plugin is not connected, ask the user to install it under personal or organization Integrations in the Replicas dashboard.
2134
+
2135
+ ## Search, inspect, execute
2136
+
2137
+ \`\`\`ts
2138
+ const search = await replicas.plugins.search({
2139
+ plugin: 'stripe',
2140
+ query: 'list recent successful payments with customer details',
2141
+ });
2142
+
2143
+ const [tool] = search.tools;
2144
+ if (!tool) throw new Error('No matching read-only tool found');
2145
+
2146
+ const { data } = await replicas.plugins.execute({
2147
+ plugin: 'stripe',
2148
+ tool: tool.slug,
2149
+ arguments: { limit: 25 },
2150
+ });
2151
+ \`\`\`
2152
+
2153
+ Use \`replicas.plugins.describe({ plugin, tools: [slug] })\` when search returned a partial schema or before constructing unfamiliar arguments.
2154
+
2155
+ ## Batch work
2156
+
2157
+ Keep dependent operations in one TypeScript file and batch independent calls:
2158
+
2159
+ \`\`\`ts
2160
+ const results = await Promise.all(customerIds.map((customer) =>
2161
+ replicas.plugins.execute({
2162
+ plugin: 'stripe',
2163
+ tool: 'STRIPE_RETRIEVE_CUSTOMER',
2164
+ arguments: { customer },
2165
+ }),
2166
+ ));
2167
+ \`\`\`
2168
+
2169
+ Never attempt to import Composio, create a session, manage a connection, or call a provider with raw credentials. Those operations are intentionally unavailable inside the workspace.
2170
+ `;
2171
+ var PLUGINS_ABILITY = {
2172
+ label: "Plugins",
2173
+ description: "Use installed marketplace tools through @replicas/sdk.",
2174
+ bullet: "- Interacting with installed marketplace plugins through TypeScript",
2175
+ section: SECTION10,
2176
+ referenceFile: { name: "PLUGINS.md", content: REFERENCE10 }
2177
+ };
2178
+
2470
2179
  // ../shared/src/default-skills/replicas-agent/abilities/replicas.ts
2471
- var SECTION10 = `### Replicas (in-workspace CLI)
2180
+ var SECTION11 = `### Replicas (in-workspace CLI)
2472
2181
  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.
2473
2182
 
2474
2183
  **Reference:** \`references/REPLICAS.md\`
@@ -2479,7 +2188,7 @@ Use this when:
2479
2188
  - The user asks "what envs / repos / automations do I have?"
2480
2189
  - The user asks you to scaffold a \`replicas.json\` / \`replicas.yaml\` in a repo
2481
2190
  - You need to run a long-lived service (dev server, daemon) \u2014 always use \`replicas service start\` so it survives workspace sleep/wake`;
2482
- var REFERENCE10 = `# Replicas (in-workspace CLI)
2191
+ var REFERENCE11 = `# Replicas (in-workspace CLI)
2483
2192
 
2484
2193
  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.
2485
2194
 
@@ -2706,149 +2415,107 @@ var REPLICAS_ABILITY = {
2706
2415
  description: "Teach the agent about Replicas itself \u2014 automations, environments, the in-workspace CLI.",
2707
2416
  // No bullet — help_instructions covers the `replicas` CLI surface in detail.
2708
2417
  bullet: "",
2709
- section: SECTION10,
2710
- referenceFile: { name: "REPLICAS.md", content: REFERENCE10 },
2418
+ section: SECTION11,
2419
+ referenceFile: { name: "REPLICAS.md", content: REFERENCE11 },
2711
2420
  locked: true
2712
2421
  };
2713
2422
 
2714
- // ../shared/src/default-skills/replicas-agent/abilities/slack.ts
2715
- var SECTION11 = `### Slack
2716
- Send messages, read threads, search conversations, and upload files via the Slack Web API.
2423
+ // ../shared/src/default-skills/replicas-agent/abilities/sentry.ts
2424
+ var SECTION12 = `### Sentry
2425
+ Read issues and project data through the workspace-scoped TypeScript SDK.
2717
2426
 
2718
- **Reference:** \`references/SLACK.md\`
2427
+ **Reference:** \`references/SENTRY.md\`
2719
2428
 
2720
2429
  Use this when:
2721
- - You need to send a message to a Slack channel or thread
2722
- - You need to read or fetch a Slack conversation
2723
- - You encounter a Slack message link and need to retrieve its content
2724
- - The task asks you to notify, update, or communicate via Slack`;
2725
- var REFERENCE11 = `# Slack Integration
2430
+ - You need to inspect Sentry projects or issues
2431
+ - A user asks for error-monitoring context outside a webhook-triggered task`;
2432
+ var REFERENCE12 = `# Sentry Integration
2726
2433
 
2727
- This guide covers how to interact with Slack from within your Replicas workspace.
2434
+ Use \`@replicas/sdk\`; never request or read a Sentry token. Sentry access is organization-scoped and read-only.
2728
2435
 
2729
- ## Prerequisites
2436
+ \`\`\`ts
2437
+ import { replicas } from '@replicas/sdk';
2730
2438
 
2731
- Check if the namespaced or legacy Slack token environment variable is set:
2439
+ const { native } = await replicas.integrations.list();
2440
+ if (!native.sentry) throw new Error('Connect Sentry in Replicas organization integrations first.');
2732
2441
 
2733
- \`\`\`bash
2734
- if [ -n "\${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}" ]; then echo set; fi
2442
+ const issues = await replicas.sentry.request('/organizations/acme/issues/?query=is:unresolved');
2735
2443
  \`\`\`
2736
2444
 
2737
- - If **set**: Your workspace has Slack access. You can use the Slack Web API as described below.
2738
- - If **not set**: Slack has not been configured for this workspace. The user needs to connect Slack in the [Replicas dashboard](https://replicas.dev) under their organization's integration settings. Let the user know and do not attempt Slack operations.
2739
-
2740
- ## Using the Slack API
2741
-
2742
- All requests prefer \`$REPLICAS_SLACK_BOT_TOKEN\` and fall back to \`$SLACK_BOT_TOKEN\` for compatibility with older workspaces.
2743
-
2744
- ### Fetching a Thread from a Slack Link
2745
-
2746
- If you encounter a Slack message link (e.g. \`https://team.slack.com/archives/C0123ABC/p1234567890123456\`), extract the channel ID and thread timestamp:
2747
-
2748
- - **Channel ID**: The segment after \`/archives/\` (e.g. \`C0123ABC\`)
2749
- - **Thread TS**: The \`p\` value with a dot inserted before the last 6 digits (e.g. \`p1234567890123456\` -> \`1234567890.123456\`)
2445
+ Pass only a relative Sentry API path. The Replicas gateway selects the connected organization credential and rejects writes, absolute URLs, oversized responses, and long-running requests.
2446
+ `;
2447
+ var SENTRY_ABILITY = {
2448
+ label: "Sentry",
2449
+ description: "Read Sentry projects and issues through @replicas/sdk.",
2450
+ bullet: "- Interacting with Sentry (reading projects, issues, and error context)",
2451
+ section: SECTION12,
2452
+ referenceFile: { name: "SENTRY.md", content: REFERENCE12 }
2453
+ };
2750
2454
 
2751
- \`\`\`bash
2752
- curl -s "https://slack.com/api/conversations.replies?channel=CHANNEL_ID&ts=THREAD_TS" \\
2753
- -H "Authorization: Bearer \${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}"
2754
- \`\`\`
2455
+ // ../shared/src/default-skills/replicas-agent/abilities/slack.ts
2456
+ var SECTION13 = `### Slack
2457
+ Send messages, read threads, search conversations, and upload files through the workspace-scoped TypeScript SDK.
2755
2458
 
2756
- ### Sending a Message
2459
+ **Reference:** \`references/SLACK.md\`
2757
2460
 
2758
- \`\`\`bash
2759
- curl -s -X POST "https://slack.com/api/chat.postMessage" \\
2760
- -H "Authorization: Bearer \${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}" \\
2761
- -H "Content-Type: application/json" \\
2762
- -d '{
2763
- "channel": "CHANNEL_ID",
2764
- "text": "Your message here",
2765
- "thread_ts": "OPTIONAL_THREAD_TS"
2766
- }'
2767
- \`\`\`
2461
+ Use this when:
2462
+ - You need to send a message to a Slack channel or thread
2463
+ - You need to read or search Slack conversations
2464
+ - You encounter a Slack message link and need its full thread`;
2465
+ var REFERENCE13 = `# Slack Integration
2768
2466
 
2769
- Omit \`thread_ts\` to post a new message to the channel. Include it to reply in a thread.
2467
+ Use \`@replicas/sdk\`; never read or send a Slack token directly. The SDK calls Replicas, which selects only this workspace's organization connection.
2770
2468
 
2771
- #### Attach every new conversation
2469
+ ## Check access
2772
2470
 
2773
- Whenever you send a Slack message outside the thread that already routes to the current workspace, immediately attach the destination thread so future replies are delivered to this workspace:
2471
+ \`\`\`ts
2472
+ import { replicas } from '@replicas/sdk';
2774
2473
 
2775
- \`\`\`bash
2776
- replicas slack thread attach --channel <channel-id> --thread-ts <thread-root-ts>
2474
+ const { native } = await replicas.integrations.list();
2475
+ if (!native.slack) throw new Error('Connect Slack in Replicas organization integrations first.');
2777
2476
  \`\`\`
2778
2477
 
2779
- - For a new top-level message or DM, use the \`channel\` and \`ts\` from the successful \`chat.postMessage\` response. That message's \`ts\` is the new thread root.
2780
- - For a reply in another existing thread, use its root \`thread_ts\`, not the reply's \`ts\`.
2781
- - Do not attach the originating thread again when it already routes to the current workspace.
2782
- - Treat sending and attaching as one operation: check that both API calls succeed, and report an attachment failure instead of implying future replies will reach you.
2783
-
2784
- #### Switch a thread to an existing workspace
2478
+ ## Read a thread
2785
2479
 
2786
- When the user wants a Slack thread to continue in a particular existing workspace, resolve the target and switch the thread:
2480
+ For a link such as \`https://team.slack.com/archives/C0123ABC/p1234567890123456\`, the channel is \`C0123ABC\` and the timestamp is \`1234567890.123456\`.
2787
2481
 
2788
- \`\`\`bash
2789
- replicas list
2790
- replicas slack thread switch <workspace-id-or-name> --channel <channel-id> --thread-ts <thread-root-ts>
2482
+ \`\`\`ts
2483
+ const thread = await replicas.slack.call('conversations.replies', {
2484
+ channel: 'C0123ABC',
2485
+ ts: '1234567890.123456',
2486
+ limit: 200,
2487
+ });
2791
2488
  \`\`\`
2792
2489
 
2793
- The channel and thread flags default to \`REPLICAS_SLACK_CHANNEL_ID\` and \`REPLICAS_SLACK_THREAD_TS\` for a Slack-triggered workspace. Workspace-authenticated agents may target only their current workspace; switching to a different workspace requires running the command with user authentication outside agent mode. If that restriction applies, give the user the exact command to run rather than claiming the switch succeeded.
2794
-
2795
- ### Searching Messages
2490
+ ## Send and attach a message
2796
2491
 
2797
- \`\`\`bash
2798
- curl -s "https://slack.com/api/search.messages?query=YOUR_SEARCH_QUERY" \\
2799
- -H "Authorization: Bearer \${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}"
2492
+ \`\`\`ts
2493
+ const message = await replicas.slack.call<{ channel: string; ts: string }>('chat.postMessage', {
2494
+ channel: 'C0123ABC',
2495
+ text: 'Update from Replicas',
2496
+ });
2497
+ await replicas.slack.attachThread({ channel: message.channel, threadTs: message.ts });
2800
2498
  \`\`\`
2801
2499
 
2802
- ### Uploading Files
2803
-
2804
- Slack deprecated \`files.upload\`; the raw HTTP replacement is a 3-step flow. (\`files.uploadV2\` is **only** an SDK convenience wrapper and is NOT a real Web API HTTP endpoint \u2014 \`POST https://slack.com/api/files.uploadV2\` returns \`unknown_method\`.)
2805
-
2806
- \`\`\`bash
2807
- FILE=/path/to/file
2808
- FILENAME=$(basename "$FILE")
2809
- LENGTH=$(stat -c%s "$FILE" 2>/dev/null || stat -f%z "$FILE")
2500
+ 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.
2810
2501
 
2811
- # 1. Reserve an upload URL + file ID.
2812
- RESERVE=$(curl -s -G "https://slack.com/api/files.getUploadURLExternal" \\
2813
- -H "Authorization: Bearer \${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}" \\
2814
- --data-urlencode "filename=$FILENAME" \\
2815
- --data-urlencode "length=$LENGTH")
2816
- UPLOAD_URL=$(echo "$RESERVE" | jq -r '.upload_url')
2817
- FILE_ID=$(echo "$RESERVE" | jq -r '.file_id')
2818
-
2819
- # 2. POST the raw bytes to the reserved URL.
2820
- # Do not use -F / multipart here \u2014 the presigned URL expects the bytes
2821
- # directly in the request body.
2822
- curl -s -X POST "$UPLOAD_URL" \\
2823
- -H "Content-Type: application/octet-stream" \\
2824
- --data-binary @"$FILE"
2502
+ ## Search and batch
2825
2503
 
2826
- # 3. Complete the upload and share into a channel (and optionally a thread).
2827
- curl -s -X POST "https://slack.com/api/files.completeUploadExternal" \\
2828
- -H "Authorization: Bearer \${REPLICAS_SLACK_BOT_TOKEN:-$SLACK_BOT_TOKEN}" \\
2829
- -H "Content-Type: application/json; charset=utf-8" \\
2830
- -d "{
2831
- \\"files\\": [{\\"id\\": \\"$FILE_ID\\", \\"title\\": \\"File title\\"}],
2832
- \\"channel_id\\": \\"CHANNEL_ID\\",
2833
- \\"thread_ts\\": \\"OPTIONAL_THREAD_TS\\",
2834
- \\"initial_comment\\": \\"Optional message body\\"
2835
- }"
2504
+ \`\`\`ts
2505
+ const [messages, users] = await Promise.all([
2506
+ replicas.slack.call('search.messages', { query: 'launch status' }),
2507
+ replicas.slack.call('users.list', { limit: 200 }),
2508
+ ]);
2836
2509
  \`\`\`
2837
2510
 
2838
- Omit \`thread_ts\` and/or \`initial_comment\` if you don't need them.
2839
-
2840
- ### Other Operations
2841
-
2842
- You can list channels, read channel history, add reactions, and perform any other operation supported by the Slack Web API using the same authentication pattern.
2843
-
2844
- For full API documentation, see: https://docs.slack.dev/apis/web-api/
2511
+ Supported methods cover conversations, messages, search, users, reactions, and Slack's external file-upload flow. An unsupported method is rejected by Replicas before Slack is called.
2845
2512
  `;
2846
2513
  var SLACK_ABILITY = {
2847
2514
  label: "Slack",
2848
- description: "Send messages, read threads, search conversations, upload files.",
2515
+ description: "Send messages, read threads, and search through @replicas/sdk.",
2849
2516
  bullet: "- Interacting with Slack (sending messages, reading threads, etc.)",
2850
- section: SECTION11,
2851
- referenceFile: { name: "SLACK.md", content: REFERENCE11 }
2517
+ section: SECTION13,
2518
+ referenceFile: { name: "SLACK.md", content: REFERENCE13 }
2852
2519
  };
2853
2520
 
2854
2521
  // ../shared/src/default-skills/replicas-agent/registry.ts
@@ -2862,7 +2529,9 @@ var REPLICAS_AGENT_ABILITY_REGISTRY = {
2862
2529
  learnings: LEARNINGS_ABILITY,
2863
2530
  linear: LINEAR_ABILITY,
2864
2531
  media: MEDIA_ABILITY,
2532
+ plugins: PLUGINS_ABILITY,
2865
2533
  previews: PREVIEWS_ABILITY,
2534
+ sentry: SENTRY_ABILITY,
2866
2535
  slack: SLACK_ABILITY
2867
2536
  };
2868
2537
  var REPLICAS_AGENT_ABILITIES = Object.keys(
@@ -5832,7 +5501,7 @@ var BaseRefreshManager = class {
5832
5501
  this.health.lastErrorMessage = message;
5833
5502
  if (attempt < 3) {
5834
5503
  console.warn(`[${this.managerName}] Initial refresh attempt ${attempt} failed, retrying in 2s...`);
5835
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
5504
+ await new Promise((resolve5) => setTimeout(resolve5, 2e3));
5836
5505
  } else {
5837
5506
  console.error(`[${this.managerName}] Initial refresh failed after 3 attempts:`, error);
5838
5507
  }
@@ -6371,7 +6040,7 @@ var GitService = class {
6371
6040
  }
6372
6041
  }
6373
6042
  readUntrackedDiff(repoPath, paths) {
6374
- return new Promise((resolve4) => {
6043
+ return new Promise((resolve5) => {
6375
6044
  const child = spawn("git", ["diff", "--", ...paths], {
6376
6045
  cwd: repoPath,
6377
6046
  stdio: ["ignore", "pipe", "pipe"]
@@ -6392,12 +6061,12 @@ var GitService = class {
6392
6061
  });
6393
6062
  child.stderr.on("data", () => {
6394
6063
  });
6395
- child.on("error", () => resolve4(""));
6064
+ child.on("error", () => resolve5(""));
6396
6065
  child.on("close", (code) => {
6397
6066
  if (code === 0 || code === 1 || truncated) {
6398
- resolve4(Buffer.concat(chunks).toString("utf-8"));
6067
+ resolve5(Buffer.concat(chunks).toString("utf-8"));
6399
6068
  } else {
6400
- resolve4("");
6069
+ resolve5("");
6401
6070
  }
6402
6071
  });
6403
6072
  });
@@ -7134,9 +6803,9 @@ var StreamWriter = class {
7134
6803
  return true;
7135
6804
  }
7136
6805
  flush(signal) {
7137
- return new Promise((resolve4, reject) => {
6806
+ return new Promise((resolve5, reject) => {
7138
6807
  if (!this.stream) {
7139
- resolve4();
6808
+ resolve5();
7140
6809
  return;
7141
6810
  }
7142
6811
  if (this.flushTimer) {
@@ -7153,7 +6822,7 @@ var StreamWriter = class {
7153
6822
  s.write("", (error) => {
7154
6823
  signal?.removeEventListener("abort", onAbort);
7155
6824
  if (error) reject(error);
7156
- else resolve4();
6825
+ else resolve5();
7157
6826
  });
7158
6827
  });
7159
6828
  }
@@ -7691,7 +7360,7 @@ var ReplicasConfigService = class {
7691
7360
  await this.logToFile(`[${params.repoName}] --- Running: ${params.label} ---`);
7692
7361
  emit(`$ ${params.label}
7693
7362
  `);
7694
- return new Promise((resolve4) => {
7363
+ return new Promise((resolve5) => {
7695
7364
  const proc = spawn2("bash", ["-lc", params.command], {
7696
7365
  cwd: params.cwd,
7697
7366
  env: process.env,
@@ -7702,7 +7371,7 @@ var ReplicasConfigService = class {
7702
7371
  const finish = (result) => {
7703
7372
  if (settled) return;
7704
7373
  settled = true;
7705
- resolve4(result);
7374
+ resolve5(result);
7706
7375
  };
7707
7376
  const timer = setTimeout(() => {
7708
7377
  timedOut = true;
@@ -8128,7 +7797,7 @@ async function registerDesktopPreview() {
8128
7797
  if (await attemptRegistration()) {
8129
7798
  return;
8130
7799
  }
8131
- await new Promise((resolve4) => setTimeout(resolve4, RETRY_DELAY_MS));
7800
+ await new Promise((resolve5) => setTimeout(resolve5, RETRY_DELAY_MS));
8132
7801
  }
8133
7802
  console.error(
8134
7803
  `[DesktopPreview] Gave up registering port ${DESKTOP_STREAM_PORT} after ${REGISTRATION_TIMEOUT_MS}ms`
@@ -9669,8 +9338,8 @@ var PromptStream = class {
9669
9338
  if (this.closed) {
9670
9339
  return Promise.resolve({ value: void 0, done: true });
9671
9340
  }
9672
- return new Promise((resolve4) => {
9673
- this.waiters.push(resolve4);
9341
+ return new Promise((resolve5) => {
9342
+ this.waiters.push(resolve5);
9674
9343
  });
9675
9344
  }
9676
9345
  };
@@ -10050,14 +9719,14 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10050
9719
  const requestId = randomUUID4();
10051
9720
  const toolUseId = options.toolUseID;
10052
9721
  const { options: requestOptions, questions: requestQuestions } = handler.getRequest(input);
10053
- const result = await new Promise((resolve4) => {
9722
+ const result = await new Promise((resolve5) => {
10054
9723
  this.pendingToolInputs.set(requestId, {
10055
9724
  requestId,
10056
9725
  toolUseId,
10057
9726
  toolName,
10058
9727
  input,
10059
9728
  handler,
10060
- resolve: resolve4
9729
+ resolve: resolve5
10061
9730
  });
10062
9731
  const onAbort = () => {
10063
9732
  const pending = this.pendingToolInputs.get(requestId);
@@ -10162,7 +9831,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10162
9831
  error
10163
9832
  );
10164
9833
  await this.tearDownSession();
10165
- await new Promise((resolve4) => setTimeout(resolve4, delayMs));
9834
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
10166
9835
  currentRequest = { ...request, message: CLAUDE_MIDTURN_CONTINUE_PROMPT, images: [] };
10167
9836
  attempt++;
10168
9837
  continue;
@@ -10175,7 +9844,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10175
9844
  error
10176
9845
  );
10177
9846
  await this.tearDownSession();
10178
- await new Promise((resolve4) => setTimeout(resolve4, delayMs));
9847
+ await new Promise((resolve5) => setTimeout(resolve5, delayMs));
10179
9848
  attempt++;
10180
9849
  continue;
10181
9850
  }
@@ -10321,8 +9990,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
10321
9990
  this.pendingInterrupt = false;
10322
9991
  return;
10323
9992
  }
10324
- await new Promise((resolve4, reject) => {
10325
- this.pendingTurn = { resolve: resolve4, reject, sawActivity: false };
9993
+ await new Promise((resolve5, reject) => {
9994
+ this.pendingTurn = { resolve: resolve5, reject, sawActivity: false };
10326
9995
  promptStream.push(userMessage);
10327
9996
  });
10328
9997
  }
@@ -10909,13 +10578,13 @@ var AspClient = class {
10909
10578
  }
10910
10579
  const id = this.nextId;
10911
10580
  this.nextId += 1;
10912
- const promise = new Promise((resolve4, reject) => {
10581
+ const promise = new Promise((resolve5, reject) => {
10913
10582
  const timeoutMs = opts?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
10914
10583
  const timer = timeoutMs > 0 ? setTimeout(() => {
10915
10584
  this.pending.delete(id);
10916
10585
  reject(new Error(`ASP request timed out for ${method}`));
10917
10586
  }, timeoutMs) : null;
10918
- this.pending.set(id, { resolve: resolve4, reject, method, timer });
10587
+ this.pending.set(id, { resolve: resolve5, reject, method, timer });
10919
10588
  });
10920
10589
  this.write({ method, id, params });
10921
10590
  return promise;
@@ -11068,7 +10737,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
11068
10737
  var MIN_CODEX_CLI_VERSION = "0.144.6";
11069
10738
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
11070
10739
  var codexCliVersionEnsured = null;
11071
- var ENGINE_PACKAGE_VERSION = "0.1.595";
10740
+ var ENGINE_PACKAGE_VERSION = "0.1.597";
11072
10741
  var INITIALIZE_METHOD = "initialize";
11073
10742
  var INITIALIZED_NOTIFICATION = "initialized";
11074
10743
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -11188,13 +10857,13 @@ var AppServerProcess = class {
11188
10857
  if (!child || child.killed) {
11189
10858
  return;
11190
10859
  }
11191
- await new Promise((resolve4) => {
10860
+ await new Promise((resolve5) => {
11192
10861
  const timer = setTimeout(() => {
11193
10862
  child.kill("SIGKILL");
11194
10863
  }, 2e3);
11195
10864
  child.once("exit", () => {
11196
10865
  clearTimeout(timer);
11197
- resolve4();
10866
+ resolve5();
11198
10867
  });
11199
10868
  child.kill("SIGTERM");
11200
10869
  });
@@ -11239,8 +10908,8 @@ var AppServerProcess = class {
11239
10908
  }
11240
10909
  this.shuttingDown = true;
11241
10910
  child.kill("SIGKILL");
11242
- await new Promise((resolve4) => {
11243
- child.once("exit", () => resolve4());
10911
+ await new Promise((resolve5) => {
10912
+ child.once("exit", () => resolve5());
11244
10913
  });
11245
10914
  }
11246
10915
  };
@@ -12520,8 +12189,8 @@ var CodexAspManager = class extends CodingAgentManager {
12520
12189
  }
12521
12190
  async observeTurn(host, threadId, request, startTurn, options = {}) {
12522
12191
  let resolveCompleted;
12523
- const completed = new Promise((resolve4) => {
12524
- resolveCompleted = resolve4;
12192
+ const completed = new Promise((resolve5) => {
12193
+ resolveCompleted = resolve5;
12525
12194
  });
12526
12195
  let rejectDisposed = () => {
12527
12196
  };
@@ -13487,8 +13156,8 @@ var CursorManager = class extends CodingAgentManager {
13487
13156
  }
13488
13157
  async processMessageInternal(request) {
13489
13158
  let run = null;
13490
- const aborted = new Promise((resolve4) => {
13491
- this.abortActiveTurn = () => resolve4(TURN_ABORTED);
13159
+ const aborted = new Promise((resolve5) => {
13160
+ this.abortActiveTurn = () => resolve5(TURN_ABORTED);
13492
13161
  });
13493
13162
  const linearSessionId = ENGINE_ENV.REPLICAS_LINEAR_SESSION_ID;
13494
13163
  const linearForwarder = new LinearEventForwarder(linearSessionId);
@@ -14129,8 +13798,8 @@ var OpencodeManager = class extends CodingAgentManager {
14129
13798
  this.configuredModels = this.providerId === OPENCODE_GO_PROVIDER ? /* @__PURE__ */ new Set() : new Set(getConfiguredOpencodeModels(model, providerId));
14130
13799
  const eventController = new AbortController();
14131
13800
  this.eventAbortController = eventController;
14132
- this.eventSubscriptionReady = new Promise((resolve4) => {
14133
- this.resolveEventSubscriptionReady = resolve4;
13801
+ this.eventSubscriptionReady = new Promise((resolve5) => {
13802
+ this.resolveEventSubscriptionReady = resolve5;
14134
13803
  });
14135
13804
  this.subscribeToEvents(client, eventController).catch((error) => {
14136
13805
  this.resolveEventSubscriptionReady?.();
@@ -14140,7 +13809,7 @@ var OpencodeManager = class extends CodingAgentManager {
14140
13809
  });
14141
13810
  await Promise.race([
14142
13811
  this.eventSubscriptionReady,
14143
- new Promise((resolve4) => setTimeout(resolve4, 2e3))
13812
+ new Promise((resolve5) => setTimeout(resolve5, 2e3))
14144
13813
  ]);
14145
13814
  return client;
14146
13815
  }
@@ -15762,7 +15431,7 @@ function isReconcileCanvasItemsResponse(value) {
15762
15431
  }
15763
15432
  async function withCanvasUploadSlot(operation) {
15764
15433
  if (activeCanvasUploads >= MAX_CONCURRENT_CANVAS_UPLOADS) {
15765
- await new Promise((resolve4) => pendingCanvasUploads.push(resolve4));
15434
+ await new Promise((resolve5) => pendingCanvasUploads.push(resolve5));
15766
15435
  } else {
15767
15436
  activeCanvasUploads++;
15768
15437
  }
@@ -15897,7 +15566,7 @@ var HISTORY_DIRS = [
15897
15566
  join25(ENGINE_DIR2, "codex-histories")
15898
15567
  ];
15899
15568
  async function putTranscript(uploadUrl, filePath, size) {
15900
- await new Promise((resolve4, reject) => {
15569
+ await new Promise((resolve5, reject) => {
15901
15570
  const url = new URL(uploadUrl);
15902
15571
  const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
15903
15572
  method: "PUT",
@@ -15913,7 +15582,7 @@ async function putTranscript(uploadUrl, filePath, size) {
15913
15582
  });
15914
15583
  response.on("end", () => {
15915
15584
  const status = response.statusCode ?? 0;
15916
- if (status >= 200 && status < 300) resolve4();
15585
+ if (status >= 200 && status < 300) resolve5();
15917
15586
  else reject(new Error(`upload failed: ${status} ${body}`));
15918
15587
  });
15919
15588
  response.on("error", reject);
@@ -16120,13 +15789,13 @@ function runBeforeDeadline(operation, deadline) {
16120
15789
  const timeoutMs = deadline - Date.now();
16121
15790
  if (timeoutMs <= 0) return Promise.reject(new DOMException("engine log flush deadline exceeded", "TimeoutError"));
16122
15791
  const signal = AbortSignal.timeout(timeoutMs);
16123
- return new Promise((resolve4, reject) => {
15792
+ return new Promise((resolve5, reject) => {
16124
15793
  const onAbort = () => reject(signal.reason);
16125
15794
  signal.addEventListener("abort", onAbort, { once: true });
16126
15795
  operation(signal).then(
16127
15796
  (value) => {
16128
15797
  signal.removeEventListener("abort", onAbort);
16129
- resolve4(value);
15798
+ resolve5(value);
16130
15799
  },
16131
15800
  (error) => {
16132
15801
  signal.removeEventListener("abort", onAbort);
@@ -17155,7 +16824,7 @@ function scoreMatch(query2, filePath) {
17155
16824
  return 0;
17156
16825
  }
17157
16826
  function execGitAsync(args, cwd, timeoutMs) {
17158
- return new Promise((resolve4, reject) => {
16827
+ return new Promise((resolve5, reject) => {
17159
16828
  const child = execFile2("git", args, {
17160
16829
  cwd,
17161
16830
  encoding: "utf-8",
@@ -17165,7 +16834,7 @@ function execGitAsync(args, cwd, timeoutMs) {
17165
16834
  if (error) {
17166
16835
  reject(error);
17167
16836
  } else {
17168
- resolve4(stdout);
16837
+ resolve5(stdout);
17169
16838
  }
17170
16839
  });
17171
16840
  child.stdin?.end();
@@ -17557,7 +17226,7 @@ async function executeHookScriptStreaming(params) {
17557
17226
  let bufferExceeded = false;
17558
17227
  params.onChunk(`$ ${params.label}
17559
17228
  `);
17560
- return new Promise((resolve4) => {
17229
+ return new Promise((resolve5) => {
17561
17230
  const proc = spawn4("bash", ["-lc", params.content], {
17562
17231
  cwd: params.cwd,
17563
17232
  env: process.env,
@@ -17590,7 +17259,7 @@ async function executeHookScriptStreaming(params) {
17590
17259
  proc.on("close", (code) => {
17591
17260
  clearTimeout(timer);
17592
17261
  const output = [`$ ${params.label}`, ...outputParts].join("");
17593
- resolve4({
17262
+ resolve5({
17594
17263
  exitCode: bufferExceeded ? 1 : code ?? 1,
17595
17264
  output,
17596
17265
  timedOut: killed && !bufferExceeded
@@ -17600,7 +17269,7 @@ async function executeHookScriptStreaming(params) {
17600
17269
  clearTimeout(timer);
17601
17270
  const output = [`$ ${params.label}
17602
17271
  `, ...outputParts, err.message].join("");
17603
- resolve4({
17272
+ resolve5({
17604
17273
  exitCode: 1,
17605
17274
  output,
17606
17275
  timedOut: false
@@ -18873,14 +18542,30 @@ var HeartbeatService = class _HeartbeatService {
18873
18542
  };
18874
18543
  var heartbeatService = new HeartbeatService();
18875
18544
 
18545
+ // src/services/workspace-sdk-service.ts
18546
+ import { cp, mkdir as mkdir17 } from "fs/promises";
18547
+ import { dirname as dirname8, resolve as resolve4 } from "path";
18548
+ import { fileURLToPath as fileURLToPath2 } from "url";
18549
+ async function installWorkspaceSdk() {
18550
+ const source = resolve4(dirname8(fileURLToPath2(import.meta.url)), "../../workspace-sdk");
18551
+ const targets = [
18552
+ resolve4(ENGINE_ENV.HOME_DIR, "node_modules/@replicas/sdk"),
18553
+ "/tmp/node_modules/@replicas/sdk"
18554
+ ];
18555
+ await Promise.all(targets.map(async (target) => {
18556
+ await mkdir17(target, { recursive: true });
18557
+ await cp(source, target, { recursive: true, force: true });
18558
+ }));
18559
+ }
18560
+
18876
18561
  // src/index.ts
18877
18562
  var startupStartedAt = performance.now();
18878
18563
  var startupTimings = {};
18879
18564
  var ENGINE_INIT_GATE_TIMEOUT_MS = 35 * 60 * 1e3;
18880
18565
  var resolveEngineReady = () => {
18881
18566
  };
18882
- var engineReadyPromise = new Promise((resolve4) => {
18883
- resolveEngineReady = resolve4;
18567
+ var engineReadyPromise = new Promise((resolve5) => {
18568
+ resolveEngineReady = resolve5;
18884
18569
  });
18885
18570
  async function timeStartupStep(name, fn) {
18886
18571
  const startedAt = performance.now();
@@ -18900,7 +18585,7 @@ async function waitForInitializationGate() {
18900
18585
  if (Date.now() >= deadline) {
18901
18586
  throw new Error("Timed out waiting for deferred engine initialization gate");
18902
18587
  }
18903
- await new Promise((resolve4) => setTimeout(resolve4, 25));
18588
+ await new Promise((resolve5) => setTimeout(resolve5, 25));
18904
18589
  }
18905
18590
  }
18906
18591
  await timeStartupStep("engine_logger_initialize", () => engineLogger.initialize());
@@ -18932,11 +18617,11 @@ async function checkActiveSSHSessions() {
18932
18617
  }
18933
18618
  }
18934
18619
  function checkDesktopReady() {
18935
- return new Promise((resolve4) => {
18620
+ return new Promise((resolve5) => {
18936
18621
  const socket = connect({ host: "127.0.0.1", port: DESKTOP_STREAM_PORT });
18937
18622
  const done = (ready) => {
18938
18623
  socket.destroy();
18939
- resolve4(ready);
18624
+ resolve5(ready);
18940
18625
  };
18941
18626
  socket.once("connect", () => done(true));
18942
18627
  socket.once("error", () => done(false));
@@ -18968,7 +18653,7 @@ app.get("/health", async (c) => {
18968
18653
  if (!engineReady && waitMs > 0) {
18969
18654
  await Promise.race([
18970
18655
  engineReadyPromise,
18971
- new Promise((resolve4) => setTimeout(resolve4, waitMs))
18656
+ new Promise((resolve5) => setTimeout(resolve5, waitMs))
18972
18657
  ]);
18973
18658
  }
18974
18659
  c.header(ENGINE_HEALTH_WAIT_HEADER, "1");
@@ -19168,6 +18853,7 @@ serve(
19168
18853
  console.log(`Replicas Engine running on port ${info.port}`);
19169
18854
  }
19170
18855
  await timeStartupStep("wait_for_initialization_gate", waitForInitializationGate);
18856
+ await timeStartupStep("workspace_sdk_install", installWorkspaceSdk);
19171
18857
  if (!IS_WARMING_MODE) {
19172
18858
  await timeStartupStep("replicas_config_initialize", () => replicasConfigService.initialize());
19173
18859
  }