@oh-hai/cli 0.2.3 → 0.3.1
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/CHANGELOG.md +125 -0
- package/README.md +59 -29
- package/dist/commands/ask.js +71 -10
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +502 -0
- package/dist/commands/bridge.js.map +1 -0
- package/dist/commands/context.d.ts +22 -0
- package/dist/commands/doctor.js +12 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/fleet.d.ts +2 -0
- package/dist/commands/fleet.js +164 -0
- package/dist/commands/fleet.js.map +1 -0
- package/dist/commands/handlers.js +8 -0
- package/dist/commands/handlers.js.map +1 -1
- package/dist/commands/messages.d.ts +2 -0
- package/dist/commands/messages.js +131 -0
- package/dist/commands/messages.js.map +1 -0
- package/dist/commands/messaging/build.d.ts +10 -1
- package/dist/commands/messaging/build.js +22 -4
- package/dist/commands/messaging/build.js.map +1 -1
- package/dist/commands/messaging/capability.d.ts +42 -0
- package/dist/commands/messaging/capability.js +164 -0
- package/dist/commands/messaging/capability.js.map +1 -0
- package/dist/commands/messaging/http.d.ts +28 -1
- package/dist/commands/messaging/http.js +234 -13
- package/dist/commands/messaging/http.js.map +1 -1
- package/dist/commands/messaging/inbox-entries.d.ts +83 -0
- package/dist/commands/messaging/inbox-entries.js +371 -0
- package/dist/commands/messaging/inbox-entries.js.map +1 -0
- package/dist/commands/messaging/inbox-stream.d.ts +37 -0
- package/dist/commands/messaging/inbox-stream.js +224 -0
- package/dist/commands/messaging/inbox-stream.js.map +1 -0
- package/dist/commands/messaging/session-drain.d.ts +50 -0
- package/dist/commands/messaging/session-drain.js +163 -0
- package/dist/commands/messaging/session-drain.js.map +1 -0
- package/dist/commands/messaging/session-scope.d.ts +93 -0
- package/dist/commands/messaging/session-scope.js +0 -0
- package/dist/commands/messaging/session-scope.js.map +1 -0
- package/dist/commands/messaging/sessions-http.d.ts +20 -0
- package/dist/commands/messaging/sessions-http.js +153 -0
- package/dist/commands/messaging/sessions-http.js.map +1 -0
- package/dist/commands/messaging/shared.d.ts +19 -1
- package/dist/commands/messaging/shared.js +69 -2
- package/dist/commands/messaging/shared.js.map +1 -1
- package/dist/commands/messaging/validate.d.ts +33 -1
- package/dist/commands/messaging/validate.js +78 -0
- package/dist/commands/messaging/validate.js.map +1 -1
- package/dist/commands/messaging/wire.d.ts +180 -3
- package/dist/commands/notify.js +47 -5
- package/dist/commands/notify.js.map +1 -1
- package/dist/commands/registry.js +128 -3
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/self-test.d.ts +17 -0
- package/dist/commands/self-test.js +472 -0
- package/dist/commands/self-test.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +158 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.js +44 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/task.js +61 -11
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/teach.js +84 -4
- package/dist/commands/teach.js.map +1 -1
- package/dist/commands/whoami.js +14 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/exit-codes.d.ts +37 -1
- package/dist/exit-codes.js +42 -1
- package/dist/exit-codes.js.map +1 -1
- package/dist/help.js +5 -1
- package/dist/help.js.map +1 -1
- package/dist/watch-lock.js +7 -0
- package/dist/watch-lock.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// The client for the §8.7.2 SSE binding — `GET /v1/inbox/stream?session=…` (issue #658).
|
|
2
|
+
//
|
|
3
|
+
// THE ONE THING A READER MUST TAKE FROM THIS FILE: the Hub closes every hold on purpose, and that
|
|
4
|
+
// close is NOT an error.
|
|
5
|
+
//
|
|
6
|
+
// SSE is unidirectional and TCP is half-open, so an open socket proves nothing — the Hub cannot tell
|
|
7
|
+
// a live consumer from a crashed process behind a NAT whose socket still accepts buffered writes.
|
|
8
|
+
// An open connection therefore renews NOTHING: not the §16 session lease, not §15 presence. Instead
|
|
9
|
+
// each hold is BOUNDED, closed with an explicit `event: reconnect {"reason":"hold_bound"}`, and the
|
|
10
|
+
// client's RECONNECT is the renewal. A consumer that treated that close as a fault and backed off
|
|
11
|
+
// exponentially would let its OWN lease lapse and get its mail bounced (§14.2) — the precise failure
|
|
12
|
+
// the design exists to prevent, arrived at by "being careful". So the close is surfaced here as a
|
|
13
|
+
// first-class `reconnect` event rather than as an end-of-stream a caller has to interpret.
|
|
14
|
+
//
|
|
15
|
+
// The transport is `ctx.runtime.fetchImpl`, like every other Hub call, so the bridge tests stay
|
|
16
|
+
// hermetic — a fake response hands back a three-method reader and the whole connection lifecycle is
|
|
17
|
+
// exercised with no socket. That is why `HttpResponse.body` is typed structurally (context.ts).
|
|
18
|
+
import { originOf } from "../../auth/resolve-token.js";
|
|
19
|
+
import { CliError } from "../../envelope.js";
|
|
20
|
+
import { throwTransportError } from "../http.js";
|
|
21
|
+
import { redactToken } from "./http.js";
|
|
22
|
+
import { isInboxEntryDelivery } from "./inbox-entries.js";
|
|
23
|
+
import { sessionStatusToCliError } from "./session-drain.js";
|
|
24
|
+
/** Slack added to a hold's own bound before the client gives up on a silent socket. Covers the
|
|
25
|
+
* Hub's 15s keep-alive tick plus network jitter; past it the connection is wedged rather than held,
|
|
26
|
+
* and the bridge must reconnect rather than sit there looking healthy. */
|
|
27
|
+
const HOLD_GRACE_MS = 20_000;
|
|
28
|
+
/** The deadline used between "headers arrived" and the `open` event that states the real bound.
|
|
29
|
+
* A Hub that opens a stream and never says anything is exactly the mute failure this file exists
|
|
30
|
+
* to make impossible. */
|
|
31
|
+
const PRE_OPEN_DEADLINE_MS = 30_000;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve the stream URL for this Hub, or `undefined` when it offers no stream.
|
|
34
|
+
*
|
|
35
|
+
* The advertised `inbound.stream_url` is honoured but PINNED TO THE HUB'S ORIGIN. That check is not
|
|
36
|
+
* pedantry: the request carries the agent bearer, and an advertised URL is a value the Hub controls,
|
|
37
|
+
* so an off-origin one would be a capability document redirecting a credential to a third party. A
|
|
38
|
+
* conformant Hub advertises its own absolute URL, so the check costs nothing and closes the hole.
|
|
39
|
+
*/
|
|
40
|
+
export function streamUrlFor(ctx, capability) {
|
|
41
|
+
const advertised = capability?.inbound?.stream_url;
|
|
42
|
+
if (typeof advertised !== "string" || advertised === "")
|
|
43
|
+
return undefined;
|
|
44
|
+
if (originOf(advertised) !== originOf(ctx.config.baseUrl)) {
|
|
45
|
+
throw new CliError("server", `refusing to open the inbox stream: the Hub advertises inbound.stream_url on a different origin (${originOf(advertised)}) ` +
|
|
46
|
+
`than the configured Hub (${originOf(ctx.config.baseUrl)}). The stream request carries this agent's bearer, so an ` +
|
|
47
|
+
"off-origin stream URL is a credential redirect, not a convenience.");
|
|
48
|
+
}
|
|
49
|
+
return advertised;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Open one HOLD and yield its events, ending when the Hub closes it.
|
|
53
|
+
*
|
|
54
|
+
* Ends NORMALLY (no throw) on a `reconnect` event or a clean EOF — a bounded hold's whole job is to
|
|
55
|
+
* end. Throws a `CliError` on a genuine fault: `auth` / `session_terminal` from the pre-flight
|
|
56
|
+
* status (see `sessionStatusToCliError`), or `network` / `timeout` / `server` from the transport,
|
|
57
|
+
* which are the codes the bridge treats as retryable.
|
|
58
|
+
*
|
|
59
|
+
* The caller drives the reconnect. Deliberately: only the caller knows whether the close was the
|
|
60
|
+
* bound (reconnect at once) or a fault (back off), and that decision is the reliability contract.
|
|
61
|
+
*/
|
|
62
|
+
export async function* readInboxStream(ctx, token, session, streamUrl) {
|
|
63
|
+
const url = `${streamUrl}${streamUrl.includes("?") ? "&" : "?"}session=${encodeURIComponent(session)}`;
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
// ONE deadline per hold, armed before the request and never extended by an arriving event. A
|
|
66
|
+
// per-event reset would let a chatty-but-wedged Hub hold the socket indefinitely, which is the
|
|
67
|
+
// zombie the §8.7.2 bound exists to kill — from the other end.
|
|
68
|
+
let deadline;
|
|
69
|
+
const armDeadline = (ms) => {
|
|
70
|
+
if (deadline !== undefined)
|
|
71
|
+
clearTimeout(deadline);
|
|
72
|
+
deadline = setTimeout(() => controller.abort(), ms);
|
|
73
|
+
deadline.unref?.();
|
|
74
|
+
};
|
|
75
|
+
let reader;
|
|
76
|
+
try {
|
|
77
|
+
armDeadline(PRE_OPEN_DEADLINE_MS);
|
|
78
|
+
let res;
|
|
79
|
+
try {
|
|
80
|
+
res = await ctx.runtime.fetchImpl(url, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "text/event-stream" },
|
|
83
|
+
signal: controller.signal,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
88
|
+
}
|
|
89
|
+
if (res.status < 200 || res.status >= 300) {
|
|
90
|
+
const { code, message } = await readErrorEnvelope(res);
|
|
91
|
+
throw sessionStatusToCliError(res.status, redactToken(message, token), code, session);
|
|
92
|
+
}
|
|
93
|
+
const body = res.body;
|
|
94
|
+
if (body === null || body === undefined) {
|
|
95
|
+
// A 2xx with no readable body is not a held stream — treat it as a transport fault so the
|
|
96
|
+
// bridge retries (and, after the budget, exits loudly) rather than spinning on empty holds.
|
|
97
|
+
throw new CliError("server", "the Hub accepted the stream request but returned no readable body.");
|
|
98
|
+
}
|
|
99
|
+
reader = body.getReader();
|
|
100
|
+
const decoder = new TextDecoder();
|
|
101
|
+
let buffer = "";
|
|
102
|
+
let frame = { data: [] };
|
|
103
|
+
for (;;) {
|
|
104
|
+
let chunk;
|
|
105
|
+
try {
|
|
106
|
+
chunk = await reader.read();
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// An abort here is OUR deadline firing (a wedged hold), which is a timeout, not a close.
|
|
110
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
111
|
+
}
|
|
112
|
+
if (chunk.done)
|
|
113
|
+
return; // clean EOF — the hold ended without an explicit reconnect event
|
|
114
|
+
buffer += chunk.value !== undefined ? decoder.decode(chunk.value, { stream: true }) : "";
|
|
115
|
+
// SSE framing: `\n`-delimited lines, a blank line dispatches the accumulated frame. `\r\n` is
|
|
116
|
+
// normalized first so a proxy that rewrites line endings cannot desynchronize the parser.
|
|
117
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
118
|
+
let nl = buffer.indexOf("\n");
|
|
119
|
+
while (nl >= 0) {
|
|
120
|
+
const line = buffer.slice(0, nl);
|
|
121
|
+
buffer = buffer.slice(nl + 1);
|
|
122
|
+
nl = buffer.indexOf("\n");
|
|
123
|
+
if (line === "") {
|
|
124
|
+
const dispatched = frame;
|
|
125
|
+
frame = { data: [] };
|
|
126
|
+
if (dispatched.event === undefined && dispatched.data.length === 0)
|
|
127
|
+
continue;
|
|
128
|
+
const parsed = parseFrame(dispatched.event, dispatched.data.join("\n"));
|
|
129
|
+
if (parsed === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
if (parsed.type === "open")
|
|
132
|
+
armDeadline(parsed.holdMs + HOLD_GRACE_MS);
|
|
133
|
+
yield parsed;
|
|
134
|
+
// `reconnect` is terminal for this hold by contract; stop reading rather than waiting for
|
|
135
|
+
// the socket to notice, so the caller reconnects at once (the connect IS the renewal).
|
|
136
|
+
if (parsed.type === "reconnect")
|
|
137
|
+
return;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
// `:` comment (the Hub's keep-alive tick) — carries no meaning by design (§8.7.2: the tick
|
|
141
|
+
// re-checks liveness, it is never evidence of anything), so it is dropped, not counted.
|
|
142
|
+
if (line.startsWith(":"))
|
|
143
|
+
continue;
|
|
144
|
+
const colon = line.indexOf(":");
|
|
145
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
146
|
+
const value = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
147
|
+
if (field === "event")
|
|
148
|
+
frame.event = value;
|
|
149
|
+
else if (field === "data")
|
|
150
|
+
frame.data.push(value);
|
|
151
|
+
// `retry:` is the Hub's advice to a BROWSER EventSource. The bridge owns its own reconnect
|
|
152
|
+
// policy (immediate on the bound, backed off on a fault), so the field is read and ignored
|
|
153
|
+
// rather than allowed to override a policy that carries the reliability contract.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
if (deadline !== undefined)
|
|
159
|
+
clearTimeout(deadline);
|
|
160
|
+
// Abort first, then cancel: aborting releases the socket even if the reader is mid-`read()`.
|
|
161
|
+
controller.abort();
|
|
162
|
+
await reader?.cancel?.().catch(() => { });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Turn one dispatched SSE frame into an event, or undefined for a frame this client ignores
|
|
166
|
+
* (an unknown event name — §10 robustness — or one whose `data` is not the documented object). */
|
|
167
|
+
function parseFrame(event, data) {
|
|
168
|
+
if (event === undefined)
|
|
169
|
+
return undefined;
|
|
170
|
+
let payload;
|
|
171
|
+
try {
|
|
172
|
+
payload = JSON.parse(data);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload))
|
|
178
|
+
return undefined;
|
|
179
|
+
const record = payload;
|
|
180
|
+
if (event === "open") {
|
|
181
|
+
// `hold_ms` is the bound for THIS hold and may be shorter than the advertised ceiling (it is the
|
|
182
|
+
// lesser of that ceiling and a margin before the lease expiry), so anything scheduled against a
|
|
183
|
+
// hold uses it — never `stream_max_hold_seconds`.
|
|
184
|
+
const holdMs = typeof record.hold_ms === "number" && Number.isFinite(record.hold_ms) && record.hold_ms >= 0
|
|
185
|
+
? record.hold_ms
|
|
186
|
+
: PRE_OPEN_DEADLINE_MS;
|
|
187
|
+
return {
|
|
188
|
+
type: "open",
|
|
189
|
+
holdMs,
|
|
190
|
+
...(typeof record.stream_max_hold_seconds === "number" ? { streamMaxHoldSeconds: record.stream_max_hold_seconds } : {}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (event === "reconnect") {
|
|
194
|
+
return { type: "reconnect", reason: typeof record.reason === "string" ? record.reason : "unspecified" };
|
|
195
|
+
}
|
|
196
|
+
if (event === "entries") {
|
|
197
|
+
const messages = record.messages;
|
|
198
|
+
if (!Array.isArray(messages) || !messages.every(isInboxEntryDelivery)) {
|
|
199
|
+
// A malformed push is a Hub/proxy contract violation. Fatal rather than skipped, for the same
|
|
200
|
+
// reason the drain's guard rejects the whole batch: silently treating garbage as "no entries"
|
|
201
|
+
// strands real mail while the bridge reports itself healthy.
|
|
202
|
+
throw new CliError("server", "the Hub pushed a malformed stream frame (expected { messages: [{ <directive|message|response|receipt>, signature }] }).");
|
|
203
|
+
}
|
|
204
|
+
return { type: "entries", entries: messages };
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
async function readErrorEnvelope(res) {
|
|
209
|
+
if (typeof res.json !== "function")
|
|
210
|
+
return { code: undefined, message: undefined };
|
|
211
|
+
try {
|
|
212
|
+
const parsed = await res.json();
|
|
213
|
+
const body = parsed !== null && typeof parsed === "object" ? parsed : {};
|
|
214
|
+
const error = body.error;
|
|
215
|
+
const record = error !== null && typeof error === "object" ? error : {};
|
|
216
|
+
const message = typeof record.message === "string" && record.message.trim() !== "" ? record.message.trim() : undefined;
|
|
217
|
+
const code = typeof record.code === "string" && record.code.trim() !== "" ? record.code.trim() : undefined;
|
|
218
|
+
return { code, message };
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { code: undefined, message: undefined };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=inbox-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox-stream.js","sourceRoot":"","sources":["../../../src/commands/messaging/inbox-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CliError } from "../../envelope.js";
|
|
2
|
+
import type { CommandContext } from "../context.js";
|
|
3
|
+
import type { InboxEntryDelivery } from "./wire.js";
|
|
4
|
+
/**
|
|
5
|
+
* Map a Hub status on a SESSION-PRESENTING call to the bridge's §7 code.
|
|
6
|
+
*
|
|
7
|
+
* The two arms that differ from the generic mapping are the point:
|
|
8
|
+
*
|
|
9
|
+
* - **401/403 → `auth` (exit 3), and it is FATAL.** A revoked or wrong credential is not a transient
|
|
10
|
+
* condition; retrying it produces an identical refusal forever while looking, from the outside,
|
|
11
|
+
* exactly like a healthy bridge waiting for mail. Exit loudly instead.
|
|
12
|
+
* - **410 with `session_closed_by_operator` → `session_closed_by_operator` (exit 14). A STOP.**
|
|
13
|
+
* §16.4's kill-switch is how an account human stops a wedged agent without impersonating its
|
|
14
|
+
* decisions. Before the Hub distinguished it, it arrived as the same 410 a lapsed lease does, so a
|
|
15
|
+
* supervised bridge restarted straight through a deliberate human stop and re-registered — the
|
|
16
|
+
* operator's action silently undone. This arm is what makes the kill-switch actually stop
|
|
17
|
+
* something: exit 14 is NOT in any restart class, and a supervisor must leave the bridge down.
|
|
18
|
+
* - **404/other 410 → `session_terminal` (exit 11).** §16.3: 410 is "your OWN session is terminal"
|
|
19
|
+
* (here, a lapsed lease), 404 is "unknown, or another principal's" — deliberately
|
|
20
|
+
* indistinguishable. Both mean the same thing to a bridge: the addressing scope it presents is
|
|
21
|
+
* unusable, and the fix is a RESTART that registers a fresh one. Leaving them on the generic
|
|
22
|
+
* `not_found` (exit 4) would tell a supervisor "you named something that does not exist" — true,
|
|
23
|
+
* but useless, because a supervisor cannot tell that from a mistyped message id.
|
|
24
|
+
*
|
|
25
|
+
* Everything else defers to the shared mapping, so `network`/`server`/`timeout` keep their §7 codes
|
|
26
|
+
* and stay in the bridge's RETRYABLE set.
|
|
27
|
+
*/
|
|
28
|
+
export declare function sessionStatusToCliError(status: number, message: string | undefined, hubCode: string | undefined, session: string): CliError;
|
|
29
|
+
export declare function isRetryable(error: unknown): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `GET /v1/inbox?session=…` — the §8.7.1 session-scoped drain. Renews the lease (§16.2) and returns
|
|
32
|
+
* the signed entries.
|
|
33
|
+
*
|
|
34
|
+
* The shape guard rejects the WHOLE batch rather than skipping a bad element, mirroring
|
|
35
|
+
* `drainInbox`: a malformed body is a Hub/proxy contract violation, and silently treating garbage as
|
|
36
|
+
* "no entries" would strand real mail while reporting success. Note it also catches the pre-v0.5
|
|
37
|
+
* misroute that `assertHubSpeaksSessionDrain` gates against — a Hub that ignored `?session=` answers
|
|
38
|
+
* `{ directives: … }`, which has no `messages` array — but the gate is what prevents that drain from
|
|
39
|
+
* being issued at all, because by the time this guard fires the claim has already happened.
|
|
40
|
+
*/
|
|
41
|
+
export declare function drainInboxSession(ctx: CommandContext, token: string, session: string, max?: number): Promise<InboxEntryDelivery[]>;
|
|
42
|
+
/**
|
|
43
|
+
* `POST /v1/inbox/ack?session=…` — consume the handed-off entries (§8.7.1 / §14).
|
|
44
|
+
*
|
|
45
|
+
* A pushed entry that is never acked reverts to queued-visible and its sender is truthfully told it
|
|
46
|
+
* was never seen (§8.7.2: a stream push advances NO delivery track). So the ack is not bookkeeping —
|
|
47
|
+
* it is the only thing that makes a stream-delivered entry count as delivered, and it must follow
|
|
48
|
+
* the durable handoff rather than precede it.
|
|
49
|
+
*/
|
|
50
|
+
export declare function ackInboxSession(ctx: CommandContext, token: string, session: string, ids: string[]): Promise<string[]>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// The SESSION-PRESENTING half of the agent mailbox — the v0.5 §8.7.1 drain and its ack
|
|
2
|
+
// (`oh-hai bridge`, issue #658).
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS IS NOT A FLAG ON `drainInbox`. The two drains are different SURFACES, not one surface
|
|
5
|
+
// with a parameter (server/src/routes/inbox.ts): session-less answers `{ directives: [...] }` — the
|
|
6
|
+
// byte-identical v0.4 body — while session-presenting answers `{ messages: [ { <kind>, signature } ] }`
|
|
7
|
+
// carrying the four §8.7.1 entry kinds. Their bodies, their shape guards and their ack keys all
|
|
8
|
+
// differ, so folding them into one function would mean a response reader that has to guess which
|
|
9
|
+
// contract it is under. `inbox watch` keeps the v0.4 reader untouched; this is the v0.5 one.
|
|
10
|
+
//
|
|
11
|
+
// EVERY CALL HERE PRESENTS `?session=`, and that is load-bearing rather than tidy: §16.2 makes a
|
|
12
|
+
// session-presenting request client-originated LEASE RENEWAL, and a session-LESS one renews nothing.
|
|
13
|
+
// During a held stream the ack may be the bridge's only client-originated traffic, so an ack that
|
|
14
|
+
// dropped the parameter would quietly stop renewing the very lease the bridge is keeping alive.
|
|
15
|
+
import { CliError } from "../../envelope.js";
|
|
16
|
+
import { throwTransportError } from "../http.js";
|
|
17
|
+
import { redactToken, requestTimeoutMs, statusToCliError } from "./http.js";
|
|
18
|
+
import { isInboxEntryDelivery } from "./inbox-entries.js";
|
|
19
|
+
/**
|
|
20
|
+
* Map a Hub status on a SESSION-PRESENTING call to the bridge's §7 code.
|
|
21
|
+
*
|
|
22
|
+
* The two arms that differ from the generic mapping are the point:
|
|
23
|
+
*
|
|
24
|
+
* - **401/403 → `auth` (exit 3), and it is FATAL.** A revoked or wrong credential is not a transient
|
|
25
|
+
* condition; retrying it produces an identical refusal forever while looking, from the outside,
|
|
26
|
+
* exactly like a healthy bridge waiting for mail. Exit loudly instead.
|
|
27
|
+
* - **410 with `session_closed_by_operator` → `session_closed_by_operator` (exit 14). A STOP.**
|
|
28
|
+
* §16.4's kill-switch is how an account human stops a wedged agent without impersonating its
|
|
29
|
+
* decisions. Before the Hub distinguished it, it arrived as the same 410 a lapsed lease does, so a
|
|
30
|
+
* supervised bridge restarted straight through a deliberate human stop and re-registered — the
|
|
31
|
+
* operator's action silently undone. This arm is what makes the kill-switch actually stop
|
|
32
|
+
* something: exit 14 is NOT in any restart class, and a supervisor must leave the bridge down.
|
|
33
|
+
* - **404/other 410 → `session_terminal` (exit 11).** §16.3: 410 is "your OWN session is terminal"
|
|
34
|
+
* (here, a lapsed lease), 404 is "unknown, or another principal's" — deliberately
|
|
35
|
+
* indistinguishable. Both mean the same thing to a bridge: the addressing scope it presents is
|
|
36
|
+
* unusable, and the fix is a RESTART that registers a fresh one. Leaving them on the generic
|
|
37
|
+
* `not_found` (exit 4) would tell a supervisor "you named something that does not exist" — true,
|
|
38
|
+
* but useless, because a supervisor cannot tell that from a mistyped message id.
|
|
39
|
+
*
|
|
40
|
+
* Everything else defers to the shared mapping, so `network`/`server`/`timeout` keep their §7 codes
|
|
41
|
+
* and stay in the bridge's RETRYABLE set.
|
|
42
|
+
*/
|
|
43
|
+
export function sessionStatusToCliError(status, message, hubCode, session) {
|
|
44
|
+
if (status === 401 || status === 403) {
|
|
45
|
+
return new CliError("auth", `${message ?? `Hub returned ${status}.`} — this credential is no longer accepted. Nothing will change by retrying: ` +
|
|
46
|
+
"re-authenticate (`oh-hai login`) or issue a new agent token, then restart the bridge.");
|
|
47
|
+
}
|
|
48
|
+
// Checked BEFORE the generic terminal arm, and on the Hub's own §8 error code rather than on the
|
|
49
|
+
// message text: the code is the machine contract, and a wording change must never silently turn a
|
|
50
|
+
// stop back into a restart. A Hub that does not send it falls through to exit 11 — the pre-#683
|
|
51
|
+
// behaviour, which is the safe default for a client talking to an older Hub.
|
|
52
|
+
if (status === 410 && hubCode === "session_closed_by_operator") {
|
|
53
|
+
return new CliError("session_closed_by_operator", `the session this bridge presents (${session}) was closed by an account human — the §16.4 operator kill-switch. ` +
|
|
54
|
+
"This is a deliberate STOP, not a lapse: do NOT restart, and do NOT register a replacement session. " +
|
|
55
|
+
"Senders waiting on this agent have already been unblocked by the §14.2 bounce. Resume only when a human says so.");
|
|
56
|
+
}
|
|
57
|
+
if (status === 410 || status === 404) {
|
|
58
|
+
const which = status === 410
|
|
59
|
+
? "is terminal — its lease lapsed (§16.3)"
|
|
60
|
+
: "is unknown to the Hub, or belongs to another principal (§16.3 answers those identically)";
|
|
61
|
+
return new CliError("session_terminal", `the session this bridge presents (${session}) ${which}. Restart the bridge so it registers a fresh session; ` +
|
|
62
|
+
"if the address was published to other agents, re-publish it — a terminal session cannot be revived.");
|
|
63
|
+
}
|
|
64
|
+
return statusToCliError(status, message, hubCode);
|
|
65
|
+
}
|
|
66
|
+
/** The error codes a bridge may RETRY. Everything else is fatal by construction: it will answer the
|
|
67
|
+
* same way on the next attempt, and a retry loop around it is how a bridge goes silently dead. */
|
|
68
|
+
const RETRYABLE = new Set(["network", "server", "timeout"]);
|
|
69
|
+
export function isRetryable(error) {
|
|
70
|
+
return error instanceof CliError && RETRYABLE.has(error.code);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `GET /v1/inbox?session=…` — the §8.7.1 session-scoped drain. Renews the lease (§16.2) and returns
|
|
74
|
+
* the signed entries.
|
|
75
|
+
*
|
|
76
|
+
* The shape guard rejects the WHOLE batch rather than skipping a bad element, mirroring
|
|
77
|
+
* `drainInbox`: a malformed body is a Hub/proxy contract violation, and silently treating garbage as
|
|
78
|
+
* "no entries" would strand real mail while reporting success. Note it also catches the pre-v0.5
|
|
79
|
+
* misroute that `assertHubSpeaksSessionDrain` gates against — a Hub that ignored `?session=` answers
|
|
80
|
+
* `{ directives: … }`, which has no `messages` array — but the gate is what prevents that drain from
|
|
81
|
+
* being issued at all, because by the time this guard fires the claim has already happened.
|
|
82
|
+
*/
|
|
83
|
+
export async function drainInboxSession(ctx, token, session, max) {
|
|
84
|
+
const query = new URLSearchParams({ session });
|
|
85
|
+
if (max !== undefined)
|
|
86
|
+
query.set("max", String(max));
|
|
87
|
+
const url = `${ctx.config.baseUrl}/v1/inbox?${query.toString()}`;
|
|
88
|
+
let res;
|
|
89
|
+
try {
|
|
90
|
+
res = await ctx.runtime.fetchImpl(url, {
|
|
91
|
+
method: "GET",
|
|
92
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
93
|
+
signal: AbortSignal.timeout(requestTimeoutMs(ctx)),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
98
|
+
}
|
|
99
|
+
if (res.status < 200 || res.status >= 300) {
|
|
100
|
+
const { code, message } = await readErrorEnvelope(res);
|
|
101
|
+
throw sessionStatusToCliError(res.status, redactToken(message, token), code, session);
|
|
102
|
+
}
|
|
103
|
+
const body = await readJson(res);
|
|
104
|
+
const messages = body.messages;
|
|
105
|
+
if (!Array.isArray(messages) || !messages.every(isInboxEntryDelivery)) {
|
|
106
|
+
throw new CliError("server", "the Hub returned a malformed session drain (expected { messages: [{ <directive|message|response|receipt>, signature }] }).");
|
|
107
|
+
}
|
|
108
|
+
return messages;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `POST /v1/inbox/ack?session=…` — consume the handed-off entries (§8.7.1 / §14).
|
|
112
|
+
*
|
|
113
|
+
* A pushed entry that is never acked reverts to queued-visible and its sender is truthfully told it
|
|
114
|
+
* was never seen (§8.7.2: a stream push advances NO delivery track). So the ack is not bookkeeping —
|
|
115
|
+
* it is the only thing that makes a stream-delivered entry count as delivered, and it must follow
|
|
116
|
+
* the durable handoff rather than precede it.
|
|
117
|
+
*/
|
|
118
|
+
export async function ackInboxSession(ctx, token, session, ids) {
|
|
119
|
+
const url = `${ctx.config.baseUrl}/v1/inbox/ack?session=${encodeURIComponent(session)}`;
|
|
120
|
+
let res;
|
|
121
|
+
try {
|
|
122
|
+
res = await ctx.runtime.fetchImpl(url, {
|
|
123
|
+
method: "POST",
|
|
124
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
125
|
+
body: JSON.stringify({ ids }),
|
|
126
|
+
signal: AbortSignal.timeout(requestTimeoutMs(ctx)),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
131
|
+
}
|
|
132
|
+
if (res.status < 200 || res.status >= 300) {
|
|
133
|
+
const { code, message } = await readErrorEnvelope(res);
|
|
134
|
+
throw sessionStatusToCliError(res.status, redactToken(message, token), code, session);
|
|
135
|
+
}
|
|
136
|
+
const body = await readJson(res);
|
|
137
|
+
const acked = body.acked;
|
|
138
|
+
if (!Array.isArray(acked) || !acked.every((id) => typeof id === "string")) {
|
|
139
|
+
throw new CliError("server", "the Hub consumed the entries but returned a malformed ack result.");
|
|
140
|
+
}
|
|
141
|
+
return acked;
|
|
142
|
+
}
|
|
143
|
+
// --- tiny body readers, duplicated for the same reason sessions-http.ts duplicates them ---
|
|
144
|
+
async function readJson(res) {
|
|
145
|
+
if (typeof res.json !== "function")
|
|
146
|
+
return {};
|
|
147
|
+
try {
|
|
148
|
+
const parsed = await res.json();
|
|
149
|
+
return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function readErrorEnvelope(res) {
|
|
156
|
+
const body = await readJson(res);
|
|
157
|
+
const error = body.error;
|
|
158
|
+
const record = error !== null && typeof error === "object" ? error : {};
|
|
159
|
+
const message = typeof record.message === "string" && record.message.trim() !== "" ? record.message.trim() : undefined;
|
|
160
|
+
const code = typeof record.code === "string" && record.code.trim() !== "" ? record.code.trim() : undefined;
|
|
161
|
+
return { code, message };
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=session-drain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-drain.js","sourceRoot":"","sources":["../../../src/commands/messaging/session-drain.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CommandContext } from "../context.js";
|
|
2
|
+
import type { Session } from "./wire.js";
|
|
3
|
+
/** The flag block every messaging command adds for session scoping. Shared so `notify`, `ask
|
|
4
|
+
* submit` and `task submit` cannot drift apart on the spelling. */
|
|
5
|
+
export declare const SESSION_OPTIONS: {
|
|
6
|
+
readonly to: {
|
|
7
|
+
readonly type: "string";
|
|
8
|
+
};
|
|
9
|
+
readonly session: {
|
|
10
|
+
readonly type: "string";
|
|
11
|
+
};
|
|
12
|
+
readonly "no-session": {
|
|
13
|
+
readonly type: "boolean";
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/** How the active session id was arrived at — surfaced by `whoami` so the resolution is explicable
|
|
17
|
+
* the same way `account_source` explains the agent id. */
|
|
18
|
+
export type SessionSource = "flag" | "env" | "auto";
|
|
19
|
+
export interface SessionScope {
|
|
20
|
+
id: string;
|
|
21
|
+
source: SessionSource;
|
|
22
|
+
/** True when THIS process minted the session. Only an owned session may be closed by this
|
|
23
|
+
* process on exit — an attached one is someone else's lifecycle to end. */
|
|
24
|
+
owned: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Drop the per-process minted-session cache. Test seam only — a real process wants the cache to
|
|
27
|
+
* live exactly as long as it does. */
|
|
28
|
+
export declare function resetSessionCache(): void;
|
|
29
|
+
/** Resolve an EXPLICIT session id without any Hub call: `--session` (highest), else
|
|
30
|
+
* `MA2H_SESSION_ID`. Mirrors the agent-id precedence discipline (flag > env) so there is one
|
|
31
|
+
* mental model for "how did this resolve". Returns undefined when neither is set; throws a usage
|
|
32
|
+
* error (exit 2) when one is set to something that could never be a Hub-minted id.
|
|
33
|
+
*
|
|
34
|
+
* `--no-session` suppresses BOTH — including the env var, which is the point: a runtime that
|
|
35
|
+
* exports `MA2H_SESSION_ID` for its children must still be overridable from one call's argv.
|
|
36
|
+
*
|
|
37
|
+
* The env half is read straight off `process.env` rather than through `resolveConfig`'s §6
|
|
38
|
+
* precedence machinery, matching `resolveRunId`'s treatment of `MA2H_AGENT_RUN_ID`. That is forced,
|
|
39
|
+
* not lazy: `--session` is a PER-COMMAND flag, not one of `GLOBAL_OPTIONS`, so `resolveConfig` never
|
|
40
|
+
* sees it and could only ever own half the precedence — which would be worse than owning none. */
|
|
41
|
+
export declare function explicitSession(flags: Record<string, unknown>): {
|
|
42
|
+
id: string;
|
|
43
|
+
source: SessionSource;
|
|
44
|
+
} | undefined;
|
|
45
|
+
/**
|
|
46
|
+
* The session a submit should speak from.
|
|
47
|
+
*
|
|
48
|
+
* `mint` is the caller's answer to "does this send engage the inter-agent leg?" — true only for an
|
|
49
|
+
* addressed (`--to`) submit. When it is false and nothing is attached, the result is `undefined`
|
|
50
|
+
* and the envelope stays byte-identical to the v0.4 wire.
|
|
51
|
+
*
|
|
52
|
+
* A mint is memoized per process, so a caller that resolves twice gets one session rather than two.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveSessionScope(ctx: CommandContext, flags: Record<string, unknown>, identity: {
|
|
55
|
+
token: string;
|
|
56
|
+
agentId: string;
|
|
57
|
+
}, mint: boolean,
|
|
58
|
+
/** THIS invocation's run id — the same value the envelope stamps as `agent.run_id`. Passed in
|
|
59
|
+
* rather than re-derived, because `resolveRunId()` mints a fresh UUID when `MA2H_AGENT_RUN_ID`
|
|
60
|
+
* is unset: calling it twice would register the session under one id and send the message under
|
|
61
|
+
* another, which silently breaks the very `fleet ls` correlation this field exists to provide. */
|
|
62
|
+
runId: string): Promise<SessionScope | undefined>;
|
|
63
|
+
/** Record a mint made outside `resolveSessionScope` (i.e. by `oh-hai session start`) so a later
|
|
64
|
+
* command in the SAME process attaches to it rather than minting a second one. */
|
|
65
|
+
export declare function rememberSession(ctx: CommandContext, agentId: string, session: Session): void;
|
|
66
|
+
/**
|
|
67
|
+
* Run a submit, releasing an OWNED session if it fails.
|
|
68
|
+
*
|
|
69
|
+
* Without this, every rejected addressed submit leaks a live session: the register succeeded, the
|
|
70
|
+
* submit threw, and nothing closes it — so it holds a slot for its whole lease. A loop hitting
|
|
71
|
+
* `unknown_destination`, or a Hub that has gone unreachable, would walk straight into the §16.1
|
|
72
|
+
* live-session cap and start 429ing sends that would otherwise have worked. The failure mode is
|
|
73
|
+
* worst exactly when things are already going wrong, which is what makes it worth a wrapper rather
|
|
74
|
+
* than a comment telling each caller to remember.
|
|
75
|
+
*
|
|
76
|
+
* Only the FAILURE path is handled here. What happens to an owned session after a SUCCESSFUL submit
|
|
77
|
+
* differs by verb — `notify` closes it, `ask`/`task` keep it as the return address — so that
|
|
78
|
+
* decision stays with the caller that knows which verb it is.
|
|
79
|
+
*/
|
|
80
|
+
export declare function releasingOwnedSessionOnFailure<T>(ctx: CommandContext, token: string, scope: SessionScope | undefined, submit: () => Promise<T>): Promise<T>;
|
|
81
|
+
/**
|
|
82
|
+
* Close a session THIS process minted, best-effort, and drop it from the cache.
|
|
83
|
+
*
|
|
84
|
+
* Best-effort is the whole point of the seam: it runs AFTER the message was already accepted, so a
|
|
85
|
+
* failing close must not turn a successful send into a non-zero exit — the caller did nothing
|
|
86
|
+
* wrong, and the lease lapses on its own within the TTL either way. The failure is reported on
|
|
87
|
+
* stderr (never stdout, which under `--json` is reserved for the envelope) so it is visible without
|
|
88
|
+
* being fatal.
|
|
89
|
+
*
|
|
90
|
+
* Only ever called for an OWNED session. Closing an attached one would end a lifecycle this process
|
|
91
|
+
* does not own — and, worse, could kill the Caller session a sibling invocation is waiting on.
|
|
92
|
+
*/
|
|
93
|
+
export declare function closeOwnedSession(ctx: CommandContext, token: string, id: string): Promise<void>;
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-scope.js","sourceRoot":"","sources":["../../../src/commands/messaging/session-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { CommandContext } from "../context.js";
|
|
2
|
+
import type { Session, SessionRegisterRequest } from "./wire.js";
|
|
3
|
+
/** POST /v1/sessions — register (spec §16.1). Every request field is optional: the Hub mints the
|
|
4
|
+
* id, owns the state, and CLAMPS `ttl_seconds` two-sidedly into its advertised bounds, so the
|
|
5
|
+
* returned `ttl_seconds`/`expires_at` are authoritative and may differ from what was asked. */
|
|
6
|
+
export declare function registerSession(ctx: CommandContext, token: string, request: SessionRegisterRequest): Promise<Session>;
|
|
7
|
+
/** GET /v1/sessions — list (spec §16.4). Scope depends on the CALLER, not on any parameter: an
|
|
8
|
+
* agent always sees its OWN sessions, and sees the whole account only where the Hub's
|
|
9
|
+
* `agent_list_visibility` policy permits it. The caller renders which of those it got. */
|
|
10
|
+
export declare function listSessions(ctx: CommandContext, token: string, page?: {
|
|
11
|
+
limit?: number;
|
|
12
|
+
offset?: number;
|
|
13
|
+
}): Promise<Session[]>;
|
|
14
|
+
/** GET /v1/sessions/:id — read one (spec §16.4). A TERMINAL session is still 200-with-its-state for
|
|
15
|
+
* the advertised retention window; 404 covers unknown AND foreign alike (deliberately
|
|
16
|
+
* indistinguishable, §9.1). */
|
|
17
|
+
export declare function getSession(ctx: CommandContext, token: string, id: string): Promise<Session>;
|
|
18
|
+
/** DELETE /v1/sessions/:id — close (spec §16.3). IDEMPOTENT: re-closing a terminal session returns
|
|
19
|
+
* it unchanged rather than erroring, so a retry is always safe. */
|
|
20
|
+
export declare function closeSession(ctx: CommandContext, token: string, id: string): Promise<Session>;
|