@stratusagent/gateway 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +90 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +580 -37
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/dist/tsconfig.test.tsbuildinfo +0 -1
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ One gateway owns:
|
|
|
7
7
|
- **Durable sessions** — `SqliteSessionStore` on `node:sqlite` (Node 22.13+, no native deps). The whole session round-trips as one JSON body, provider replay state included, so a conversation resumed after a daemon restart replays exactly. Session ids are caller-chosen and stable (channels derive them from conversations), which is what makes any inbound message resumable.
|
|
8
8
|
- **A per-provider runner pool** — each agent's soul pins its own provider/model; config re-resolves on every dispatch and runners are pooled by resolved configuration, so two agents never share a billing path or credentials by accident.
|
|
9
9
|
- **Dispatcher-backed delegation** — `agent.delegate` routes the target through the same per-provider dispatch as a direct message; a delegated specialist runs on *their* provider, never the delegator's.
|
|
10
|
+
- **Approval recovery** — a turn parked on a human is a durable checkpoint, not a lost turn. The runner records the parked call and the calls queued behind it *before* asking, and clears it before the tool runs, so the record covers exactly the window in which nothing has happened. On start the gateway sweeps for `pending_approval` sessions and finishes them: the parked call is re-asked (or refused, if its window ran out while the process was down), and the queue behind it drains, so every `tool_use` still ends with a `tool_result`. Earlier calls are never replayed — their results were already durable, and a re-asked request keeps the remainder of its original window rather than starting a new one. Calls made through a provider that drives its own inner loop are deliberately excluded: recovery re-enters the *kernel* loop and cannot rebuild that provider's, so those fail cleanly instead (see [04](../../docs/roadmap/04-agent-sdk-bridge.md)).
|
|
10
11
|
- **An activity watchdog** — progress-based, not wall-clock: any event from the session resets it, and an idle turn is aborted cleanly (the abort cancels the underlying provider request and kills tool subprocesses).
|
|
11
12
|
- **Single-flight per session** — a second message to a busy session queues behind the in-flight turn; different sessions run concurrently.
|
|
12
13
|
- **Live-refresh** — souls are re-read on each dispatch, so an edited persona or allowlist reaches existing conversations on their next turn. Sessions pin an agent *id* and never cross identities.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EventBus, type AgentDefinition, type ApprovalPolicy, type JsonObject, type Session, type SessionStore } from '@stratusagent/core';
|
|
1
|
+
import { EventBus, type AgentDefinition, type ApprovalAnswer, type ApprovalPolicy, type JsonObject, type Session, type SessionStatus, type SessionStore, type ToolRisk } from '@stratusagent/core';
|
|
2
2
|
import { type ParsedSoul } from '@stratusagent/agents';
|
|
3
3
|
import { type RuntimeSelection, type StateEnvironment } from '@stratusagent/state';
|
|
4
4
|
/**
|
|
@@ -24,6 +24,12 @@ export declare class SqliteSessionStore implements SessionStore {
|
|
|
24
24
|
create(input: Omit<Session, 'createdAt' | 'updatedAt'>): Promise<Session>;
|
|
25
25
|
get(id: string): Promise<Session | undefined>;
|
|
26
26
|
save(session: Session): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Session ids in a state, oldest first — the index a restarting daemon
|
|
29
|
+
* sweeps for turns parked on a human. The `status` column carries it, so
|
|
30
|
+
* no conversation body is deserialized to answer the question.
|
|
31
|
+
*/
|
|
32
|
+
listIdsByStatus(status: SessionStatus): Promise<string[]>;
|
|
27
33
|
/** Newest-first session listing for one agent (or all agents). */
|
|
28
34
|
list(agentId?: string): Array<Pick<Session, 'id' | 'status' | 'createdAt' | 'updatedAt'> & {
|
|
29
35
|
agentId: string;
|
|
@@ -63,13 +69,77 @@ export interface GatewayChannelAdapter {
|
|
|
63
69
|
start(gateway: Gateway): Promise<void>;
|
|
64
70
|
stop(): Promise<void>;
|
|
65
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* What the gateway hands a policy that needs a human somewhere else: one
|
|
74
|
+
* function that publishes a request and settles on the answer. Handed to a
|
|
75
|
+
* factory rather than exposed on the built gateway because the policy has
|
|
76
|
+
* to exist before the first dispatch, and a gateway cannot be constructed
|
|
77
|
+
* with a policy that needs the gateway.
|
|
78
|
+
*/
|
|
79
|
+
export interface ApprovalTransport {
|
|
80
|
+
request: ApprovalRequester;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A parked call, published for anyone who can reach a person. Mirrors
|
|
84
|
+
* `@stratusagent/permissions`' `ApprovalRequest` structurally rather than
|
|
85
|
+
* importing it: the gateway consumes any `ApprovalPolicy`, and depending on
|
|
86
|
+
* one implementation of the seam to describe the seam would invert that.
|
|
87
|
+
*/
|
|
88
|
+
export interface GatewayApprovalRequest {
|
|
89
|
+
session: Session;
|
|
90
|
+
call: {
|
|
91
|
+
id: string;
|
|
92
|
+
toolName: string;
|
|
93
|
+
input: JsonObject;
|
|
94
|
+
};
|
|
95
|
+
risk: ToolRisk;
|
|
96
|
+
/**
|
|
97
|
+
* When this call first parked, if a restart is re-asking it. The wait is
|
|
98
|
+
* measured from here, so a request keeps the window it started with
|
|
99
|
+
* instead of winning a fresh one — otherwise a daemon that restarts a
|
|
100
|
+
* minute before the deadline grants another full timeout, and one that
|
|
101
|
+
* crash-loops keeps a request alive forever.
|
|
102
|
+
*/
|
|
103
|
+
parkedAt?: string;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}
|
|
106
|
+
export type ApprovalRequester = (request: GatewayApprovalRequest) => Promise<ApprovalAnswer>;
|
|
107
|
+
export interface ResolveApprovalInput {
|
|
108
|
+
requestId: string;
|
|
109
|
+
answer: ApprovalAnswer;
|
|
110
|
+
/**
|
|
111
|
+
* The person who decided, in whatever ids their channel uses. Recorded on
|
|
112
|
+
* the resolved event; who is *allowed* to decide is the channel's
|
|
113
|
+
* question, since the approver set is written in its ids.
|
|
114
|
+
*/
|
|
115
|
+
actor?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Why, when it was not a person. A channel that cannot deliver a request
|
|
118
|
+
* — nobody configured to ask, nowhere to ask — still has to settle it,
|
|
119
|
+
* and recording that as `decided` would file a denial nobody made
|
|
120
|
+
* alongside the ones somebody did. `timeout` and `cancelled` are not
|
|
121
|
+
* offered here: those are the gateway's own endings to declare.
|
|
122
|
+
*/
|
|
123
|
+
reason?: 'decided' | 'undeliverable';
|
|
124
|
+
}
|
|
66
125
|
export interface GatewayOptions {
|
|
67
126
|
env?: StateEnvironment;
|
|
68
127
|
/** Gateway-wide provider/model overrides applied beneath per-soul pins. */
|
|
69
128
|
selection?: RuntimeSelection;
|
|
70
129
|
/** Channel adapters to run (Slack, …). Started on start(), stopped on stop(). */
|
|
71
130
|
channels?: GatewayChannelAdapter[];
|
|
72
|
-
|
|
131
|
+
/**
|
|
132
|
+
* The policy, or a factory that builds one from the gateway's approval
|
|
133
|
+
* transport. Pass a factory whenever the policy parks turns on a human
|
|
134
|
+
* reached through a channel — `remote` mode — since that policy needs the
|
|
135
|
+
* transport the gateway is still constructing.
|
|
136
|
+
*/
|
|
137
|
+
approvals?: ApprovalPolicy | ((transport: ApprovalTransport) => ApprovalPolicy);
|
|
138
|
+
/**
|
|
139
|
+
* How long a parked call waits before it denies itself. 0 waits forever,
|
|
140
|
+
* which is only ever right in a test. Default 15 minutes.
|
|
141
|
+
*/
|
|
142
|
+
approvalTimeoutMs?: number;
|
|
73
143
|
maxTurns?: number;
|
|
74
144
|
/**
|
|
75
145
|
* The activity watchdog: abort a turn when no event for its session has
|
|
@@ -106,7 +176,25 @@ export interface Gateway {
|
|
|
106
176
|
readonly store: SqliteSessionStore;
|
|
107
177
|
/** The current roster, default agent included. */
|
|
108
178
|
agents(): AgentDefinition[];
|
|
179
|
+
/**
|
|
180
|
+
* Settles a parked call. Returns false when the request is not pending —
|
|
181
|
+
* already decided, expired, or belonging to a turn that was cancelled —
|
|
182
|
+
* which is the normal outcome of a button clicked a minute too late, not
|
|
183
|
+
* an error. Callers surface it to whoever clicked; they must never treat
|
|
184
|
+
* it as "try again".
|
|
185
|
+
*/
|
|
186
|
+
resolveApproval(input: ResolveApprovalInput): boolean;
|
|
109
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Recorded on a turn the previous process was still running when it died.
|
|
190
|
+
*
|
|
191
|
+
* Exported because "distinguishable" is the whole point: a surface, a
|
|
192
|
+
* test, or an operator reading `lastError` has to be able to tell a turn
|
|
193
|
+
* nobody finished apart from one a provider or a tool actually failed, and
|
|
194
|
+
* matching on prose that lives in one place beats each caller inventing
|
|
195
|
+
* its own guess at the wording.
|
|
196
|
+
*/
|
|
197
|
+
export declare const ABANDONED_TURN_ERROR = "stratusd stopped while this turn was still running; it was not resumed. Send the message again.";
|
|
110
198
|
/**
|
|
111
199
|
* The always-on Stratus process. One gateway holds the roster, the durable
|
|
112
200
|
* session store, the shared event bus, and a pool of runners keyed by
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|