@volter/twin-slack
v0.1.0
Published
Local Slack twin — Web API + Events API; your real `@slack/web-api` talks to it unmodified. Mirror, simulate, and fork. Built on @volter/twin.
Readme
@volter/twin-slack
The Slack twin — a local replica of Slack's method-style Web API
(POST /api/<method> → { ok, … }) on the shared @volter/twin
kernel, over the existing chat world. The real @slack/web-api WebClient
works against it unmodified via slackApiUrl.
Surface
- Web API (
slack-twin.ts; HTTP wrapperslack-server.ts→createSlackTwinServer): conversations.list/info/history, users.list/info, auth.test. - Writes (
applySlackWrite): chat.postMessage, chat.update, conversations.create — local transactions, visible via an action overlay (R3/R5/R18); the --read-only flag returnsok:false. - Events (
slack-events.ts): Events-APIevent_callbackenvelopes on write (message,channel_created). - Conformance (
slack-conformance.ts): field-name subset vs the vendored Slack channel/user/message object schemas (standing gate; also strips connector bookkeeping likecreatedAtso served objects are pure Slack shapes). - UI mirror (
slack-mirror-ui.ts): a Slack-style workspace (React) over the twin's Web API.
CLI
world-slack serve [--read-only] [--port N] [--root DIR]
world-slack mirror [--port N] [--root DIR]
world-slack conformance [--root DIR]Point the real @slack/web-api WebClient at it via slackApiUrl
(http://127.0.0.1:<port>/api/).
Four ways to use it
- Mock SDK — zero edits (preferred):
SLACK_TWIN_URL=http://127.0.0.1:PORT node --require @volter/twin/inject your-appredirects the real@slack/web-apifromslack.comto the twin. Or override directly:new WebClient(token, { slackApiUrl: 'http://127.0.0.1:PORT/api/' }). - API + CLI —
world-slack serve(writable) + drive withvolter-twin status|plan|refs chat, then push (cookbook/slack-live-sync). - Read-only —
world-slack serve --read-only: unlimited local reads, no rate limits; writes refuse like Slack ({ ok: false }). - UI mirror —
world-slack mirrorrenders a Slack-style workspace over the twin's state.
(See the repo's GETTING-STARTED → "Four ways to use a twin", and cookbook/zero-edit-inject.)
Stable on the twin rubric: fidelity, read/write/fork, sync, observability, event emission, and conformance are tracked with explicit coverage gaps.
Coverage
Goal: honest, explicitly tracked coverage of Slack's core feature surface. The only accepted carve-outs are explicit out-of-scope items below. Anything not done or carved out is a gap to close.
Done — conversations: list/info/history/replies/members + create/join/leave/invite
(membership tracked, num_members/is_member updated); chat: postMessage/update/delete/
getPermalink + scheduleMessage/deleteScheduledMessage/scheduledMessages.list; threads
(parent/reply split, reply_count/reply_users/latest_reply derived); reactions add/
remove/get; pins add/list (pin_count, pinned_to); bookmarks add/list; users
list/info + profile.get/set (incl. custom status), getPresence + setPresence, dnd.info +
setSnooze/endSnooze; files info/list/delete (+ derived shares/channels); team.info;
views open/update/push/publish (modals + App Home, hash optimistic-concurrency); Canvas
create/edit/delete + sections.lookup + conversations.canvases.create (markdown body parsed into
addressable sections; channel canvas stamps the real properties.canvas channel field; canvas
state held in twin-internal canvas subjects, never in slackState); Block Kit
blocks + message metadata round-trip (stored verbatim, returned on history); opaque
cursor pagination + oldest/latest/inclusive time windows; faithful { ok, ... }
shapes with real Slack error codes; Events-API emission (event_callback on write:
message/channel_created/reaction/pin/member/file); interactivity dispatch — delivers
vendor-faithful block_actions / view_submission / view_closed payloads (deterministic
trigger_id from occurredAt+seq; modal payloads embed the REAL stored view + merged
state.values) and slash-command POSTs (command/text/user_id/channel_id/
response_url/trigger_id) to registered endpoints via injected delivery (fake in tests,
HTTP POST live — no real network), incl. the ack/response_action (clear/update/push/errors)
shape; R2 conformance + recorded-diff gates;
UI mirror (rung-5 structural DOM checklist ✅, incl. a huddle tray for the Calls API); connector pull (channel + history,
incremental cursor) + push (postMessage/update/delete/scheduleMessage/deleteScheduledMessage,
reactions, conversations create/join/leave/invite, pins.add, files.delete, bookmarks.add,
profile.set, setPresence, dnd snooze/end — confirmed per action);
Calls API (calls.add/update/end/info + participants.add/remove — used to model huddles
as in-channel live calls, rendered in the huddle tray); Workflow Builder steps
(workflows.stepCompleted/Failed/updateStep) + custom functions (functions.completeSuccess/Error)
- workflow triggers (workflows.triggers.create/list/update/delete — link/shortcut/event/
scheduled/webhook records, with minted webhook/shortcut urls);
legacy secondary message attachments (chat.postMessage/update — normalized 1-based
id, round-trip on history); thread subscriptions (subscriptions.thread.add/remove —subscribedreflected on the thread root in conversations.replies); file comments (files.comments.add/list/delete — bumpscomments_count); files.remote.* (add/info/update/remove/list — external file refs asmode:externalfile objects, addressable byfileid orexternal_id); Enterprise Grid admin.* — admin.users (invite/remove/role changes + list), admin.conversations (archive/unarchive/delete/rename/convertToPrivate/setTeams + search + getConversationPrefs), admin.teams (create/settings + list), admin.apps (approve/restrict + approved/restricted lists); SCIM provisioning (Users create/list/delete, Groups create/list — mapped to chat users/subteams); Audit Logs API (audit.v1.logs/actions/schemas — admin.* role/channel/app/workspace actions append faithful audit entries, read back newest-first + action-filtered); Slack Connect (conversations.inviteShared + externalInvitePermissions.set — stamps the realis_pending_ext_shared/pending_sharedchannel fields); canvas sharing (canvases.access.set/delete); Sign in with Slack (users.identity); team.accessLogs/billableInfo/integrationLogs; app lifecycle (apps.connections.open negotiation url + apps.uninstall).
Planned (known-missing, will do) — deeper Enterprise Grid admin.* families not yet
modeled (each returns unknown_method today): admin.usergroups, admin.inviteRequests,
admin.barriers (information barriers), admin.roles, admin.auth.policy, admin.users.session,
admin.conversations.restrictAccess / ekm, admin.emoji.list/addAlias, admin.workflows,
admin.functions, admin.analytics.getFile; search.messages deepening; scheduled DND
windows (only manual snooze today); plus the recognized-but-unmodeled methods that return a
faithful method_not_modeled rather than a fake success.
Out of scope (deliberately not modeled, with reason) —
- Live realtime transports — Socket Mode / RTM websockets: the HTTP Web API + Events API surface is fully modeled; the persistent-socket transport is infra, not the API.
- Actual file bytes:
files.upload/completeUploadExternalmint a real-shaped file object with syntheticurl_private, but store no content — so the connector honestly skips pushing uploads (no bytes to replay). The file objects stay modeled (only the raw bytes are out of scope). - Ephemeral typing indicators and real avatar/team-icon image bytes (synthetic URLs): transient/binary, not durable API state.
