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