Skip to main content
These pieces take the same options and answer with the same shapes as on the Promise API. The examples use the Effect template’s Tasks service. For slow work, see Deferred answers.

Commands

defineCommand(name, options, handler) adds a slash command. The options are description, options, scopes, blocks, env, config and progress, as on the Promise API. options is a Schema.Struct, and its fields appear in the composer in the order you write them. Give every field a description annotation, which the member sees in the composer. The handler gets the decoded values:
src/taskCommand.tsx
Add it with .addCommand(TaskCommand). The answer can be public, private or a notice on its own; see Answers. Reintersect waits up to 30 seconds for it. A union of Schema.Literals is a choice; give each literal a title annotation for its label, or use choice({ value: "Label" }, { description }). member({ description }), circle({ description }) and appObject(kind, { description }) from @reintersect/sdk/effect/app add pickers whose values are { kind: "member", memberId, name } and so on. To receive only the ID, annotate a string instead: Schema.String.annotations({ description: "Owner", jsonSchema: memberOption }). Put the description on the field’s own schema. JSONSchema.make drops a description set on a property signature that has a default, such as Schema.optionalWith(Schema.String, { default }), so annotate the inner schema instead. The value table and the rules for unsupported shapes are in Option schemas.

Message actions

defineMessageAction(id, options, handler) adds an item to the menu on a message. The options are label, icon, context, scopes, blocks, env and config, as on the Promise API:
src/saveAsTask.tsx
Add it with .addMessageAction(SaveAsTask). With context: "thread" or context: "conversation", the handler also gets the thread or conversation in context; see Context. The client in a message action always holds conversations:read.

Progress updates

A command or message action can return a Stream of answers instead of one answer. The first element is the answer, and every later element replaces it in place, so a block can move through the stages of long work:
src/deployCommand.tsx
Services the stream needs and the ways it can fail become the command’s requirements and errors, as they do for an Effect handler. Mark visibility as const inside Stream.map, or the answer won’t type-check. A failure or defect in the stream finishes the answer with the failure notice, and a later element that changes the visibility or the block dies with ReintersectAppProgressMismatchError. Delivery, coalescing and limits work as described in Progress updates. In tests, host.settle runs the stream to the end on the TestClock, moving the clock through the SDK’s waits, and host.answersOf(interactionToken) lists every answer it sent:

Events

defineEvent(name, handler) or defineEvent(name, options, handler) handles a lifecycle event. The payload’s fields arrive at the top of the context, typed for the event:
src/onDecision.tsx
Add it with .addEvent(OnDecision). An event handler returns nothing. If it fails, the delivery fails and Reintersect retries it with backoff, so make the handler safe to run twice. See Delivery and the event catalog.

Routes

defineRoute({ method, path, scopes, blocks, env, handler }) serves your own HTTP endpoint, such as a webhook from another service. The handler returns a Response. Routes belong to the app, not to an install, so the context has reintersect.forInstall(installId) in place of a client:
src/ciWebhook.ts
params is typed from the path. Calling a client method whose scope the route doesn’t declare is a type error, as it is for every piece. A failed route answers 500 with no details. See HTTP routes for where routes are served.

MCP servers

.addMcp(handler, { auth }) works the same on an Effect app. ReintersectApp.layer mounts the handler at /mcp next to the delivery path. See MCP servers.