@rome-os/app-runtime 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,16 +1,9 @@
1
1
  import type { BetterSQLite3Database } from "drizzle-orm/better-sqlite3";
2
2
  import { z } from "zod";
3
+ import type { JSONSchema } from "zod/v4/core";
3
4
  export { z };
5
+ export type { JSONSchema };
4
6
  export * from "./browser.js";
5
- export { getWorkerRpc, isWorkerRpcResponse, WorkerRpcTimeoutError, WorkerRpcDisconnectError, setWorkerRpcInProcessDispatcher, type WorkerRpcInProcessDispatcher, type RpcResponseMessage, } from "./worker-rpc.js";
6
- export { RoutineEngineProxy, EventBusProxy, EventCatalogProxy, AppManagerProxy, } from "./proxies.js";
7
- export { IpcRpc, IpcRpcTimeoutError, IpcRpcDisconnectError, IpcStreamError, isIpcMessage, createWorkerProcessTransport, createChildProcessTransport, type IpcMessage, type IpcTransport, type IpcCallContext, type IpcInboundStream, type IpcOutboundStream, } from "./ipc.js";
8
- import { IpcRpc } from "./ipc.js";
9
- /**
10
- * Lazy worker-side IpcRpc bound to the parent process IPC channel. Returns
11
- * the same instance for the lifetime of the worker.
12
- */
13
- export declare function getWorkerIpc(): IpcRpc;
14
7
  export type SpanAttributes = Record<string, unknown>;
15
8
  export interface TelemetryBridge {
16
9
  withRomeSpan<T>(name: string, attrs: SpanAttributes, fn: () => Promise<T>): Promise<T>;
@@ -87,41 +80,182 @@ export type PreviewPayload = {
87
80
  value: string;
88
81
  }[];
89
82
  };
90
- export interface ActionResult {
91
- success: boolean;
92
- data?: unknown;
93
- error?: string;
94
- pendingApproval?: {
95
- approvalId: string;
96
- actionName: string;
97
- description: string;
98
- };
83
+ /** Execution was suspended pending guardian approval; the action body has NOT
84
+ * run. When the guardian approves, core re-runs the action for real and the
85
+ * outcome arrives out-of-band (a new agent turn / webhook poll update). */
86
+ export interface PendingApproval {
87
+ approvalId: string;
88
+ actionName: string;
89
+ description: string;
90
+ }
91
+ /**
92
+ * Park the calling agent on a self-contained guardian input. When an
93
+ * agent-callable action returns this, the calling agent is told to wait (it
94
+ * does not receive data); the chat host mounts this app's own component as a
95
+ * first-class assistant block in the transcript (declared in app.yaml under
96
+ * `components:` and registered via the web SDK's `defineComponent`). Best for
97
+ * a structured question card (see the `ask-user` app). The thread is not
98
+ * locked; several inline components can be open at once.
99
+ *
100
+ * Resolves by posting an `interaction_result` (the produced artifact, or
101
+ * `{ dismissed: true }`) on the session, which re-drives the calling agent
102
+ * with a server-built outcome prompt. Control never leaves the calling agent —
103
+ * for a transfer of the floor to another agent, see {@link Handoff}.
104
+ */
105
+ export interface PendingInteraction {
106
+ /** This app's id (owns the component to render). */
107
+ appId: string;
99
108
  /**
100
- * Suspend the calling agent and hand control to an app surface. When an
101
- * agent-callable action returns this, the calling agent is told to wait (it
102
- * does not receive `data`); the chat host mounts the named app surface so the
103
- * guardian can produce an artifact collaboratively. The surface posts the
104
- * artifact back as an `interaction_result` on the parent session, which
105
- * re-drives the calling agent with the outcome — the action body does NOT
106
- * re-run. Generalizes `ask_user_question` so any app can extend it into an
107
- * arbitrarily rich interactive flow.
109
+ * Human-readable rendering of what the component asks for. Only webchat can
110
+ * mount the component; on a messaging channel (or inside a subagent) the
111
+ * calling agent relays this text as prose and the guardian's reply arrives
112
+ * as the next turn.
108
113
  */
109
- pendingInteraction?: {
110
- /** App that owns the surface to mount (this app's id, e.g. "workflow-studio"). */
111
- appId: string;
112
- /** Surface route within the app the host opens (app-defined, e.g. "design"). */
113
- surface: string;
114
- /**
115
- * Agent that borrows this session's turns while the interaction is open. The
116
- * guardian keeps using the same chat thread; their turns route to this agent
117
- * (on the same webchat session) until the surface resolves, then route back
118
- * to the calling agent. Omit for a surface that needs no conversation.
119
- */
120
- agentName?: string;
121
- /** Optional seed context passed through to the surface. */
122
- payload?: Record<string, unknown>;
114
+ promptText: string;
115
+ render: {
116
+ kind: "inline";
117
+ /** Component id declared in app.yaml and registered via defineComponent. */
118
+ componentId: string;
119
+ /** Optional seed props handed to the component renderer. */
120
+ props?: Record<string, unknown>;
123
121
  };
124
122
  }
123
+ /**
124
+ * Result contract of a handoff's child conversation — what the specialist
125
+ * agent must hand back before control returns to the caller.
126
+ */
127
+ export interface HandbackSpec {
128
+ /**
129
+ * JSON Schema the handback artifact must satisfy. The specialist agent gets
130
+ * a `submit_output` tool AJV-validated against it; invalid submissions
131
+ * bounce back as tool errors until they conform.
132
+ */
133
+ schema: Record<string, unknown>;
134
+ /**
135
+ * Optional action the host runs on each submission AFTER the schema passes,
136
+ * for app-specific semantic checks the schema can't express. It receives the
137
+ * candidate payload as its input and must return `{ valid: boolean,
138
+ * errors?: string[] }` in `data`. Invalid (or any validator failure —
139
+ * fail-closed) bounces the submission back to the agent with the errors.
140
+ * Must be read-only: it can run once per submission attempt.
141
+ */
142
+ validate?: string;
143
+ }
144
+ /**
145
+ * Transfer the floor to another agent. When an agent-callable action returns
146
+ * this, the calling agent suspends and the host opens a dedicated child session
147
+ * the guardian collaborates in with `agentName` (see Workflow Studio's
148
+ * `design_workflow`). The parent thread is locked until control returns.
149
+ *
150
+ * The handoff itself mounts no app surface — the summoned agent brings one up on
151
+ * demand by calling the `show_app` action (e.g. Workflow Studio's author shows
152
+ * its live storyboard once there is a plan to preview). `appId` only names the
153
+ * app that owns the handoff (the host install-checks it).
154
+ *
155
+ * The handback: when `handback` is set, a schema-valid (and, if declared,
156
+ * app-validated) `submit_output` submission renders an approval card in the
157
+ * child session; guardian approval ends the handoff and posts the payload as
158
+ * an `interaction_result` on the parent session, re-driving the caller with a
159
+ * server-built outcome prompt (plus `handbackHint`). Dismissal hands control
160
+ * back with `{ dismissed: true }` and no artifact.
161
+ */
162
+ export interface Handoff {
163
+ /** The app that owns this handoff (the host install-checks it). */
164
+ appId: string;
165
+ /**
166
+ * Off-webchat fallback. Only webchat can run a handoff; on a messaging
167
+ * channel (or inside a subagent) the calling agent relays this text as prose
168
+ * and the guardian's reply arrives as the next turn.
169
+ */
170
+ promptText: string;
171
+ /**
172
+ * Agent the guardian collaborates with in the child session. Omit for a
173
+ * handoff that needs no conversation.
174
+ */
175
+ agentName?: string;
176
+ /** Optional seed context (e.g. the opening summary). */
177
+ payload?: Record<string, unknown>;
178
+ /** Result contract the specialist must satisfy to hand control back. */
179
+ handback?: HandbackSpec;
180
+ /**
181
+ * Appended to the resolution prompt the resumed caller receives, so the
182
+ * caller learns its exact next step. The literal token `<childSessionId>` is
183
+ * substituted with the handoff's child session id by the host.
184
+ */
185
+ handbackHint?: string;
186
+ }
187
+ /**
188
+ * Place one of this app's web widgets onto the guardian's workspace (the
189
+ * freegrid), WITHOUT parking the calling agent. Unlike {@link PendingInteraction}
190
+ * — which suspends the turn and waits for the guardian to resolve a surface —
191
+ * this is fire-and-forget: the widget is mounted as a side effect and the agent
192
+ * is told it succeeded and keeps going on the same turn. Use it to surface a
193
+ * read-only view the guardian can glance at (e.g. Workflow Studio's storyboard),
194
+ * not to collect input.
195
+ *
196
+ * Only takes effect on a webchat turn (the placement rides the turn's event
197
+ * stream to the browser). On a messaging channel or inside a subagent there is
198
+ * no workspace, so the calling agent is told the widget could not be shown.
199
+ */
200
+ export interface PlaceWidget {
201
+ /** This app's id — the app whose web widget is mounted onto the freegrid. */
202
+ appId: string;
203
+ /**
204
+ * `true` scopes the placement to the current chat session: the widget is added
205
+ * to this session's layout and mounted with `?session=<id>`, so a session-keyed
206
+ * surface shows this conversation's state. `false` is a standing placement that
207
+ * persists across sessions (as on app install).
208
+ */
209
+ sessionScoped: boolean;
210
+ /**
211
+ * Optional route within the app, e.g. `"orders/123"`. It rides the widget src
212
+ * as the path after the app id (`/full/apps/<appId>/orders/123`), which the
213
+ * host forwards to the app as `bootstrap.routePath` so the app's own router
214
+ * resolves it. An unknown route is the app's to handle (typically a not-found
215
+ * screen). This is *addressing*, not data: payloads belong in the app's own
216
+ * session storage, which the route reads on mount (keyed by `session` + `params`).
217
+ */
218
+ route?: string;
219
+ /**
220
+ * Optional flat scalar parameters carried as query params on the widget src
221
+ * (`?orderId=123`). The app reads them off `window.location.search`. Values must
222
+ * be primitives so they serialize into the persisted placement and survive
223
+ * remount. Host-owned query keys (`session`, `interaction`) always win.
224
+ */
225
+ params?: Record<string, string | number | boolean>;
226
+ }
227
+ /**
228
+ * The envelope every action returns, discriminated on `status`. This one type
229
+ * crosses every invocation boundary — agent↔action tool results, app↔app
230
+ * `runAction`, webhook invocation records — so the states are modeled as a union
231
+ * rather than independent flags: a result is exactly one of completed (`ok`),
232
+ * domain-rejected (`error`), suspended on approval, suspended on an inline
233
+ * guardian input (`pending_interaction`), suspended on a handoff to another agent
234
+ * (`handoff`), or completed-with-a-widget-placed (`place_widget`, which does NOT
235
+ * suspend). Infrastructure failures (handler throw, serialization, worker crash)
236
+ * are NOT `error` results — they surface as a thrown {@link ActionInvocationError}.
237
+ * The `error` variant is for domain rejections the caller can act on ("routine
238
+ * name already taken").
239
+ */
240
+ export type ActionResult<T = unknown> = {
241
+ status: "ok";
242
+ data?: T;
243
+ } | {
244
+ status: "error";
245
+ error: string;
246
+ } | {
247
+ status: "pending_approval";
248
+ approval: PendingApproval;
249
+ } | {
250
+ status: "pending_interaction";
251
+ interaction: PendingInteraction;
252
+ } | {
253
+ status: "handoff";
254
+ handoff: Handoff;
255
+ } | {
256
+ status: "place_widget";
257
+ placement: PlaceWidget;
258
+ };
125
259
  /**
126
260
  * Build an {@link Action} from a Zod input schema. The schema is the single
127
261
  * source of truth: it generates the model-facing JSON Schema (`inputSchema`),
@@ -246,42 +380,77 @@ export interface AgentLifecycleHookDeps {
246
380
  appContext?: RomeAppContext;
247
381
  agentRunner?: AgentRunnerInterface;
248
382
  }
249
- export type AgentMessage = {
383
+ /** Turn bracketing: the first event of a turn's stream. */
384
+ export interface TurnStartMessage {
385
+ type: "turn_start";
386
+ turnId: string;
387
+ /** Session the turn runs on — stable across turns of a conversation. */
388
+ sessionId: string;
389
+ userPrompt: string;
390
+ }
391
+ /** Turn bracketing: the last event of a turn's stream, emitted after the
392
+ * terminal `result`/`error` block. */
393
+ export interface TurnEndMessage {
394
+ type: "turn_end";
395
+ turnId: string;
396
+ /** Turn outcome. `interrupted` means the user stopped the turn mid-flight;
397
+ * it takes precedence over `error` (an abort surfaced as an error block is
398
+ * still an interruption, not a failure). */
399
+ status: "completed" | "interrupted" | "error";
400
+ /** Turn wall-clock measured by the AgentSession. Distinct from
401
+ * `accounting.durationMs` on the terminal block, which is the
402
+ * provider's self-reported per-call duration. */
403
+ durationMs: number;
404
+ }
405
+ export interface TextMessage {
250
406
  type: "text";
251
407
  content: string;
252
- } | {
408
+ }
409
+ /**
410
+ * Incremental preview of an in-flight `text` block (provider streaming).
411
+ * Transient: the complete `text` block still follows, so consumers that
412
+ * only care about whole blocks (trace, persistence, accounting) must
413
+ * ignore this variant. Emitted only by providers that support partial
414
+ * output; absence degrades to whole-block delivery.
415
+ */
416
+ export interface TextDeltaMessage {
417
+ type: "text_delta";
418
+ content: string;
419
+ }
420
+ export interface ThinkingMessage {
253
421
  type: "thinking";
254
422
  content: string;
255
- } | {
423
+ }
424
+ export interface ToolUseMessage {
256
425
  type: "tool_use";
257
426
  id: string;
258
427
  tool: string;
259
428
  input: unknown;
260
429
  startedAt?: string;
261
- } | {
430
+ }
431
+ export interface ToolResultMessage {
262
432
  type: "tool_result";
263
433
  toolUseId: string;
264
434
  tool: string;
265
435
  output: unknown;
266
436
  endedAt?: string;
267
- } | {
437
+ }
438
+ /** Terminal content block: the agent's final answer for its turn.
439
+ * The turn boundary itself is the `turn_end` event that follows. */
440
+ export interface ResultMessage {
268
441
  type: "result";
269
442
  content: string;
443
+ /** Provider-reported usage for the agent that produced this block.
444
+ * Sub-agent terminals carry their own accounting. */
270
445
  accounting?: AgentAccounting;
271
- /** True on the single block that ends the turn (the outermost
272
- * AgentSession's terminal). Sub-agent terminals carry false. */
273
- turnTerminal?: boolean;
274
- /** Turn wall-clock; set only when `turnTerminal === true`. Separate
275
- * from `accounting.durationMs`, which is the provider's per-call
276
- * duration. */
277
- turnDurationMs?: number;
278
- } | {
446
+ }
447
+ /** Terminal content block: the turn failed. `turn_end` still follows. */
448
+ export interface ErrorMessage {
279
449
  type: "error";
280
450
  error: string;
281
451
  accounting?: AgentAccounting;
282
- turnTerminal?: boolean;
283
- turnDurationMs?: number;
284
- } | {
452
+ }
453
+ export interface SessionInitMessage {
285
454
  type: "session_init";
286
455
  sessionId: string;
287
456
  systemPrompt?: string;
@@ -289,17 +458,19 @@ export type AgentMessage = {
289
458
  projectPath?: string;
290
459
  /** RFC 013: per-turn id allocated by AgentSession.sendTurn. */
291
460
  turnId?: string;
292
- } | {
293
- /**
294
- * Structured payload an agent submitted via its `outputSchema` (RFC 016
295
- * §submit_output). Emitted by AgentSession only AFTER the payload has
296
- * been schema-validated and accepted, so consumers can treat it as
297
- * authoritative — distinct from the raw `tool_use` event for the
298
- * underlying `submit_output` tool which represents the model's proposal.
299
- */
461
+ }
462
+ /**
463
+ * Structured payload an agent submitted via its `outputSchema` (RFC 016
464
+ * §submit_output). Emitted by AgentSession only AFTER the payload has
465
+ * been schema-validated and accepted, so consumers can treat it as
466
+ * authoritative — distinct from the raw `tool_use` event for the
467
+ * underlying `submit_output` tool which represents the model's proposal.
468
+ */
469
+ export interface StructuredOutputMessage {
300
470
  type: "structured_output";
301
471
  payload: unknown;
302
- };
472
+ }
473
+ export type AgentMessage = TurnStartMessage | TurnEndMessage | TextMessage | TextDeltaMessage | ThinkingMessage | ToolUseMessage | ToolResultMessage | ResultMessage | ErrorMessage | SessionInitMessage | StructuredOutputMessage;
303
474
  export type StreamAgentMessage = AgentMessage & {
304
475
  agent?: string;
305
476
  };
@@ -312,6 +483,12 @@ export interface ThreadContext {
312
483
  threadType?: "private" | "group";
313
484
  projectName?: string;
314
485
  projectPath?: string;
486
+ /**
487
+ * Bond level of the message sender, when known. Lets turn-level logic tell the
488
+ * guardian apart from other trusted senders (inner-circle, acquaintance) who
489
+ * reach the same `<channel>:<thread>` trusted path.
490
+ */
491
+ senderBondLevel?: string;
315
492
  }
316
493
  export interface RunParams {
317
494
  agentName: string;
@@ -334,6 +511,27 @@ export interface ForkRunParams {
334
511
  export interface AgentRunnerInterface {
335
512
  run(params: RunParams): AsyncIterable<AgentMessage>;
336
513
  runForked?(params: ForkRunParams): AsyncIterable<AgentMessage>;
514
+ /**
515
+ * Returns true when the agent with the given name is loaded in the catalog
516
+ * and can be invoked via `run`. Used by callers (e.g. the inbox message
517
+ * handler) to fall back gracefully when a channel is configured to route to
518
+ * an agent that has been uninstalled. Optional for backwards compatibility;
519
+ * implementations that don't provide it are treated as always-true by
520
+ * callers. May be async — the worker-side runner resolves the catalog over
521
+ * RPC, so callers must `await` the result.
522
+ */
523
+ hasAgent?(name: string): boolean | Promise<boolean>;
524
+ /**
525
+ * Returns true when the named agent is allowed to call the named action —
526
+ * i.e. the action resolves through the agent's allow-list (or `*`, or a
527
+ * globally-granted action), the same resolution the agent session uses to
528
+ * gate tool calls. Lets callers (e.g. the inbox message handler) tailor
529
+ * guidance to what the routed agent can actually do, instead of assuming.
530
+ * Optional for backwards compatibility; implementations that don't provide
531
+ * it are treated as "cannot" by callers. May be async — the worker-side
532
+ * runner resolves the catalog over RPC, so callers must `await` the result.
533
+ */
534
+ hasAction?(agentName: string, actionName: string): boolean | Promise<boolean>;
337
535
  }
338
536
  export interface Attachment {
339
537
  type: "image" | "video" | "audio" | "document" | "sticker" | "location" | "contact";
@@ -385,26 +583,6 @@ export type MessagePart = {
385
583
  actionName: string;
386
584
  preview: PreviewPayload;
387
585
  status: ApprovalCardStatus;
388
- } | {
389
- /**
390
- * The model called the built-in `ask_user_question` MCP tool. The host
391
- * channel (web chat today) snapshots the tool's input into this part so
392
- * the question card renders as a first-class assistant message, not a
393
- * detail buried inside the trace drawer.
394
- */
395
- type: "ask_user_question_card";
396
- toolUseId: string;
397
- questions: AskUserQuestionDefinition[];
398
- } | {
399
- /**
400
- * A user's reply to a previously rendered ask_user_question card. Lives
401
- * inside a user-role webchat message so the UI can derive "this question
402
- * was answered" by scanning subsequent messages for a matching
403
- * toolUseId, instead of mutating any tool_use block in place.
404
- */
405
- type: "user_answer";
406
- toolUseId: string;
407
- answers: UserAnswerEntry[];
408
586
  } | {
409
587
  /**
410
588
  * An interactive "routine draft" the agent proposes mid-conversation.
@@ -417,6 +595,62 @@ export type MessagePart = {
417
595
  type: "routine_draft_card";
418
596
  toolUseId: string;
419
597
  draft: RoutineDraftSpec;
598
+ } | {
599
+ /**
600
+ * A parked inline guardian input the agent is waiting on. The action that
601
+ * called for it returned a `pending_interaction` result; the webchat drain
602
+ * loop snapshots this part keyed by the tool call's id, and the host
603
+ * mounts the app's component in the transcript. The component lives in
604
+ * the app, not core.
605
+ */
606
+ type: "pending_interaction";
607
+ toolUseId: string;
608
+ appId: string;
609
+ render: {
610
+ kind: "inline";
611
+ componentId: string;
612
+ props?: Record<string, unknown>;
613
+ };
614
+ } | {
615
+ /**
616
+ * A handoff in progress: the calling agent suspended and `agentName`
617
+ * holds the floor in `childSessionId` (the spawned design conversation),
618
+ * with the app's `surface` mounted as a side-by-side widget. The webchat
619
+ * drain loop snapshots this part keyed by the tool call's id.
620
+ */
621
+ type: "handoff";
622
+ toolUseId: string;
623
+ appId: string;
624
+ surface: string;
625
+ agentName?: string;
626
+ payload?: Record<string, unknown>;
627
+ childSessionId?: string;
628
+ /** Mirrors {@link Handoff.handbackHint}; appended to the resolution
629
+ * prompt when the handoff resolves with an artifact. */
630
+ handbackHint?: string;
631
+ } | {
632
+ /**
633
+ * A schema-valid payload the specialist agent submitted via
634
+ * `submit_output` in a handoff child session that carries a handback
635
+ * contract. The host renders it as an approval card: Approve ends the
636
+ * handoff with `payload` as the output; "keep editing" feedback continues
637
+ * the conversation and a later submission supersedes this card.
638
+ */
639
+ type: "submission_card";
640
+ payload: Record<string, unknown>;
641
+ } | {
642
+ /**
643
+ * Resolution of an inline interaction or a handoff — the single
644
+ * resolution part both suspension kinds share. Lives in a user-role
645
+ * webchat message so the UI derives "this resolved" by scanning for a
646
+ * matching toolUseId, and the server re-drives the calling agent with a
647
+ * server-built outcome prompt. `output` is the produced artifact (a
648
+ * handoff's approved handback payload, or an inline component's result),
649
+ * or `{ dismissed: true }`.
650
+ */
651
+ type: "interaction_result";
652
+ toolUseId: string;
653
+ output: Record<string, unknown>;
420
654
  };
421
655
  /** The proposed routine, as snapshotted from the agent's `propose_routine`
422
656
  * tool call. `name`/`trigger`/`actionName`/`args` are the create payload;
@@ -441,45 +675,20 @@ export interface RoutineDraftSpec {
441
675
  /** Single argument object for the action. */
442
676
  args: Record<string, unknown>;
443
677
  }
444
- export type AskUserQuestionDefinition = {
445
- id: string;
446
- question: string;
447
- type: "single" | "multi";
448
- options: AskUserQuestionOption[];
449
- /** When true (default), the user may add a free-text note alongside their selection. */
450
- allowFreeText?: boolean;
451
- } | {
452
- id: string;
453
- question: string;
454
- type: "text";
455
- };
456
- export interface AskUserQuestionOption {
457
- id: string;
458
- text: string;
459
- preview?: AskUserQuestionOptionPreview;
460
- }
461
- export interface AskUserQuestionOptionPreview {
462
- url: string;
463
- kind?: "image" | "video" | "html" | "csv";
464
- alt?: string;
465
- }
466
- export type UserAnswerEntry = {
467
- type: "single";
468
- questionId: string;
469
- selected: string;
470
- selectedText?: string;
471
- freeText?: string;
472
- } | {
473
- type: "multi";
474
- questionId: string;
475
- selected: string[];
476
- selectedText?: string[];
477
- freeText?: string;
478
- } | {
479
- type: "text";
480
- questionId: string;
481
- text: string;
482
- };
678
+ /**
679
+ * Optional per-message routing overrides resolved by the channel adapter from
680
+ * its channel-level configuration. When present, the downstream message handler
681
+ * uses these to override the default trusted-path target agent. Untrusted
682
+ * messages still go through sentinel regardless.
683
+ */
684
+ export interface MessageRouting {
685
+ /**
686
+ * Name of the agent to route this message to on the trusted path. Falls back
687
+ * to the default ("main") when undefined or when the named agent is not
688
+ * present in the catalog.
689
+ */
690
+ agentName?: string;
691
+ }
483
692
  export interface NormalizedMessage {
484
693
  id: string;
485
694
  channel: "telegram" | "telegram_user" | "whatsapp" | "wechat" | "webchat" | "discord";
@@ -492,6 +701,7 @@ export interface NormalizedMessage {
492
701
  text: string;
493
702
  attachments: Attachment[];
494
703
  replyToMessageId?: string;
704
+ routing?: MessageRouting;
495
705
  rawEvent: unknown;
496
706
  }
497
707
  export interface ChannelAdapter {
@@ -600,6 +810,20 @@ export interface RomeAppContext {
600
810
  runAction(name: string, args: Record<string, unknown>): Promise<ActionResult>;
601
811
  listRoutines(): Promise<Routine[]>;
602
812
  }
813
+ export type ActionInvocationErrorCode = "not_found" | "unserializable" | "handler_error" | "worker_failure";
814
+ /**
815
+ * The single failure shape of `RomeAppContext.runAction`, RFC 030. Every
816
+ * invocation failure — unknown action, args/result that can't cross a process
817
+ * boundary, a throwing handler, a dead worker — surfaces as this type with the
818
+ * same fields regardless of which process the caller or callee ran in, so app
819
+ * code never branches on transport. Cancellation is the one pass-through: it
820
+ * is a control signal for the runtime, not a failure the app handles.
821
+ */
822
+ export declare class ActionInvocationError extends Error {
823
+ readonly actionName: string;
824
+ readonly code: ActionInvocationErrorCode;
825
+ constructor(actionName: string, code: ActionInvocationErrorCode, message: string);
826
+ }
603
827
  export type AppActionRuntimeDeps<TShared = Record<string, unknown>> = TShared & {
604
828
  appContext: RomeAppContext;
605
829
  };
@@ -741,6 +965,20 @@ export interface EventBusTrigger {
741
965
  export interface EventCatalogEntry {
742
966
  eventType: string;
743
967
  appId: string;
968
+ /** JSON Schema for the event's payload — the same vocabulary action input
969
+ * schemas use. With `schemaOrigin: "observed"` it is inferred from the most
970
+ * recent non-empty emission, so it is a discovery aid for a consumer writing
971
+ * a routine `trigger.filter` (whose dot-paths read payload fields), **not** a
972
+ * contract: it reflects one observed payload, may omit optional keys, and
973
+ * deliberately carries no `required` (one sample can't establish
974
+ * requiredness). Absent until the type has been emitted with a non-empty
975
+ * payload. */
976
+ payloadSchema?: JSONSchema.ObjectSchema;
977
+ /** Where `payloadSchema` came from. `"observed"` = inferred from a real
978
+ * emission. `"declared"` is reserved for producer-authored schemas
979
+ * (pre-registration); a declared schema is authoritative and is never
980
+ * overwritten by an observed one. */
981
+ schemaOrigin?: "observed" | "declared";
744
982
  }
745
983
  export interface PollTrigger {
746
984
  type: "poll";
@@ -812,9 +1050,28 @@ export interface EventCatalogReader {
812
1050
  total: number;
813
1051
  }>;
814
1052
  }
1053
+ /** A Discord guild channel as enumerated by the running bot. */
1054
+ export interface DiscordGuildChannel {
1055
+ id: string;
1056
+ name: string;
1057
+ guildId: string;
1058
+ guildName: string;
1059
+ type: string;
1060
+ parentId?: string;
1061
+ }
1062
+ /** Control surface of the live Discord adapter (which runs in the main
1063
+ * process). Both operations tolerate the adapter not running: `reloadConfig`
1064
+ * is a no-op and `listChannels` returns `[]`, so callers can render a clean
1065
+ * "Discord not configured" outcome instead of handling transport errors. */
1066
+ export interface DiscordChannelControl {
1067
+ reloadConfig(): Promise<void>;
1068
+ listChannels(): Promise<DiscordGuildChannel[]>;
1069
+ }
815
1070
  /** App lifecycle authority (create / install / uninstall / enable). Results are
816
1071
  * app-local shapes the caller knows, so they cross this surface as `unknown` and
817
- * the caller narrows — the SDK stays free of the concrete result types. */
1072
+ * the caller narrows — the SDK stays free of the concrete result types.
1073
+ * `install` takes no appId: the daemon derives it from the source (manifest id
1074
+ * for local sources, listing slug for appstore) and returns it in the result. */
818
1075
  export interface AppLifecycle {
819
1076
  create(params: {
820
1077
  appId: string;
@@ -822,7 +1079,6 @@ export interface AppLifecycle {
822
1079
  template?: "default" | "workflow";
823
1080
  }): Promise<unknown>;
824
1081
  install(params: {
825
- appId: string;
826
1082
  source: unknown;
827
1083
  enabled?: boolean;
828
1084
  }): Promise<unknown>;