@stratusagent/gateway 0.14.7 → 0.14.8

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.
@@ -0,0 +1,1103 @@
1
+ import type { AgentDefinition, AlwaysMeans, ApprovalAnswer, ApprovalDelegationOrigin, ApprovalOutcome, ApprovalPolicy, ApprovalScheduleDestination, CredentialScope, EventBus, ImageAttachment, JsonObject, ObserveEntry, Session, Tool, ToolCall, ToolRisk, TrustLevel, UnlistedTools } from '@stratusagent/core';
2
+ import type { ConversationArchiver, ConversationCreator, ConversationInviter, ConversationJoiner, ConversationReader, ParsedSoul, ScheduleRecord } from '@stratusagent/agents';
3
+ import type { OptionalModuleHost } from '@stratusagent/plugins';
4
+ import type { MemoryPassSummary, PluginsConfig, RuntimeSelection, StateEnvironment } from '@stratusagent/state';
5
+ import type { ChannelReloadOutcome, EventDeliveryRow, EventIntakeRequest, EventIntakeResponse, EventSourceConfig, EventsSummary, IntakeState, MemoryPassDetail, RestartOutcome, RestartRequest, RestartStatus, SchedulerLimits, ShardedSessionStore } from './index.ts';
6
+ /**
7
+ * A channel adapter as the gateway sees it: started after the roster
8
+ * loads, stopped before the store drains. Structurally identical to
9
+ * `ChannelAdapter` in @stratusagent/core, declared here because it
10
+ * predates the channel contract moving into core, when the gateway could
11
+ * not import it without depending on the channels package.
12
+ */
13
+ export interface GatewayChannelAdapter {
14
+ name: string;
15
+ start(gateway: Gateway): Promise<void>;
16
+ stop(): Promise<void>;
17
+ /**
18
+ * Whether the gateway may serve without this channel.
19
+ *
20
+ * A channel whose `start()` rejects is logged and skipped, which is right
21
+ * for a Slack app whose tokens went stale: the rest of the fleet stays
22
+ * up. A required channel fails `start()` instead — after stopping the
23
+ * channels that did come up, and before the scheduler starts or any
24
+ * sweep re-asks an approval, so a daemon refused this way has done no
25
+ * work. The control API is the case: a second `stratus serve` on the
26
+ * same home lost the port to the first, logged it, and went on serving
27
+ * with no channel at all — and the next scheduled firing ran in it,
28
+ * where nobody could approve or hear anything. A host that omits this
29
+ * gets the skip.
30
+ */
31
+ required?: boolean;
32
+ /**
33
+ * Whether this channel starts while the gateway is held (`held: true`).
34
+ * Only a surface for the operator should: the control API, so health can
35
+ * be asked and `promote` sent. A conversation channel must not, or the
36
+ * held daemon would take work. Absent means it waits for `promote()`.
37
+ */
38
+ servesWhileHeld?: boolean;
39
+ /**
40
+ * The addressable outbound seam, mirroring `ChannelAdapter.resolveOutbound`
41
+ * structurally for the same reason the rest of this interface does. Optional: an
42
+ * adapter with no concept of a destination simply lacks it, and the
43
+ * gateway treats absence as "this channel cannot be spoken through".
44
+ * Implementations MUST reject a destination they could not deliver to,
45
+ * with a message fit to show the person who named it.
46
+ */
47
+ resolveOutbound?(address: {
48
+ agentId: string;
49
+ to: string;
50
+ thread?: string;
51
+ speaker?: {
52
+ sessionId: string;
53
+ trust: TrustLevel;
54
+ };
55
+ }): Promise<{
56
+ post(text: string): Promise<unknown>;
57
+ }>;
58
+ /**
59
+ * The read side of an addressable conversation, mirroring
60
+ * `ChannelAdapter.readConversation`. Optional:
61
+ * an adapter without it cannot be read through `message.read`.
62
+ * Implementations MUST reject a conversation their app may not read,
63
+ * with a sentence for the agent.
64
+ */
65
+ readConversation?(request: {
66
+ agentId: string;
67
+ conversation: string;
68
+ thread?: string;
69
+ after?: string;
70
+ before?: string;
71
+ limit: number;
72
+ sessionId?: string;
73
+ images?: {
74
+ count: number;
75
+ bytes: number;
76
+ };
77
+ }): ReturnType<ConversationReader>;
78
+ /**
79
+ * Creates a conversation with the agent's own app, mirroring
80
+ * `ChannelAdapter.createConversation`. Optional: an adapter without it
81
+ * cannot serve `channel.create`. Implementations MUST reject what they
82
+ * can tell is invalid before creating anything, and MUST resolve once
83
+ * the conversation exists, reporting what did not happen after it.
84
+ */
85
+ createConversation?(request: Omit<Parameters<ConversationCreator>[0], 'channel'>): ReturnType<ConversationCreator>;
86
+ /**
87
+ * Joins an existing conversation with the agent's own app, mirroring
88
+ * `ChannelAdapter.joinConversation`. Optional: an adapter without it
89
+ * cannot serve `channel.join`.
90
+ */
91
+ joinConversation?(request: Omit<Parameters<ConversationJoiner>[0], 'channel'>): ReturnType<ConversationJoiner>;
92
+ /**
93
+ * Invites people to an existing conversation through the agent's own
94
+ * app, mirroring `ChannelAdapter.inviteToConversation`. Optional: an
95
+ * adapter without it cannot serve `channel.invite`. Implementations
96
+ * MUST reject only when nobody was invited.
97
+ */
98
+ inviteToConversation?(request: Omit<Parameters<ConversationInviter>[0], 'channel'>): ReturnType<ConversationInviter>;
99
+ /**
100
+ * Archives a conversation the agent's own app created, mirroring
101
+ * `ChannelAdapter.archiveConversation`. Optional: an adapter without it
102
+ * cannot serve `channel.archive`. The gateway has already refused the
103
+ * conversation the work was asked from, which it asks the same adapter's
104
+ * `sessionConversation`; without that, every archive is refused.
105
+ */
106
+ archiveConversation?(request: Omit<Parameters<ConversationArchiver>[0], 'channel'>): ReturnType<ConversationArchiver>;
107
+ /** See `ChannelAdapter.accountFor`. */
108
+ accountFor?(agentId: string): string | undefined;
109
+ /** See `ChannelAdapter.conversationKindFor`. */
110
+ conversationKindFor?(agentId: string, conversation: string): Promise<'channel' | 'direct' | undefined>;
111
+ /** See `ChannelAdapter.sessionConversation`. */
112
+ sessionConversation?(agentId: string, sessionId: string): string | undefined;
113
+ /**
114
+ * Picks up which agents this channel carries without a restart, mirroring
115
+ * `ChannelAdapter.reload`. The gateway calls it on every started channel
116
+ * after a roster reload, one channel at a time, and stops waiting after
117
+ * `channelReloadTimeoutMs`. Optional: a channel without it changes only
118
+ * at a restart.
119
+ */
120
+ reload?(): Promise<void>;
121
+ /**
122
+ * Shows a person a form for a credential an agent asked for, mirroring
123
+ * `ChannelAdapter.requestCredential`. Optional:
124
+ * a channel without it cannot take credentials, and a request made in
125
+ * its conversations is refused instead of announced. Implementations
126
+ * MUST reject when the form reached nobody who can answer it, with a
127
+ * sentence for the agent.
128
+ */
129
+ requestCredential?(request: {
130
+ sessionId: string;
131
+ agentId: string;
132
+ requestId: string;
133
+ name: string;
134
+ scope: CredentialScope;
135
+ reason?: string;
136
+ replace?: boolean;
137
+ env?: string;
138
+ metadata: JsonObject;
139
+ }): Promise<void>;
140
+ /** See `ChannelAdapter.credentialReplaceForms`. */
141
+ credentialReplaceForms?: boolean;
142
+ /** See `ChannelAdapter.credentialEnvForms`. */
143
+ credentialEnvForms?: boolean;
144
+ /** See `ChannelAdapter.say`. */
145
+ say?(request: {
146
+ sessionId: string;
147
+ agentId: string;
148
+ text: string;
149
+ }): Promise<void>;
150
+ /**
151
+ * Issues a one-time link to a form for a credential request, for the
152
+ * agent to pass on: what a request falls back to when its conversation
153
+ * cannot show a form, and what an agent asking `via: 'link'` gets. The
154
+ * control API implements it. Optional: a host with no adapter carrying it
155
+ * refuses such requests with the machine-side command instead.
156
+ *
157
+ * The link is the whole credential: whoever opens it can answer the
158
+ * request once, through `provideCredential`, which stays add-only and
159
+ * grants only the agent that asked.
160
+ */
161
+ requestCredentialLink?(request: {
162
+ sessionId: string;
163
+ agentId: string;
164
+ agentName: string;
165
+ requestId: string;
166
+ name: string;
167
+ scope: CredentialScope;
168
+ reason?: string;
169
+ /** The variable the agent's shell will read the key from; the page must say so. */
170
+ env?: string;
171
+ }): Promise<{
172
+ url: string;
173
+ expiresAt: string;
174
+ /** The link is on an address only the machine itself can open (no `api.publicUrl`). */
175
+ localOnly?: boolean;
176
+ }>;
177
+ }
178
+ /**
179
+ * Where a session came from, as the gateway reports it. Structural for the
180
+ * same reason as the adapter above: core's channel contract declares the
181
+ * matching shape (`SessionRouting`) for adapters to consume.
182
+ */
183
+ export interface SessionRouting {
184
+ /** Whose app must do the talking. */
185
+ agentId: string;
186
+ /** The metadata the dispatching surface attached to the session. */
187
+ metadata: JsonObject;
188
+ /**
189
+ * When this agent last spoke, ISO-8601 — its newest reply's timestamp,
190
+ * absent when it has never produced one. Not the session's modification
191
+ * time, which a mid-turn save moves; see `SessionRouting` in @stratusagent/core.
192
+ */
193
+ lastSpokeAt?: string;
194
+ /** The latest turn's text (`latestTurnReply`), when it produced any — see `SessionRouting` in @stratusagent/core. */
195
+ reply?: string;
196
+ /** Whether the turn the session is on is one nobody asked for (`isUnaddressedTurn`) — see `SessionRouting` in @stratusagent/core. */
197
+ unaddressed?: boolean;
198
+ }
199
+ /**
200
+ * What the gateway hands a policy that needs a human somewhere else: one
201
+ * function that publishes a request and settles on the answer. Handed to a
202
+ * factory rather than exposed on the built gateway because the policy has
203
+ * to exist before the first dispatch, and a gateway cannot be constructed
204
+ * with a policy that needs the gateway.
205
+ */
206
+ export interface ApprovalTransport {
207
+ request: ApprovalRequester;
208
+ /**
209
+ * Which plugin package contributes a tool right now, or undefined for a
210
+ * kernel tool (`@stratusagent/permissions`' `grants.contributorOf` takes
211
+ * exactly this shape). Read live from the plugins' tool records rather
212
+ * than snapshotted, for the same reason `tools()` is: a bridge registers
213
+ * what its server advertises, and a grant compared against a stale map
214
+ * would follow the wrong tool.
215
+ */
216
+ contributorOf(toolName: string): string | undefined;
217
+ /**
218
+ * The schedule carve-out, for a policy that understands destinations
219
+ * (`@stratusagent/permissions`' `destinations` option takes exactly this
220
+ * shape). True when the session is a firing of a schedule that still
221
+ * exists, belongs to this agent, and was approved with this destination
222
+ * — the gateway answers from the schedule row, so cancelling revokes.
223
+ */
224
+ destinations: {
225
+ isPreauthorized(session: Session, destination: string): boolean;
226
+ /**
227
+ * The workspace the agent's channel for this `channel:to` destination
228
+ * is connected to (`ChannelAdapter.accountFor`), so a standing grant
229
+ * on one conversation is bound to the workspace it was made in.
230
+ */
231
+ accountFor(agentId: string, destination: string): string | undefined;
232
+ /**
233
+ * Whether a `channel:to` destination is a channel or a DM, asked of the
234
+ * agent's own app for that channel (`ChannelAdapter.conversationKindFor`).
235
+ * Undefined when no adapter can say.
236
+ */
237
+ conversationKindFor(agentId: string, destination: string): Promise<'channel' | 'direct' | undefined>;
238
+ };
239
+ /**
240
+ * The review posture's reviewer (`@stratusagent/permissions`' `review`
241
+ * option wraps it): one bounded model call on the agent's own provider
242
+ * and credentials, exactly the model named (the agent's own when none is)
243
+ * with no fallback and no tools, counted by the provider-call guards like
244
+ * any other call. The case it is shown holds the people's own words and
245
+ * the call, never tool output (`review.ts`).
246
+ *
247
+ * Never rejects: a review that couldn't run answers `failed` with a kind
248
+ * a person reads in the log, and the policy asks about the call.
249
+ */
250
+ review(request: GatewayReviewRequest): Promise<{
251
+ reply: Record<string, unknown>;
252
+ } | {
253
+ failed: string;
254
+ }>;
255
+ /**
256
+ * The values of the stored credentials this agent's soul lists, for the
257
+ * review posture's built-in rule against sending one out. Read from disk
258
+ * per call, like every credential, so a key set a moment ago counts.
259
+ * Never logged; an unreadable store answers nothing.
260
+ */
261
+ secretsFor(agentId: string): Promise<string[]>;
262
+ /**
263
+ * Work the policy runs off the call path, such as a shadow review
264
+ * (`@stratusagent/permissions`' `review.offPath`), from its start: its
265
+ * credential read as well as its reviewer call. A shutdown waits for it
266
+ * after the turns have drained, so what it reports isn't lost.
267
+ */
268
+ offPath(task: Promise<unknown>): void;
269
+ }
270
+ /** One call put to the reviewer. Mirrors `@stratusagent/permissions`' `ReviewRequest`. */
271
+ export interface GatewayReviewRequest {
272
+ session: Session;
273
+ call: ToolCall;
274
+ tool: Tool;
275
+ risk: ToolRisk;
276
+ command?: string;
277
+ origin?: string;
278
+ destination?: string;
279
+ /** `approvals.review.model`; undefined for the agent's own model. */
280
+ model?: string;
281
+ signal: AbortSignal;
282
+ }
283
+ /**
284
+ * A parked call, published for anyone who can reach a person. Mirrors
285
+ * `@stratusagent/permissions`' `ApprovalRequest` structurally rather than
286
+ * importing it: the gateway consumes any `ApprovalPolicy`, and depending on
287
+ * one implementation of the seam to describe the seam would invert that.
288
+ */
289
+ export interface GatewayApprovalRequest {
290
+ session: Session;
291
+ call: {
292
+ id: string;
293
+ toolName: string;
294
+ input: JsonObject;
295
+ };
296
+ risk: ToolRisk;
297
+ /**
298
+ * The site this call would act on, for a tool judged by one
299
+ * (`Tool.originFor`). Passed straight through to whatever renders the
300
+ * request, because the call's arguments do not say where a click lands.
301
+ */
302
+ origin?: string;
303
+ /**
304
+ * True when answering `always` runs this call once and remembers
305
+ * nothing, so a renderer can stop offering one. Passed straight through.
306
+ */
307
+ oneShot?: boolean;
308
+ /** What `always` would remember, when it would. Passed straight through. */
309
+ always?: AlwaysMeans;
310
+ /** Why the review posture is asking. Passed straight through. */
311
+ reviewNote?: string;
312
+ /**
313
+ * When this call first parked, if a restart is re-asking it. The wait is
314
+ * measured from here, so a request keeps the window it started with
315
+ * instead of winning a fresh one — otherwise a daemon that restarts a
316
+ * minute before the deadline grants another full timeout, and one that
317
+ * crash-loops keeps a request alive forever.
318
+ */
319
+ parkedAt?: string;
320
+ signal?: AbortSignal;
321
+ }
322
+ /**
323
+ * Settles with the answer and, when a person gave it, who — the policy
324
+ * records the actor on a standing grant. Every ending settles: a timeout,
325
+ * an abort, and a shutdown are each a `deny` with nobody behind it.
326
+ */
327
+ export type ApprovalRequester = (request: GatewayApprovalRequest) => Promise<ApprovalOutcome>;
328
+ /**
329
+ * A parked call as an observer sees it — everything `tool.approval-requested`
330
+ * announced, still outstanding.
331
+ *
332
+ * Listable because an event stream only tells you what happened while you
333
+ * were listening. A surface that connects while a turn is already parked
334
+ * would otherwise show no pending request at all until the next one, and
335
+ * rebuilding the set from the bus means keeping a second copy of state the
336
+ * gateway already holds.
337
+ */
338
+ export interface PendingApproval {
339
+ requestId: string;
340
+ sessionId: string;
341
+ agentId: string;
342
+ call: {
343
+ id: string;
344
+ toolName: string;
345
+ input: JsonObject;
346
+ };
347
+ risk: ToolRisk;
348
+ /** The site it would act on, for a tool judged by one. */
349
+ origin?: string;
350
+ /** True when `always` on this call remembers nothing — see the event. */
351
+ oneShot?: boolean;
352
+ /** What `always` would remember, when it would — see the event. */
353
+ always?: AlwaysMeans;
354
+ /** Why the review posture is asking — see the event. */
355
+ reviewNote?: string;
356
+ /** When this call parked, ISO-8601. */
357
+ parkedAt: string;
358
+ /**
359
+ * When it denies itself, ISO-8601. Absent when it never will — the same
360
+ * distinction the announcing event draws.
361
+ */
362
+ expiresAt?: string;
363
+ /** The session's metadata: where the turn is happening. */
364
+ metadata?: JsonObject;
365
+ /** Where a delegated call's work was asked from — see the event. */
366
+ delegatedFrom?: ApprovalDelegationOrigin;
367
+ /** Where a scheduled firing's schedule reports — see the event. */
368
+ scheduleDestination?: ApprovalScheduleDestination;
369
+ }
370
+ export interface ResolveApprovalInput {
371
+ requestId: string;
372
+ answer: ApprovalAnswer;
373
+ /**
374
+ * The person who decided, in whatever ids their channel uses. Recorded on
375
+ * the resolved event; who is *allowed* to decide is the channel's
376
+ * question, since the approver set is written in its ids.
377
+ */
378
+ actor?: string;
379
+ /**
380
+ * Why, when it was not a person. A channel that cannot deliver a request
381
+ * — nobody configured to ask, nowhere to ask — still has to settle it,
382
+ * and recording that as `decided` would file a denial nobody made
383
+ * alongside the ones somebody did. `timeout` and `cancelled` are not
384
+ * offered here: those are the gateway's own endings to declare.
385
+ */
386
+ reason?: 'decided' | 'undeliverable';
387
+ }
388
+ /** One registered tool, as an operator's surfaces need to see it. */
389
+ export interface GatewayTool {
390
+ name: string;
391
+ description?: string;
392
+ risk: ToolRisk;
393
+ /** The plugin package that contributed it; absent for kernel tools. */
394
+ package?: string;
395
+ /** Whether that package is trusted to declare a tool `safe`. */
396
+ trusted?: boolean;
397
+ /**
398
+ * What a call to it is judged by, when it offers exactly one scope hook
399
+ * (`Tool.commandFor`, `originFor`, `destinationFor`): what an
400
+ * `approvals.rules` matcher for it can name. Absent for none or several.
401
+ */
402
+ scopedBy?: 'command' | 'origin' | 'destination';
403
+ }
404
+ /** One skill this daemon serves — identity and provenance, never the body. */
405
+ export interface GatewaySkill {
406
+ /** Canonical id: bare for an operator-installed skill, `<package>:<skill>` for a plugin's. */
407
+ id: string;
408
+ name: string;
409
+ description: string;
410
+ /** The bare id a plugin's skill also answers to, while no one else claims it. */
411
+ alias?: string;
412
+ /** The plugin package that contributed it; absent for operator-installed skills. */
413
+ package?: string;
414
+ /** Where the SKILL.md lives. */
415
+ path?: string;
416
+ /** Ships with Stratus and is enabled for every agent, whatever its soul's `skills:` says. */
417
+ builtin?: boolean;
418
+ }
419
+ /** A plugin this daemon was asked to load, and what came of it. */
420
+ export interface GatewayPluginStatus {
421
+ package: string;
422
+ name?: string;
423
+ trusted?: boolean;
424
+ tools?: GatewayTool[];
425
+ skills?: GatewaySkill[];
426
+ /** Provider names it registered — what a soul's `provider:` can select. */
427
+ providers?: string[];
428
+ /** Channel kinds it registered, and the agents each carries. */
429
+ channels?: Array<{
430
+ kind: string;
431
+ agents: string[];
432
+ }>;
433
+ /** Memory store names it registered — what `memoryStore` can select. */
434
+ memory?: string[];
435
+ /** Executor names it registered — what `executor` can select. */
436
+ executors?: string[];
437
+ /** Present when the plugin did not load, and the only field that is. */
438
+ error?: string;
439
+ }
440
+ /** One provider a soul's `provider:` can select on this daemon. */
441
+ export interface GatewayProviderStatus {
442
+ /** The name as a soul writes it: `anthropic`, or a plugin's `ollama`. */
443
+ name: string;
444
+ /** The plugin package that registered it; absent for a built-in. */
445
+ package?: string;
446
+ }
447
+ export interface GatewayOptions {
448
+ env?: StateEnvironment;
449
+ /** Gateway-wide provider/model overrides applied beneath per-soul pins. */
450
+ selection?: RuntimeSelection;
451
+ /** Channel adapters to run (Slack, …). Started on start(), stopped on stop(). */
452
+ channels?: GatewayChannelAdapter[];
453
+ /**
454
+ * Which (kind, agent) pairs the host's own adapters carry, so a plugin
455
+ * channel claiming one of them is refused at load rather than started
456
+ * beside it. The Slack adapter the CLI wires reports its agents here;
457
+ * a host that omits this lets a plugin double up on a host adapter.
458
+ */
459
+ hostChannelClaims?: Array<{
460
+ kind: string;
461
+ agents: readonly string[];
462
+ }>;
463
+ /**
464
+ * What an agent whose soul has no `tools:` list may call; see
465
+ * `AgentRunnerOptions.unlistedTools`. Omitted, the gateway asks the home
466
+ * (`unlistedToolsFor`): every tool on one an older build set up, safe
467
+ * tools only on a new one.
468
+ */
469
+ unlistedTools?: UnlistedTools;
470
+ /**
471
+ * The policy, or a factory that builds one from the gateway's approval
472
+ * transport. Pass a factory whenever the policy parks turns on a human
473
+ * reached through a channel — `remote` mode — since that policy needs the
474
+ * transport the gateway is still constructing.
475
+ *
476
+ * Omitted, the runners get core's default: only `safe` tools run, and
477
+ * every other call is refused with an error naming this option. Pass
478
+ * `AllowAllApprovalPolicy` from `@stratusagent/core` to run every call.
479
+ */
480
+ approvals?: ApprovalPolicy | ((transport: ApprovalTransport) => ApprovalPolicy);
481
+ /**
482
+ * How long a parked call waits before it denies itself. 0 waits forever,
483
+ * which is only ever right in a test. Default 15 minutes.
484
+ */
485
+ approvalTimeoutMs?: number;
486
+ /**
487
+ * How many tool turns one dispatched turn may take before the runner
488
+ * wraps it up with a summary. Default 40 (`DEFAULT_MAX_TURNS` in core).
489
+ *
490
+ * Applies to every runner this gateway builds, so a delegated
491
+ * sub-session is held to the same ceiling as the turn that delegated it
492
+ * — they are separate dispatches and each gets its own allowance, not a
493
+ * share of one. `stratus serve` fills it from the trusted config's
494
+ * `maxTurns`.
495
+ */
496
+ maxTurns?: number;
497
+ /**
498
+ * One agent's own budget, replacing `maxTurns` for its messages and the
499
+ * sub-sessions it delegates to that agent. Undefined means the shared one.
500
+ * `stratus serve` fills it from the trusted config's `agentMaxTurns`.
501
+ */
502
+ maxTurnsFor?: (agentId: string) => number | undefined;
503
+ /**
504
+ * Whether an agent keeps working past its budget instead of wrapping up
505
+ * (`AgentRunnerOptions.autoContinue`): `true` without a cap, a number for
506
+ * that many extra allowances per message, undefined for off. `stratus
507
+ * serve` fills it from the trusted config's `autoContinue`.
508
+ */
509
+ autoContinueFor?: (agentId: string) => true | number | undefined;
510
+ /**
511
+ * The activity watchdog: abort a turn when no event for its session has
512
+ * arrived for this long. Progress-based, not wall-clock — any delta, tool
513
+ * event, or response resets it. 0 disables. Default 120s.
514
+ */
515
+ idleTimeoutMs?: number;
516
+ /**
517
+ * The events listener's sources and body limit (card 590), from the
518
+ * trusted config's `events` block. With no sources the event store is
519
+ * never opened, and every delivery is to an unknown source.
520
+ */
521
+ events?: {
522
+ sources?: Record<string, EventSourceConfig>;
523
+ maxBodyBytes?: number;
524
+ };
525
+ /**
526
+ * Where the per-agent session stores (`agents/<id>/sessions.db`) and the
527
+ * fleet database (`fleet.db`, holding the schedules and the session
528
+ * index) live. Default `~/.stratus`.
529
+ *
530
+ * A directory rather than the single database path it replaced: after
531
+ * per-agent state there is no one file to point at, which is the
532
+ * point — a store is opened on one agent's path, so no query can return
533
+ * another agent's rows.
534
+ */
535
+ stateDir?: string;
536
+ /**
537
+ * Scheduler limits — the interval floor, the per-agent cap on concurrent
538
+ * scheduled turns, and the tick cadence. Defaults are production values;
539
+ * tests lower them.
540
+ */
541
+ schedules?: SchedulerLimits;
542
+ /**
543
+ * Plugins to load, keyed by package name — the `plugins` block, which the
544
+ * caller has already read from a **trusted** config. The gateway does not
545
+ * go looking for one: a plugin runs in-process with the daemon, and which
546
+ * file may say so is a decision that belongs where the config precedence
547
+ * is already understood.
548
+ */
549
+ plugins?: PluginsConfig;
550
+ /**
551
+ * Which executor runs tool calls, by the name a plugin registered it
552
+ * under (`local`, or absent, is the built-in child-process executor).
553
+ * From a **trusted** config only, like `plugins`: an executor is where
554
+ * an agent's commands run, and a cloned repository swapping a sandbox
555
+ * for the host is the downgrade that rule exists to refuse. A name no
556
+ * loaded plugin registered fails `start()` — a daemon silently running
557
+ * commands on the host when the operator selected a sandbox is worse
558
+ * than one that did not start.
559
+ */
560
+ executor?: string;
561
+ /**
562
+ * Host-owned limits for sub-agents (roadmap 24). `maxConcurrentPerTurn`
563
+ * caps how many one parent turn runs at once; the rest wait for a slot.
564
+ * It must be a whole number of at least 1, or `createGateway` throws.
565
+ */
566
+ subAgents?: {
567
+ maxConcurrentPerTurn?: number;
568
+ };
569
+ /**
570
+ * Which store backs agent memory, by registered name (`file`, or absent,
571
+ * is the built-in). Same trust rule and same refusal as `executor`: a
572
+ * daemon writing memories into a store the operator did not select is a
573
+ * daemon whose agents remember into the wrong place.
574
+ */
575
+ memoryStore?: string;
576
+ /**
577
+ * How plugin packages are resolved and imported.
578
+ *
579
+ * Worth passing, and `stratus serve` does. `import.meta.resolve` answers
580
+ * relative to the module that calls it, so the default here asks "is this
581
+ * plugin visible from the gateway package" when the question is "is it
582
+ * visible from the thing the operator installed". Those differ under any
583
+ * layout that is not flat — a pnpm install being the obvious one — and the
584
+ * failure is a plugin reported as not installed while sitting right next
585
+ * to the CLI.
586
+ */
587
+ pluginHost?: OptionalModuleHost;
588
+ /**
589
+ * How this daemon comes back after `restart()`: called once the gateway
590
+ * has stopped, by the host that owns the process — `stratus serve`
591
+ * starts a fresh daemon and supervises it. The gateway can drain and
592
+ * stop; only its host can start a process. A host that omits this gives
593
+ * up `restart()`, which then throws `RestartUnsupportedError`.
594
+ */
595
+ onRestart?: (outcome: RestartOutcome) => void | Promise<void>;
596
+ /**
597
+ * Asked by {@link Gateway.checkRestart} before a restart is announced,
598
+ * while this daemon still serves: a throw refuses the restart with its
599
+ * message and leaves the daemon up. `stratus serve` checks here that the
600
+ * trusted config will load, because the replacement refuses to start on
601
+ * one that will not (#214) — and a restart that drained first would turn
602
+ * a half-saved file into an outage. A host that omits this restarts
603
+ * unchecked.
604
+ */
605
+ restartPreflight?: () => Promise<void>;
606
+ /**
607
+ * Start held: migrate, load the roster, and bring up only the channels
608
+ * that serve while held (the control API), so the daemon answers health
609
+ * and nothing else. No conversation channel connects, no schedule fires,
610
+ * no parked approval is re-asked and no abandoned turn is failed, and a
611
+ * dispatch is refused, until `promote()`. For an upgrade that has to prove
612
+ * the new version healthy before it touches any work.
613
+ */
614
+ held?: boolean;
615
+ /**
616
+ * Called once after a held gateway is promoted and its work has begun.
617
+ * `stratus serve --hold-new-version` records the running version here,
618
+ * so the next start of the same version is not held. A rejection is
619
+ * warned about and does not undo the promotion: the daemon is serving.
620
+ */
621
+ onPromoted?: () => Promise<void>;
622
+ /** The drain window a `restart()` uses when the request names none. Default 30s. */
623
+ restartDrainTimeoutMs?: number;
624
+ /**
625
+ * How long a roster reload waits on one channel's `reload()` before it
626
+ * goes on without it. Default 30s. The reload is not cancelled: the
627
+ * channel finishes on its own, and its outcome is logged when it does.
628
+ */
629
+ channelReloadTimeoutMs?: number;
630
+ /** The drain window a `stop()` uses. Default 30s; see `DEFAULT_STOP_DRAIN_TIMEOUT_MS`. */
631
+ stopDrainTimeoutMs?: number;
632
+ log?: (line: string) => void;
633
+ warn?: (line: string) => void;
634
+ }
635
+ export interface DispatchInput {
636
+ /**
637
+ * Stable session id chosen by the caller — channels derive it from the
638
+ * conversation (e.g. a Slack thread), so any later message with the same
639
+ * id resumes the conversation, across daemon restarts included.
640
+ */
641
+ sessionId: string;
642
+ /** Roster agent id. Defaults to the gateway's default agent. */
643
+ agentId?: string;
644
+ userMessage: string;
645
+ /** Images sent with the message — see `Message.images` in `@stratusagent/core`. */
646
+ images?: ImageAttachment[];
647
+ /**
648
+ * Whether the message was said to the agent — see `RunInput.addressed`
649
+ * in `@stratusagent/core`. `false` runs a turn the agent may answer with
650
+ * nothing; omitted, the turn is one somebody asked for.
651
+ */
652
+ addressed?: boolean;
653
+ /**
654
+ * Said to the agent, but possibly needing no answer — see
655
+ * `RunInput.replyOptional` in `@stratusagent/core` (card 601). Messages
656
+ * merged into one turn (card 423) keep it only if every one of them
657
+ * carries it: one that owes an answer makes the turn owe it.
658
+ */
659
+ replyOptional?: boolean;
660
+ /**
661
+ * What was said before this message that the agent never heard — see
662
+ * `RunInput.earlier` in `@stratusagent/core`. Used only when this
663
+ * dispatch creates the session; a session that already exists ignores
664
+ * it, so a caller that cannot tell which case it is in may always send it.
665
+ */
666
+ earlier?: ObserveEntry[];
667
+ metadata?: JsonObject;
668
+ signal?: AbortSignal;
669
+ /**
670
+ * Caller-chosen id for this turn, so events emitted while it runs can be
671
+ * attributed to it. A session processes several turns in sequence and
672
+ * `StratusEvent` carries no turn identifier, so a surface that queued a
673
+ * message has no other way to tell its own deltas from the next caller's.
674
+ *
675
+ * Optional because most callers do not need one: a channel renders
676
+ * whatever arrives for the conversation it is watching.
677
+ */
678
+ turnId?: string;
679
+ /**
680
+ * The caller's name for this message, unique within the session — for a
681
+ * channel, the platform's own message id. A repeat never starts a second
682
+ * turn: while the first is queued or running the repeat resolves with it;
683
+ * once it has finished the repeat resolves with the session as it stands,
684
+ * running nothing and emitting nothing — which may have moved on since,
685
+ * so the repeated message's own reply is `turnReplyFor(session, key)`,
686
+ * not `latestTurnReply`; and a turn whose process died
687
+ * mid-run is continued, once (`AgentRunner.continueTurn`), by whichever
688
+ * comes first — the repeat or the next start's sweep. Not on a harness
689
+ * (`runsOnHarness`), whose own tool loop may already have acted on the
690
+ * prompt: that turn is failed, as an unkeyed one is.
691
+ *
692
+ * Stored on the user message in the write that accepts it
693
+ * (`Message.idempotencyKey`), so it lasts as long as the transcript does.
694
+ * A repeat's other fields are not compared, and its `signal` does not
695
+ * reach a turn it attaches to; a failed turn stays failed, and retrying
696
+ * it takes a new key.
697
+ *
698
+ * Optional, and a host that omits it gives up crash-safe dedupe: a
699
+ * channel that delivers at least once can only dedupe in memory, which a
700
+ * restart erases, and a turn that was running when the process died is
701
+ * failed at the next start instead of finished.
702
+ */
703
+ idempotencyKey?: string;
704
+ /**
705
+ * Called, before the dispatch resolves, when it repeats a turn rather
706
+ * than starting or continuing one, and says which kind of repeat:
707
+ *
708
+ * - `live` — it attached to a turn another dispatch with the same key
709
+ * started in this process and is still waiting on. That dispatch is
710
+ * alive to render the outcome, so a caller with a reply of its own in
711
+ * progress takes it down and posts nothing.
712
+ * - `finished` — the turn is over and nothing ran. Its outcome may never
713
+ * have been posted — the process can die between the turn's last write
714
+ * and the channel's — and nothing durable says whether it was, so the
715
+ * caller posts it from the session: `turnReplyFor`, `turnFilesFor`, and
716
+ * `turnFailureFor` from `@stratusagent/core`, since the session may
717
+ * have moved on and no event will carry any of it. Said twice beats
718
+ * never said.
719
+ *
720
+ * Not called for a repeat that continues a turn a crash left unfinished:
721
+ * that turn runs, its events are this caller's, and nobody else is
722
+ * rendering it.
723
+ */
724
+ onRepeat?: (repeat: 'live' | 'finished') => void;
725
+ /**
726
+ * Opts this dispatch into merged turns. A message that arrives while the
727
+ * session's turn is running is not given a turn of its own: every
728
+ * opted-in, addressed dispatch queued by the time that turn ends is
729
+ * answered by ONE next turn, its messages joined in arrival order in one
730
+ * user message (`Message.absorbedKeys` keeps each one's key, so a repeat
731
+ * is that turn). No delay is added; the batch is whatever queued.
732
+ *
733
+ * The first queued dispatch leads: its `turnId` stamps the merged turn's
734
+ * events, and it resolves as any dispatch does. Each later one is
735
+ * absorbed — called here, before it resolves (or rejects) with the same
736
+ * outcome — and, like a `live` repeat, posts nothing of its own: the
737
+ * leader's caller renders the answer. A caller that cannot withdraw what
738
+ * it put up leaves this unset and keeps one turn per dispatch.
739
+ *
740
+ * The merged turn runs at the least trusted sender in it. A turn nobody
741
+ * asked for (`addressed: false`) is never merged.
742
+ *
743
+ * Steering (card 948): while the running turn is itself an opted-in,
744
+ * addressed dispatch for the same agent, the batch waiting behind it is
745
+ * handed to that turn at its next tool step instead (`RunInput.steer`),
746
+ * so the agent reads it before its next call rather than after its whole
747
+ * turn. Every dispatch in the batch, its leader included, is then
748
+ * absorbed into the running turn — called here and settled with that
749
+ * turn's outcome — and the running turn's caller renders the answer. A
750
+ * turn that answers without another tool step leaves the batch to run
751
+ * next, merged, as above.
752
+ */
753
+ onAbsorbed?: () => void;
754
+ }
755
+ export interface ObserveInput {
756
+ sessionId: string;
757
+ /** Must match the session's agent when given; a session never crosses identities. */
758
+ agentId?: string;
759
+ /** What was said, speaker included — see `ObserveInput.message` in core. */
760
+ message: string;
761
+ /** This message's metadata, read for the speaker's trust; never merged. */
762
+ metadata?: JsonObject;
763
+ }
764
+ export interface Gateway {
765
+ start(): Promise<void>;
766
+ stop(): Promise<void>;
767
+ /** The one entrypoint: resolve the agent, load-or-create the session, run a turn. */
768
+ dispatch(input: DispatchInput): Promise<Session>;
769
+ /**
770
+ * A message into an existing session with no turn run on it —
771
+ * `AgentRunner.observe`, on the session's chain so an overheard message
772
+ * and a turn never interleave writes, and behind the same two refusals
773
+ * as `dispatch`: the scheduler's reserved id namespace and the daemon's
774
+ * reserved metadata keys, since text entering a session's context is
775
+ * text entering its next prompt whichever door it came through.
776
+ *
777
+ * Existing only: an agent overhears a conversation it is already in, and
778
+ * a session that would have to be created is one it was never invited
779
+ * to — so a session that does not exist is nothing to hear into, and
780
+ * resolves `undefined` rather than refusing. Read on the chain, which is
781
+ * what makes that answer right: an invitation still in flight — a first
782
+ * mention whose dispatch is queued ahead of this — has created the
783
+ * session by the time this runs, where a read outside the chain would
784
+ * have found nothing and dropped the message for good. A rolled-over
785
+ * transcript refuses like a dispatch would.
786
+ */
787
+ observe(input: ObserveInput): Promise<Session | undefined>;
788
+ /** Live events from every runner, one stream for all consumers. */
789
+ readonly bus: EventBus;
790
+ /** The store shared by every runner (durable across restarts). */
791
+ readonly store: ShardedSessionStore;
792
+ /** The current roster, default agent included. */
793
+ agents(): AgentDefinition[];
794
+ /**
795
+ * Re-read the agents directory and the configured default soul, returning
796
+ * the roster that results. Souls are already re-read on every dispatch, so
797
+ * this is for files that appeared or disappeared since start — an agent
798
+ * created by another surface, or one whose soul was deleted.
799
+ */
800
+ reloadRoster(): Promise<AgentDefinition[]>;
801
+ /**
802
+ * `reloadRoster()`, then what each started channel that can reload
803
+ * (`ChannelAdapter.reload`) did with it, in start order: where a secret
804
+ * stored while the daemon runs is picked up without a restart. A channel
805
+ * missing from the list can't reload, and reads its secrets at the next
806
+ * start. Rejects as `reloadRoster()` does, when the roster won't load.
807
+ */
808
+ reloadRosterAndChannels(): Promise<{
809
+ agents: AgentDefinition[];
810
+ channels: ChannelReloadOutcome[];
811
+ }>;
812
+ /**
813
+ * Re-read `~/.stratus/skills/` and serve what is there now, without a
814
+ * restart — the shape `reloadRoster()` established: idempotent,
815
+ * serialized against itself and the roster reload, and a failure that
816
+ * leaves the previous set serving. Plugin-contributed skills are rebuilt
817
+ * from the plugins already loaded (never re-imported), in the same order
818
+ * they loaded, so operator precedence and contested aliases come out as
819
+ * a restart would produce them.
820
+ *
821
+ * A skill that would not load — a malformed `SKILL.md`, a directory whose
822
+ * name is not an id — rejects with the file named and changes nothing:
823
+ * half a catalog is worse than a stale one.
824
+ *
825
+ * Loaded is not enabled: a reloaded skill still reaches only the agents
826
+ * whose souls list it.
827
+ */
828
+ reloadSkills(): Promise<GatewaySkill[]>;
829
+ /**
830
+ * Announce a restart: refuse new turns now, let running ones finish
831
+ * inside the drain window, abort what is still running at the end of
832
+ * it, stop, and hand the process back to the host through `onRestart`.
833
+ * Returns at once with the terms; the drain runs behind it. Idempotent —
834
+ * a second call while one is underway reports the same announcement.
835
+ *
836
+ * What comes back is what a stop-and-start brings back: durable sessions,
837
+ * schedules, and channels; a call parked on a human is denied as
838
+ * cancelled, exactly as a stop denies it. The refusal comes first, which
839
+ * makes the drain's known gap smaller — a turn finishing after the
840
+ * snapshot can still start work the snapshot never saw — but does not
841
+ * close it.
842
+ */
843
+ restart(request?: RestartRequest): RestartStatus;
844
+ /**
845
+ * Whether a `restart()` now would bring the daemon back, as the host
846
+ * judges it (`restartPreflight`): rejects with the host's refusal,
847
+ * resolves when there is none. Separate from `restart()`, which stays
848
+ * synchronous and idempotent, so a caller asks this first — `POST
849
+ * /restart` does.
850
+ */
851
+ checkRestart(): Promise<void>;
852
+ /**
853
+ * The turn currently running on a session, if the caller that started it
854
+ * named one. Single-flight per session is what makes this exact: at most
855
+ * one turn is ever running on a session, so an event emitted for it
856
+ * belongs to this turn and no other.
857
+ */
858
+ activeTurnId(sessionId: string): string | undefined;
859
+ /**
860
+ * True while a side run is running under this session id: a reviewer
861
+ * call (`review:…`) or a memory pass (`memory-pass:…`). Its events reach
862
+ * `bus` like a turn's, so whatever counts provider calls there counts
863
+ * them, but it is not a conversation: nobody can send to it, it is never
864
+ * stored, and a surface that lists conversations leaves it out. Known by
865
+ * the run, not the id's shape, so a dispatched session whose id happens
866
+ * to start the same way is still a conversation.
867
+ */
868
+ isSideRun(sessionId: string): boolean;
869
+ /**
870
+ * Aborts every turn running on a session right now, one recovered after a
871
+ * restart included, the way the watchdog and a restart's window do: each
872
+ * fails with `reason`, its session saved. Returns how many turns are on
873
+ * the session, each now stopping: one aborted earlier that has not
874
+ * settled yet still counts (it is not aborted again), so 0 means nothing
875
+ * is running there at all. An idle or unknown session is 0 and nothing
876
+ * happens. A message queued on the session but not started is left to
877
+ * its caller's own signal. The caller decides who may stop; this asks
878
+ * nothing.
879
+ */
880
+ abortTurns(sessionId: string, reason: string): number;
881
+ /** Every call parked on a human right now, oldest first. */
882
+ pendingApprovals(): PendingApproval[];
883
+ /**
884
+ * Every schedule the fleet has set, oldest first — the audit list the
885
+ * spec demands: an agent that scheduled something an operator cannot see
886
+ * or stop is a bug. Each row is also the scope of its own destination
887
+ * grant, so this doubles as the list of standing permissions to speak.
888
+ */
889
+ schedules(): ScheduleRecord[];
890
+ /**
891
+ * Cancel any agent's schedule — the operator's stop button, and the
892
+ * revocation of the destination grant riding on the row. False when no
893
+ * such schedule exists.
894
+ */
895
+ cancelSchedule(scheduleId: string): boolean;
896
+ /**
897
+ * One request to the events port (card 590): verified, deduplicated,
898
+ * matched against event triggers and recorded, all before the answer.
899
+ * The listener in control-api calls this with the raw body.
900
+ */
901
+ receiveEvent(request: EventIntakeRequest): Promise<EventIntakeResponse>;
902
+ /**
903
+ * Per configured event source: the last delivery, the last refusal and
904
+ * why, and the last 24 hours by status, with the requests refused before
905
+ * they could leave a row (`stratus events`, `/health`). No sources when
906
+ * none is configured.
907
+ */
908
+ eventSummary(): EventsSummary;
909
+ /** Delivery rows, newest first; a row never holds the body. Empty when no source is configured. */
910
+ eventDeliveries(options?: {
911
+ source?: string;
912
+ limit?: number;
913
+ }): EventDeliveryRow[];
914
+ /**
915
+ * An agent's memory passes from its ledger, newest first (spec 962).
916
+ * Read from the file each time, so a pass that just ended shows.
917
+ */
918
+ memoryPasses(agentId: string): Promise<MemoryPassSummary[]>;
919
+ /**
920
+ * One pass and the facts it wrote, from the store this daemon writes
921
+ * to (in shadow, the facts it would have written). Undefined when the
922
+ * ledger has no such pass.
923
+ */
924
+ memoryPass(agentId: string, passId: string): Promise<MemoryPassDetail | undefined>;
925
+ /**
926
+ * Every registered tool, with the risk it will actually be judged at and
927
+ * the package it came from.
928
+ *
929
+ * Provenance is the reason this is not `tools.describe()`: "which of
930
+ * these can this daemon run, and whose code is it" is the question the
931
+ * catalog endpoint and 07's tool screens are asking, and a descriptor
932
+ * list answers only half of it. Kernel tools have no package, which is
933
+ * itself the answer for them.
934
+ */
935
+ tools(): GatewayTool[];
936
+ /**
937
+ * Every skill this daemon serves — operator-installed and
938
+ * plugin-contributed — with the ids an allowlist can name. Descriptors
939
+ * only: bodies stay behind `skill.read`, on the agent's own turn.
940
+ */
941
+ skills(): GatewaySkill[];
942
+ /**
943
+ * The plugins this daemon loaded, and the ones it could not — a plugin an
944
+ * operator enabled that failed to load is exactly what they need to see,
945
+ * and an empty list would say the opposite.
946
+ */
947
+ plugins(): GatewayPluginStatus[];
948
+ /**
949
+ * Every provider a soul's `provider:` can select on this daemon: the
950
+ * built-ins, then what loaded plugins registered, in load order. The
951
+ * registry, not the literal — a surface listing providers reads this so
952
+ * a contributed one is offered where the built-ins are.
953
+ */
954
+ providers(): GatewayProviderStatus[];
955
+ /**
956
+ * The soul behind each agent in the current roster, built-in included
957
+ * (which has none).
958
+ *
959
+ * The roster the daemon is *serving*, which the agents directory stops
960
+ * describing the moment a soul is added, deleted, or broken since the last
961
+ * reload — and the gateway keeps dispatching from the soul it cached. A
962
+ * caller that has to answer "what would a turn as this agent run on" needs
963
+ * the pins that answer it, so this hands back the parsed soul rather than a
964
+ * path a second reader would have to re-read (and now read differently).
965
+ *
966
+ * Async because it re-reads each soul file, exactly as `refreshAgent` does
967
+ * before every dispatch: a pin edited on disk is live on the next turn, so
968
+ * a caller answering from the load-time snapshot would name the provider
969
+ * the daemon has already stopped billing. An unreadable file falls back to
970
+ * the cached soul, which is what that next dispatch falls back to as well.
971
+ *
972
+ * An agent is **absent** from this list when its file now declares a
973
+ * different id: `refreshAgent` refuses every dispatch for the old one until
974
+ * the roster reloads, so nothing is served under it and no runtime should
975
+ * be claimed for it. The roster it reports is therefore what can currently
976
+ * run, which is a narrower thing than `agents()`.
977
+ */
978
+ servedSouls(): Promise<Array<{
979
+ id: string;
980
+ soul?: ParsedSoul;
981
+ }>>;
982
+ /**
983
+ * Where a durable session came from — its agent, and the metadata the
984
+ * dispatching surface attached to it.
985
+ *
986
+ * A projection rather than an accessor: the session's messages are not
987
+ * a channel's business, and handing back the whole record to save a few
988
+ * lines would make them one.
989
+ */
990
+ sessionRouting(sessionId: string): Promise<SessionRouting | undefined>;
991
+ /**
992
+ * Whether a session already accepted the message an idempotency key names,
993
+ * as addressed to it: a turn was started for it, finished, running or
994
+ * still queued, here or in the transcript a recent rollover archived
995
+ * (`settleRepeat` reads the same). A turn nobody asked for
996
+ * (`addressed: false`) does not count — that agent heard the message, it
997
+ * did not take it. Lets a channel route a redelivered message to the agent
998
+ * that accepted it rather than to whoever its routing rule picks now.
999
+ */
1000
+ holdsMessage(sessionId: string, idempotencyKey: string): Promise<boolean>;
1001
+ /**
1002
+ * Start a conversation over under the same id, leaving its transcript so
1003
+ * far behind as an archived session.
1004
+ *
1005
+ * The remedy for a session whose trust label cannot honestly be raised. A
1006
+ * session from before provenance was tracked reads `unknown`, the label
1007
+ * never rises, and some sessions never end — a Slack DM is one resumable
1008
+ * session for the life of the install — so without this an operator's
1009
+ * own conversation would write `unknown` forever, however carefully the
1010
+ * memory it injects was re-asserted. A session boundary is the only move
1011
+ * that neither strands the conversation nor quietly raises trust over a
1012
+ * transcript nobody re-read: the old session stays exactly what it was,
1013
+ * under `<id>:rolledover:<time>-<suffix>`, and the fresh one starts at the top of
1014
+ * the lattice with only the routing metadata the channel needs to keep
1015
+ * finding it. The fresh session is still `unknown` on its first turn if
1016
+ * the memory it injects is — correctly, and that is the case that
1017
+ * catches anyone who thinks the rollover alone is the remedy.
1018
+ *
1019
+ * Refuses a session with a turn in flight: the runner holds the live
1020
+ * object and would save the old transcript back over the fresh row.
1021
+ */
1022
+ rolloverSession(sessionId: string): Promise<{
1023
+ sessionId: string;
1024
+ archivedAs: string;
1025
+ }>;
1026
+ /**
1027
+ * Settles a parked call. Returns false when the request is not pending —
1028
+ * already decided, expired, or belonging to a turn that was cancelled —
1029
+ * which is the normal outcome of a button clicked a minute too late, not
1030
+ * an error. Callers surface it to whoever clicked; they must never treat
1031
+ * it as "try again".
1032
+ */
1033
+ resolveApproval(input: ResolveApprovalInput): boolean;
1034
+ /** Whether the daemon is taking new work. */
1035
+ intake(): IntakeState;
1036
+ /**
1037
+ * Stop taking new work without stopping. Turns already running or queued
1038
+ * finish; a new dispatch is refused with `IntakePausedError` carrying
1039
+ * `message` (or `DEFAULT_PAUSED_MESSAGE`); due recurring schedules are
1040
+ * skipped and one-shots wait. Channels stay connected, so they can say
1041
+ * so. Durable: the pause outlives a restart until `resumeIntake`.
1042
+ * Pausing again replaces the message and keeps the original `since`.
1043
+ */
1044
+ pauseIntake(input?: {
1045
+ message?: string;
1046
+ }): Promise<IntakeState>;
1047
+ /** Take new work again. Idempotent. */
1048
+ resumeIntake(): Promise<IntakeState>;
1049
+ /** Whether the gateway started held and has not been promoted yet. */
1050
+ held(): boolean;
1051
+ /**
1052
+ * End a held start: connect the remaining channels, start the scheduler,
1053
+ * and run the recovery sweeps a normal start runs. Resolves once the
1054
+ * channels are up. A no-op on a gateway that is not held. A required
1055
+ * channel that fails to start rejects, and the gateway stays held.
1056
+ */
1057
+ promote(): Promise<void>;
1058
+ /**
1059
+ * Answers a `credential.requested` with the value an approver entered:
1060
+ * stores it add-only (`addNamedCredential`), under the agent or the fleet
1061
+ * as the request asked, and adds the name to the requesting agent's soul.
1062
+ * The value goes to the credential store and nowhere else — not onto the
1063
+ * bus, the log, or the session.
1064
+ *
1065
+ * Who may answer is the channel's question, as it is for approvals.
1066
+ */
1067
+ provideCredential(input: ProvideCredentialInput): Promise<ProvideCredentialResult>;
1068
+ }
1069
+ export interface ProvideCredentialInput {
1070
+ requestId: string;
1071
+ /**
1072
+ * As entered, line breaks included: a key file such as a PEM private key
1073
+ * is stored with the ones inside it. Line breaks at either end are
1074
+ * dropped, the way `stratus credential set` drops a trailing one.
1075
+ */
1076
+ value: string;
1077
+ /** Who provided it. Channel-native id (a Slack user). */
1078
+ actor?: string;
1079
+ }
1080
+ /**
1081
+ * `ok: false` carries a sentence for the person who entered the value: the
1082
+ * request is gone, the name is already stored, or the value is empty or a
1083
+ * PEM key whose line breaks were lost.
1084
+ * `granted: false` on success means the soul already listed the name.
1085
+ *
1086
+ * `retired: true` means the request is no longer pending and no answer
1087
+ * can change that, so a channel should take its form down: the name was
1088
+ * stored since (add-only means no retry can land), the soul went to another
1089
+ * agent, or the request was already answered. Without it the refusal is
1090
+ * one a corrected value can clear, like an empty one, and the form stays.
1091
+ */
1092
+ export type ProvideCredentialResult = {
1093
+ ok: true;
1094
+ name: string;
1095
+ scope: CredentialScope;
1096
+ agentId: string;
1097
+ granted: boolean;
1098
+ } | {
1099
+ ok: false;
1100
+ message: string;
1101
+ retired?: boolean;
1102
+ };
1103
+ //# sourceMappingURL=types.d.ts.map