Skip to content

feat(sdk): re-export github primitive from root entry#823

Merged
khaliqgant merged 12 commits intomainfrom
primitive
May 9, 2026
Merged

feat(sdk): re-export github primitive from root entry#823
khaliqgant merged 12 commits intomainfrom
primitive

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

Summary

  • Re-exports @agent-relay/github-primitive (already a bundled dep) from the root @agent-relay/sdk so workflow authors don't need the /github subpath.
  • Adds two new import shapes alongside the existing subpath:
    • import { github } from '@agent-relay/sdk' — namespaced full surface, no collision risk
    • import { createGitHubStep, GitHubClient } from '@agent-relay/sdk' — curated helpers for the common workflow path
  • Deliberately avoids a flat export * from the root: the primitive ships ~40 generic-named action helpers (createFile, readFile, getUser, errorMessage, …) that would pollute the root namespace.

Marked as an early PR for visibility — happy to iterate on which symbols (if any) belong unprefixed at the root.

Branch contents

This branch (primitive) also contains pre-existing unrelated commits ahead of main:

  • 75f4d4f2 spec(slack-primitive): design for workflow ↔ human messaging
  • 24d1180c trajectories cleanup
  • c5a3ad27 merge from spec/slack-primitive

The github export is b2f4cbb8. Let me know if the slack-primitive spec / trajectories commits should be split into their own PRs.

Test plan

  • npm run build in packages/sdk is clean (no name-collision errors)
  • Built dist/index.js and dist/index.d.ts contain both export * as github and the curated createGitHubStep/GitHubClient re-exports
  • Smoke-test in a downstream workflow: import { createGitHubStep } from '@agent-relay/sdk' resolves and runs

🤖 Generated with Claude Code

khaliqgant and others added 4 commits May 5, 2026 16:10
…l + cloud adapters

Mirrors the github-primitive's adapter shape so the same workflow file
runs locally (gh CLI / Slack token) and in cloud (Nango). Two
flagship verbs:

  - postMessage — fire-and-forget human notification (PR opened,
    workflow done, etc.). Resolves @-mentions and #channel names at
    step time.
  - askQuestion — block the workflow on a human reply. Configurable
    timeout, replyMatch (regex/choice/any), allowedReplyFrom, and
    Block Kit interactive forms. Resumable across sandbox restarts
    via run-record metadata so retries don't re-ask.

Captures the cultural change the primitive enables: agents should ask
for clarification when blocked rather than guess. Includes two
recipes ("Announce + Done", "Ask Before You Guess") that the
writing-agent-relay-workflows skill will pick up when v1 ships.

Cloud-runtime auth reuses the workspace's existing Nango Slack
connection — no new SST resource bindings. Slack tokens don't rotate
per-call, so the proxy form (nango.proxy({ endpoint: '/chat.postMessage' }))
is the right shape — avoids the get-token-vs-proxy confusion the
github-primitive had to work through.

Phasing:
  A — postMessage + resolvers
  B — askQuestion (blocking, resumable)
  C — Block Kit interactive forms + utility verbs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exposes the bundled `@agent-relay/github-primitive` from the root
`@agent-relay/sdk` so workflow authors no longer need the subpath
import. Two new shapes added alongside the existing `/github` subpath:

- `import { github } from '@agent-relay/sdk'` — full namespaced surface,
  no collision risk with other root exports.
- `import { createGitHubStep, GitHubClient } from '@agent-relay/sdk'` —
  curated helpers for the common workflow-author path.

Avoided a flat `export *` because the primitive ships ~40 generic-named
action helpers (createFile, readFile, getUser, errorMessage, ...) that
would pollute the root namespace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@khaliqgant khaliqgant requested a review from willwashburn as a code owner May 8, 2026 13:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a Slack primitive package implementation (types, actions, runtimes, adapter, client, workflow-step, tests, examples, configs), finalizes multiple trajectory artifacts with portable project/path normalization and retrospectives, appends a compacted trajectory summary for Apr 10–May 8, and updates Slack design and implementation specs.

Changes

Trajectory Management

Layer / File(s) Summary
Path Normalization
.trajectories/completed/2026-04/*, .trajectories/completed/2026-05/*
Absolute projectId values converted to <repo-root> across many trajectory JSON files.
Evidence Normalization
.trajectories/completed/*
Completion-evidence/finding/channelPosts entries normalized to use <repo-root> instead of absolute local paths.
Trajectory Completion
.trajectories/completed/2026-05/*.json, .trajectories/completed/2026-05/*.md
Selected trajectories marked completed with completedAt, chapter endedAt timestamps, and appended retrospective objects; associated markdown docs added.
Compacted Batch
.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
New aggregated artifact covering 2026-04-10 → 2026-05-08 with decisionGroups, keyFindings, narrative, decisions, conventions, lessons, and openQuestions.
Index Update
.trajectories/index.json
Multiple path entries normalized from absolute to relative .trajectories/completed/... paths while preserving compactedInto.

Slack Primitive Implementation

Layer / File(s) Summary
Types & Errors
packages/slack-primitive/src/types.ts
Defines Slack runtime model, action enum, runtime/detection interfaces, SlackPostBackError class, API-like shapes, and SlackClientInterface.
Core Actions
packages/slack-primitive/src/actions/post-message.ts, resolve-channel.ts, resolve-user.ts
postMessage resolves channel and mentions (soft-fail warnings), prepends resolved mentions to text, and posts via chat.postMessage; resolveChannel paginates conversations.list to map name→id; resolveUser resolves id/email/handle with caching and users.list pagination.
Adapter & Factory
packages/slack-primitive/src/adapter.ts
BaseSlackAdapter standardizes execution timing/result shape; SlackAdapterFactory detects and instantiates runtimes (local/cloud/noop) and reports availability.
Runtimes
local-runtime.ts, cloud-relay-runtime.ts, noop-runtime.ts
Local runtime uses @slack/web-api WebClient and requires SLACK_BOT_TOKEN; cloud-relay runtime proxies postMessage to cloud endpoint and maps errors (user/channel resolution unsupported); noop runtime returns deterministic noop outputs with warnings.
Client & Exports
packages/slack-primitive/src/client.ts, src/index.ts
SlackClient high-level wrapper delegates to adapters; package barrel re-exports types, adapter, runtimes, client, workflow-step, and action modules.
Workflow Integration
packages/slack-primitive/src/workflow-step.ts
SlackStepExecutor validates and coerces step config, merges runtime/env, renders {{steps.X.output.*}} templates, executes postMessage via SlackClient, and formats output across modes (data/result/summary/raw/none) with optional JSON path projection and metadata wrapping.
Tests & Examples
packages/slack-primitive/src/__tests__/*, examples/*
Vitest suites for post-message, cloud-relay, noop, and runtime selection; example notify-on-pr.ts with README documenting runtimes, setup, and smoke tests.
Package & CI Integration
packages/slack-primitive/package.json, .github/workflows/*, packages/sdk/*
Package manifest and TypeScript/Vitest configs; CI publish workflows updated to pack/publish slack-primitive; SDK adds ./slack subpath and re-exports Slack primitive surface.

Spec Updates

Layer / File(s) Summary
Design Spec
specs/slack-primitive.md
postMessage channel param made optional with runtime-specific default resolution rules; askQuestion semantics clarified (primitive returns Q/A pair; runner handles durability); v1 limitation: askQuestion disallows DMs; section headings renumbered.
Implementation Spec
specs/slack-primitive-impl.md
New implementation spec defining Phase A scope (local runtime, postMessage/resolveUser/resolveChannel), files to create, auth expectations (SLACK_BOT_TOKEN required with SlackPostBackError on missing token), templating/resolution rules, required tests/examples, acceptance gates, and out-of-scope items.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Suggested reviewers

  • willwashburn

🐰 Hops through trajectories neat and clean,
Slack primitive steps make the workflow keen,
Paths now portable, specs refined with care,
Message resolution floating through the air!

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch primitive

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json:
- Around line 316-326: The compacted artifact
compact_j5u7qhaw4q6a_2026-05-08.json contains absolute, machine-specific
filesystem paths embedded in strings like the entries starting with
"fix-unit-tests", "fix-regressions", and "implement-fixes"; update the code that
writes these compacted findings to sanitize/redact local paths before
serialization by (1) adding a sanitizePath utility that replaces user/home/drive
prefixes and full absolute paths with a portable placeholder (e.g.,
"<REDACTED_PATH>" or relative repo paths), (2) applying sanitizePath to any
fields used in the messages/entries (the code paths that construct those strings
for "fix-unit-tests", "update-tests", "implement-fixes", etc.) right before
writing the JSON, and (3) add tests to ensure entries no longer contain patterns
matching ^/|[A-Za-z]:\\|/Users/ or other OS-specific absolute path forms.

In @.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md:
- Line 27: The documentation line currently uses incorrect casing for the
product name; update the phrase "**Web-only changes gate the heavy core test,
package validation, rust CI, and node compatibility workflows**" (the
user-facing sentence in the compacted trajectory text) to use the official
product casing "GitHub" wherever the platform name appears (e.g., replace any
occurrences of "Github" or "github" with "GitHub") so the documentation uses the
correct branding.

In @.trajectories/completed/2026-05/traj_1776113772922_bc92f121.md:
- Line 34: Replace the embedded absolute path
'/Users/khaliqgant/Projects/AgentWorkforce/relay' in the committed artifact with
a neutral placeholder like REPO_ROOT or "tracked parent worktree"; locate the
offending string in the trajectory file (the line containing the cwd copy under
.relay/workspace) and update it to the placeholder, then re-commit the file so
the PR no longer contains user-specific filesystem details.

In @.trajectories/index.json:
- Around line 10-11: The index currently stores host-absolute paths in the
"path" fields of .trajectories/index.json; change the generator that writes
these entries so it stores repo-relative paths instead (compute path relative to
the repository root and write that string into the "path" key), leaving
"compactedInto" values unchanged; update the code that builds each index entry
(the logic that assigns the "path" property for trajectory entries) to use
path.relative(repoRoot, absolutePath) or equivalent and apply the same change
for all listed entries.

In `@specs/slack-primitive.md`:
- Line 52: Update the Slack token prefix example in the Slack primitive
documentation: replace the incorrect token example "xoxb-_" with the correct
"xoxb-*" in specs/slack-primitive.md (look for the string "xoxb-_" or the
sentence mentioning "bot user OAuth token (xoxb-_)") so the example uses the
standard bot token prefix "xoxb-*".
- Around line 237-258: The fenced code block that lists the package tree
(starting with the line "packages/slack-primitive/") is missing a language
identifier and triggers MD040; add a language tag (e.g., "text") right after the
opening triple backticks so the block begins with "```text" instead of "```" to
satisfy markdownlint and preserve formatting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b921f08d-c615-4f17-a6cf-2078ddfb0e60

📥 Commits

Reviewing files that changed from the base of the PR and between dc8247d and b2f4cbb.

📒 Files selected for processing (16)
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md
  • .trajectories/completed/2026-05/traj_1775914133873_35667beb.json
  • .trajectories/completed/2026-05/traj_1775914133873_35667beb.md
  • .trajectories/completed/2026-05/traj_1776073106646_1839be2d.json
  • .trajectories/completed/2026-05/traj_1776073106646_1839be2d.md
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.json
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.md
  • .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json
  • .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.md
  • .trajectories/completed/2026-05/traj_o9cx33xn5u39.json
  • .trajectories/completed/2026-05/traj_o9cx33xn5u39.md
  • .trajectories/index.json
  • packages/sdk/src/github.ts
  • packages/sdk/src/index.ts
  • specs/slack-primitive.md

Comment thread .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json Outdated
Comment thread .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md Outdated
Comment thread .trajectories/completed/2026-05/traj_1776113772922_bc92f121.md Outdated
Comment thread .trajectories/index.json Outdated
Comment thread specs/slack-primitive.md Outdated
Comment thread specs/slack-primitive.md Outdated
Walked through §8 open questions and folded answers into the body:
- DM support deferred to v2 (limitation noted in §4.3)
- Slack Connect verification moved to Phase A implementation work
- Audit-trail persistence assigned to the runner; tracked as #825
- channel optional for postMessage (reuses sage notify-channel resolver),
  required for askQuestion
- Retry idempotency keyed on (runId, stepName); folded into §4.3 resumability

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
specs/slack-primitive.md (2)

52-52: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix Slack token prefix examples to avoid invalid-looking tokens.

Line 52 still shows xoxb-_ / xoxp-_; use wildcard form (xoxb-*, xoxp-*) so the examples match standard token-prefix notation.

Suggested patch
-For Slack, we expect the connection to be a **bot user OAuth token** (xoxb-_), not user-token (xoxp-_). Posting and reading replies both work with `chat:write`, `channels:history`, and `groups:history` scopes. The primitive validates scopes on first call and throws a typed error early if they're missing.
+For Slack, we expect the connection to be a **bot user OAuth token** (xoxb-*), not user-token (xoxp-*). Posting and reading replies both work with `chat:write`, `channels:history`, and `groups:history` scopes. The primitive validates scopes on first call and throws a typed error early if they're missing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/slack-primitive.md` at line 52, Update the example token prefixes by
replacing the literal strings "xoxb-_" and "xoxp-_" with the wildcard forms
"xoxb-*" and "xoxp-*" in the Slack primitive documentation so the examples use
standard token-prefix notation; search for occurrences of xoxb-_ and xoxp-_ (the
examples in the Slack description) and swap them to xoxb-* and xoxp-*
respectively.

241-262: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced package-tree block (MD040).

Line 241 opens a fenced block without a language identifier; markdownlint will keep flagging this.

Suggested patch
-```
+```text
 packages/slack-primitive/
   src/
     index.ts            // public exports
@@
   examples/
     end-to-end-ask-question.ts
     notify-on-pr.ts
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @specs/slack-primitive.md around lines 241 - 262, The fenced code block
showing the package tree in specs/slack-primitive.md is missing a language tag
which triggers markdownlint MD040; update the opening fence from to include a language (e.g.,text) so the block becomes ```text and save—this targets
the package-tree fenced block that lists packages/slack-primitive/ and its src/
and examples/ entries.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @specs/slack-primitive.md:

  • Line 52: Update the example token prefixes by replacing the literal strings
    "xoxb-" and "xoxp-" with the wildcard forms "xoxb-" and "xoxp-" in the Slack
    primitive documentation so the examples use standard token-prefix notation;
    search for occurrences of xoxb-_ and xoxp-_ (the examples in the Slack
    description) and swap them to xoxb-* and xoxp-* respectively.
  • Around line 241-262: The fenced code block showing the package tree in
    specs/slack-primitive.md is missing a language tag which triggers markdownlint
    MD040; update the opening fence from to include a language (e.g.,text)
    so the block becomes ```text and save—this targets the package-tree fenced block
    that lists packages/slack-primitive/ and its src/ and examples/ entries.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `b99e36db-5936-460e-bc4f-16346cffd1dd`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b2f4cbb8d1117cf0be5ff50d47bfb1d6471c68a0 and c997b219ef51607db16dc368ea7f4b7a38ed7114.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `specs/slack-primitive.md`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

CodeRabbit flagged privacy/portability issues on the trajectory artifacts and
two minor formatting issues on the Slack primitive spec.

- Sanitize machine-specific paths in .trajectories/* — replace
  /Users/khaliqgant/... and /Users/will/... with <repo-root>/<home>
  placeholders. Path fields in .trajectories/index.json normalized to
  repo-relative paths so lookup works across environments.
- specs/slack-primitive.md: wrap token-prefix examples in backticks
  (xoxb-* / xoxp-*) so markdown doesn't eat the asterisks.
- specs/slack-primitive.md: add 'text' language tag to the package-tree
  fenced code block so markdownlint MD040 stops complaining.

Skipped: the "use GitHub casing" comment on the compacted .md — it triggered
on the literal '.github/workflows/' directory path, not the product name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md:
- Line 12: Replace the malformed token text `at*live*\*` in the markdown with
the exact literal token wrapped in backticks so it renders literally;
specifically find the string `at*live*\*` and change it to `at*live*` (i.e., a
single backticked token) so readers see the intended token text unparsed by
markdown.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: eebc1ced-b82b-405f-8ed3-25196c193467

📥 Commits

Reviewing files that changed from the base of the PR and between c997b21 and f08cc19.

📒 Files selected for processing (34)
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md
  • .trajectories/completed/2026-04/traj_05xg7j388bc4.json
  • .trajectories/completed/2026-04/traj_0t92gxaz6igh.json
  • .trajectories/completed/2026-04/traj_1776105988184_29f1270c.json
  • .trajectories/completed/2026-04/traj_222ha5671idc.json
  • .trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json
  • .trajectories/completed/2026-04/traj_4zqhfqw7g28l.json
  • .trajectories/completed/2026-04/traj_530xmbfeljyb.json
  • .trajectories/completed/2026-04/traj_703m7sqyq89t.json
  • .trajectories/completed/2026-04/traj_8oh4r5km5eic.json
  • .trajectories/completed/2026-04/traj_9tt55is74dq5.json
  • .trajectories/completed/2026-04/traj_abjovknvcijv.json
  • .trajectories/completed/2026-04/traj_avmkyoo2s3rt.json
  • .trajectories/completed/2026-04/traj_d48czxmgx4ac.json
  • .trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json
  • .trajectories/completed/2026-04/traj_e5i62wdjx0jd.json
  • .trajectories/completed/2026-04/traj_g3muawdq6bsb.json
  • .trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json
  • .trajectories/completed/2026-04/traj_o8kgzhfu6jth.json
  • .trajectories/completed/2026-04/traj_qb54w47qwod6.json
  • .trajectories/completed/2026-04/traj_rs2bt3x0fqba.json
  • .trajectories/completed/2026-04/traj_tjadoebpscps.json
  • .trajectories/completed/2026-04/traj_tv1x9pamkqad.json
  • .trajectories/completed/2026-04/traj_ui5omrgz819d.json
  • .trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json
  • .trajectories/completed/2026-05/traj_1776073106646_1839be2d.json
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.json
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.md
  • .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json
  • .trajectories/completed/2026-05/traj_o9cx33xn5u39.json
  • .trajectories/completed/traj_1776105620545_9dcebb3d.json
  • .trajectories/index.json
  • specs/slack-primitive.md
✅ Files skipped from review due to trivial changes (27)
  • .trajectories/completed/2026-04/traj_0t92gxaz6igh.json
  • .trajectories/completed/2026-04/traj_tv1x9pamkqad.json
  • .trajectories/completed/2026-04/traj_05xg7j388bc4.json
  • .trajectories/completed/2026-04/traj_w0xpsaoxuiyw.json
  • .trajectories/completed/2026-04/traj_4zqhfqw7g28l.json
  • .trajectories/completed/2026-04/traj_530xmbfeljyb.json
  • .trajectories/completed/2026-04/traj_8oh4r5km5eic.json
  • .trajectories/completed/2026-04/traj_d48czxmgx4ac.json
  • .trajectories/completed/2026-04/traj_o8kgzhfu6jth.json
  • .trajectories/completed/2026-04/traj_ui5omrgz819d.json
  • .trajectories/completed/2026-04/traj_rs2bt3x0fqba.json
  • .trajectories/completed/2026-04/traj_avmkyoo2s3rt.json
  • .trajectories/completed/traj_1776105620545_9dcebb3d.json
  • .trajectories/completed/2026-04/traj_703m7sqyq89t.json
  • .trajectories/completed/2026-04/traj_9tt55is74dq5.json
  • .trajectories/completed/2026-04/traj_abjovknvcijv.json
  • .trajectories/completed/2026-04/traj_3b3p1z4y7qlo.json
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.md
  • .trajectories/completed/2026-04/traj_tjadoebpscps.json
  • .trajectories/completed/2026-04/traj_222ha5671idc.json
  • .trajectories/completed/2026-04/traj_dw8ihhdb8ip7.json
  • .trajectories/completed/2026-04/traj_1776105988184_29f1270c.json
  • .trajectories/completed/2026-04/traj_qb54w47qwod6.json
  • .trajectories/completed/2026-04/traj_mk0t0cgn4ytq.json
  • .trajectories/completed/2026-05/traj_3b3p1z4y7qlo.json
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
  • .trajectories/completed/2026-05/traj_1776073106646_1839be2d.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • .trajectories/completed/2026-05/traj_o9cx33xn5u39.json
  • .trajectories/completed/2026-05/traj_1776113772922_bc92f121.json
  • .trajectories/index.json

Comment thread .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md Outdated
khaliqgant and others added 2 commits May 8, 2026 19:39
Implements packages/slack-primitive following the design spec at
specs/slack-primitive.md and the implementation prompt at
specs/slack-primitive-impl.md. Phase A only — local Web API runtime,
postMessage + resolveUser + resolveChannel.

- Local runtime via @slack/web-api, SLACK_BOT_TOKEN env auth.
- createSlackStep workflow helper with postMessage action.
- Mention resolution: emails via users.lookupByEmail, handles via
  user-cache, raw IDs pass through. Unresolved mentions logged as soft
  error on step output.
- Channel name resolution via conversations.list; IDs pass through.
- Example workflow + smoke-test docs in examples/.
- Unit tests cover token-missing, channel resolution, mention success
  and soft-fail, and templating substitution.

Out of scope: askQuestion, alternate-runtime adapter, Block Kit and
utility verbs, runner schema for askQuestion audit trail (#825).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
#	.trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.md
#	.trajectories/completed/2026-05/traj_1776073106646_1839be2d.json
#	.trajectories/completed/2026-05/traj_1776113772922_bc92f121.md
#	.trajectories/completed/2026-05/traj_o9cx33xn5u39.json
#	.trajectories/index.json
#	packages/sdk/src/index.ts
#	specs/slack-primitive.md
Comment thread packages/slack-primitive/src/actions/resolve-user.ts Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/slack-primitive/src/actions/resolve-user.ts (1)

4-4: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Consider a simpler email pattern to avoid ReDoS risk.

CodeQL flags this regex as polynomial, which can cause ReDoS on pathological inputs like a@a. repeated many times. While the risk is low here (mentions come from workflow configs, not untrusted user input), it's worth using a simpler pattern.

🛡️ Proposed fix
-const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+const EMAIL_PATTERN = /^[^@\s]+@[^@\s]+$/;

This simpler pattern avoids the polynomial behavior while still distinguishing email-like strings from handles. For production-grade email validation, consider a dedicated library, but for mention resolution this lightweight check is sufficient.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slack-primitive/src/actions/resolve-user.ts` at line 4, The current
EMAIL_PATTERN constant can trigger ReDoS; replace the regex-based check with a
linear-time heuristic: implement a small isLikelyEmail helper used in place of
EMAIL_PATTERN that (1) returns false if the string contains whitespace, (2)
finds the single '@' (indexOf === lastIndexOf and not at ends), and (3) ensures
there is at least one '.' after the '@' with characters on both sides; update
usages in resolve-user.ts (replace EMAIL_PATTERN references) to call this helper
so email-like detection remains cheap and ReDoS-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/slack-primitive/src/actions/post-message.ts`:
- Line 22: The code currently calls resolveChannel(slack, params.channel)
unconditionally; add explicit handling for an omitted channel in the postMessage
flow: if params.channel is undefined, first try to read
process.env.SLACK_DEFAULT_CHANNEL and use that value, and only if that env var
is also undefined throw a clear, descriptive Error stating that the channel is
missing and instructing the caller to provide a channel or set
SLACK_DEFAULT_CHANNEL (this avoids a cryptic failure inside resolveChannel);
then pass the resolved channel to resolveChannel(slack, resolvedChannel) instead
of params.channel.

In `@packages/slack-primitive/src/adapter.ts`:
- Around line 48-53: The isAuthenticated method can throw if
this.slack.auth.test() rejects; change isAuthenticated (the async
isAuthenticated() method) to catch errors from this.slack.auth.test() and return
false on any failure instead of letting the exception bubble; keep the existing
behavior of returning Boolean(this.config.token) when this.slack.auth is falsy,
but wrap the call to this.slack.auth.test() (and the subsequent response.ok
check) in a try/catch that returns false on catch so callers get a simple
boolean readiness result.

In `@packages/slack-primitive/src/types.ts`:
- Around line 96-102: PostMessageParams currently requires channel but the spec
allows omitting it; update the PostMessageParams interface in types.ts so the
channel property is optional (change channel: string to channel?: string) and
ensure any code using PostMessageParams (e.g., the postMessage handler/consumer
functions) handles the fallback resolution when channel is undefined.

In `@packages/slack-primitive/src/workflow-step.ts`:
- Around line 292-324: The projection logic in buildOutputProjection returns
null for failed steps when mode is the default data/result path because it uses
result.data ?? (result.output ? result.output : null), which causes
formatStepOutput to surface the string "null" instead of the real error; update
the data-path branch in buildOutputProjection to prefer result.error when both
data and output are empty (i.e., coalesce to result.error before falling back to
null) so the real Slack failure is preserved, and make the same change in the
other occurrence referenced (lines ~154-157) that builds the data/output
projection; locate buildOutputProjection and the alternate occurrence and adjust
the null-coalescing order to check result.error before returning null.
- Around line 398-422: normalizeResolvedParams currently coerces
numeric/boolean-looking strings via coerceScalar which breaks Slack-specific
fields (e.g., threadTs) and later readers
(readOptionalString/readRequiredString). Change coerceScalar so it no longer
converts plain "true"/"false"/"null" or numeric strings to booleans/numbers;
only parse values when they are explicit JSON objects/arrays or explicitly
quoted JSON strings (i.e., keep the JSON.parse branch for values starting with
'{', '[' or wrapped in quotes) and otherwise return the original string
unchanged; keep normalizeResolvedParams calling coerceScalar but ensure it
preserves numeric/boolean-looking strings as strings so Slack readers work
correctly.

---

Duplicate comments:
In `@packages/slack-primitive/src/actions/resolve-user.ts`:
- Line 4: The current EMAIL_PATTERN constant can trigger ReDoS; replace the
regex-based check with a linear-time heuristic: implement a small isLikelyEmail
helper used in place of EMAIL_PATTERN that (1) returns false if the string
contains whitespace, (2) finds the single '@' (indexOf === lastIndexOf and not
at ends), and (3) ensures there is at least one '.' after the '@' with
characters on both sides; update usages in resolve-user.ts (replace
EMAIL_PATTERN references) to call this helper so email-like detection remains
cheap and ReDoS-safe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6f2f2e7-938c-4636-838d-5e2554c0541b

📥 Commits

Reviewing files that changed from the base of the PR and between f08cc19 and 0fc49f4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (26)
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json
  • .trajectories/completed/2026-05/traj_2tqxnib25omk.json
  • .trajectories/completed/2026-05/traj_60qc24ufr96g.json
  • .trajectories/completed/2026-05/traj_itgr2w8qs3xn.json
  • .trajectories/completed/2026-05/traj_m7mpv7j8n78h.json
  • .trajectories/completed/2026-05/traj_o9cx33xn5u39.json
  • .trajectories/completed/2026-05/traj_vkozdglobkyg.json
  • .trajectories/index.json
  • packages/slack-primitive/examples/README.md
  • packages/slack-primitive/examples/notify-on-pr.ts
  • packages/slack-primitive/package.json
  • packages/slack-primitive/src/__tests__/post-message.test.ts
  • packages/slack-primitive/src/actions/post-message.ts
  • packages/slack-primitive/src/actions/resolve-channel.ts
  • packages/slack-primitive/src/actions/resolve-user.ts
  • packages/slack-primitive/src/adapter.ts
  • packages/slack-primitive/src/client.ts
  • packages/slack-primitive/src/index.ts
  • packages/slack-primitive/src/local-runtime.ts
  • packages/slack-primitive/src/types.ts
  • packages/slack-primitive/src/workflow-step.ts
  • packages/slack-primitive/tsconfig.examples.json
  • packages/slack-primitive/tsconfig.json
  • packages/slack-primitive/vitest.config.ts
  • specs/slack-primitive-impl.md
  • specs/slack-primitive.md
✅ Files skipped from review due to trivial changes (12)
  • .trajectories/completed/2026-05/traj_60qc24ufr96g.json
  • packages/slack-primitive/tsconfig.json
  • packages/slack-primitive/examples/README.md
  • .trajectories/completed/2026-05/traj_2tqxnib25omk.json
  • packages/slack-primitive/tsconfig.examples.json
  • packages/slack-primitive/src/index.ts
  • .trajectories/completed/2026-05/traj_itgr2w8qs3xn.json
  • packages/slack-primitive/vitest.config.ts
  • .trajectories/completed/2026-05/traj_vkozdglobkyg.json
  • packages/slack-primitive/package.json
  • specs/slack-primitive-impl.md
  • .trajectories/compacted/compact_j5u7qhaw4q6a_2026-05-08.json

Comment thread packages/slack-primitive/src/actions/post-message.ts Outdated
Comment thread packages/slack-primitive/src/adapter.ts Outdated
Comment thread packages/slack-primitive/src/types.ts
Comment thread packages/slack-primitive/src/workflow-step.ts Outdated
Comment thread packages/slack-primitive/src/workflow-step.ts
…blish wiring

The Phase A primitive previously hard-failed when SLACK_BOT_TOKEN wasn't
set. That breaks the realistic CLI flow: most users don't have a bot
token locally — they have a relay session and a workspace that's already
connected to Slack via ricky's Nango app.

This adds two new runtimes alongside `local`:

- `cloud-relay`: posts via relay-cloud's POST /api/v1/slack/post-message
  (cloud PR #493). Activated when CLOUD_API_TOKEN + CLOUD_API_URL are
  set. The cloud endpoint uses the workspace's existing Nango Slack
  connection — no per-user bot token needed.
- `noop`: postMessage logs a warning and returns a placeholder ts.
  Activated when no tokens are set. Lets workflows run end-to-end in
  CI / smoke environments without hard-failing on missing Slack creds.

Selection priority: cloud-relay → local → noop. Override with
`runtime: 'local' | 'cloud-relay' | 'noop' | 'auto'`.

In cloud-relay mode, resolveUser/resolveChannel throw
`unsupported_in_cloud_relay` (Phase A intentionally exposes only
postMessage). Mention resolution is local-only; cloud-relay surfaces
unresolved mentions as warnings on the step output.

Also wires slack-primitive as an SDK internal dep, mirroring the github
primitive shape:
- packages/sdk/package.json: dep + ./slack subpath export
- packages/sdk/src/slack.ts: re-exports the full surface
- packages/sdk/src/index.ts: `export * as slack` + curated
  `{ createSlackStep, SlackClient }` from the root
- .github/workflows/publish.yml: pack + install in smoke build,
  publish-sdk-internal-deps matrix, dry-run loop, publish_if_missing chain
- .github/workflows/verify-publish-sdk.yml: package-availability check

Tests: 22 new vitest cases (cloud-relay 11, noop 3, runtime-selection 8)
covering auth requirements, success path with thread_ts/unfurl
forwarding, mention warnings, error mapping (rate_limited / not_connected
/ slack_error / upstream_error), resolve rejection, and the auto-detect
priority order.

Related: AgentWorkforce/cloud#493 (the cloud-side endpoint).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
.github/workflows/publish.yml (1)

1261-1264: ⚡ Quick win

Consider removing duplicate internal-dep publish loops in publish-sdk-only.

This job already depends on publish-sdk-internal-deps, but the same package list is maintained and published again here. That duplication is easy to drift (as seen by repeated list edits) and adds avoidable complexity.

♻️ Suggested simplification
-      - name: Dry run SDK internal deps
-        if: github.event.inputs.dry_run == 'true'
-        run: |
-          set -euo pipefail
-          for package in config github-primitive slack-primitive workflow-types; do
-            echo "Dry run - would publish `@agent-relay/`${package}"
-            (cd "packages/${package}" && npm publish --dry-run --access public --tag ${{ github.event.inputs.tag }} --ignore-scripts)
-          done
-
@@
-      - name: Publish SDK internal deps to NPM
-        if: github.event.inputs.dry_run != 'true'
-        run: |
-          set -euo pipefail
-          VERSION="${{ needs.build.outputs.new_version }}"
-
-          publish_if_missing() {
-            local package="$1"
-            local name="@agent-relay/${package}"
-            if npm view "${name}@${VERSION}" version >/dev/null 2>&1; then
-              echo "✓ ${name}@${VERSION} is already published"
-              return
-            fi
-
-            echo "Publishing ${name}@${VERSION}"
-            (cd "packages/${package}" && npm publish --access public --provenance --tag ${{ github.event.inputs.tag }} --ignore-scripts)
-          }
-
-          publish_if_missing config
-          publish_if_missing workflow-types
-          publish_if_missing github-primitive
-          publish_if_missing slack-primitive
+      # Internal deps are already handled by `publish-sdk-internal-deps` (job dependency).

Also applies to: 1271-1292

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/publish.yml around lines 1261 - 1264, The publish-sdk-only
job duplicates the internal package publish loop (the "for package in config
github-primitive slack-primitive workflow-types; do" block) even though it
depends on publish-sdk-internal-deps; remove the duplicated loop from the
publish-sdk-only job and either rely on the dependent job
(publish-sdk-internal-deps) to publish those internal packages or centralize the
package list into a single reusable input/variable used by both jobs; update
publish-sdk-only to only publish the SDK artifacts it is responsible for (or
reference a shared package list/matrix) so the package list is not maintained in
two places.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In @.github/workflows/publish.yml:
- Around line 1261-1264: The publish-sdk-only job duplicates the internal
package publish loop (the "for package in config github-primitive
slack-primitive workflow-types; do" block) even though it depends on
publish-sdk-internal-deps; remove the duplicated loop from the publish-sdk-only
job and either rely on the dependent job (publish-sdk-internal-deps) to publish
those internal packages or centralize the package list into a single reusable
input/variable used by both jobs; update publish-sdk-only to only publish the
SDK artifacts it is responsible for (or reference a shared package list/matrix)
so the package list is not maintained in two places.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d2b1641e-771a-4522-8fc6-b03cd82faca9

📥 Commits

Reviewing files that changed from the base of the PR and between 0fc49f4 and 3fccf89.

📒 Files selected for processing (14)
  • .github/workflows/publish.yml
  • .github/workflows/verify-publish-sdk.yml
  • packages/sdk/package.json
  • packages/sdk/src/index.ts
  • packages/sdk/src/slack.ts
  • packages/slack-primitive/examples/README.md
  • packages/slack-primitive/src/__tests__/cloud-relay-runtime.test.ts
  • packages/slack-primitive/src/__tests__/noop-runtime.test.ts
  • packages/slack-primitive/src/__tests__/runtime-selection.test.ts
  • packages/slack-primitive/src/adapter.ts
  • packages/slack-primitive/src/cloud-relay-runtime.ts
  • packages/slack-primitive/src/index.ts
  • packages/slack-primitive/src/noop-runtime.ts
  • packages/slack-primitive/src/types.ts
✅ Files skipped from review due to trivial changes (7)
  • packages/slack-primitive/src/tests/noop-runtime.test.ts
  • .github/workflows/verify-publish-sdk.yml
  • packages/sdk/src/slack.ts
  • packages/slack-primitive/src/tests/runtime-selection.test.ts
  • packages/slack-primitive/src/noop-runtime.ts
  • packages/slack-primitive/src/index.ts
  • packages/slack-primitive/examples/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/slack-primitive/src/types.ts

@khaliqgant khaliqgant merged commit b00c8ef into main May 9, 2026
48 checks passed
@khaliqgant khaliqgant deleted the primitive branch May 9, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants