@oh-hai/cli 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +59 -29
  3. package/dist/commands/ask.js +71 -10
  4. package/dist/commands/ask.js.map +1 -1
  5. package/dist/commands/bridge.d.ts +2 -0
  6. package/dist/commands/bridge.js +502 -0
  7. package/dist/commands/bridge.js.map +1 -0
  8. package/dist/commands/context.d.ts +22 -0
  9. package/dist/commands/doctor.js +12 -1
  10. package/dist/commands/doctor.js.map +1 -1
  11. package/dist/commands/fleet.d.ts +2 -0
  12. package/dist/commands/fleet.js +164 -0
  13. package/dist/commands/fleet.js.map +1 -0
  14. package/dist/commands/handlers.js +8 -0
  15. package/dist/commands/handlers.js.map +1 -1
  16. package/dist/commands/messages.d.ts +2 -0
  17. package/dist/commands/messages.js +131 -0
  18. package/dist/commands/messages.js.map +1 -0
  19. package/dist/commands/messaging/build.d.ts +10 -1
  20. package/dist/commands/messaging/build.js +22 -4
  21. package/dist/commands/messaging/build.js.map +1 -1
  22. package/dist/commands/messaging/capability.d.ts +42 -0
  23. package/dist/commands/messaging/capability.js +164 -0
  24. package/dist/commands/messaging/capability.js.map +1 -0
  25. package/dist/commands/messaging/http.d.ts +28 -1
  26. package/dist/commands/messaging/http.js +234 -13
  27. package/dist/commands/messaging/http.js.map +1 -1
  28. package/dist/commands/messaging/inbox-entries.d.ts +83 -0
  29. package/dist/commands/messaging/inbox-entries.js +362 -0
  30. package/dist/commands/messaging/inbox-entries.js.map +1 -0
  31. package/dist/commands/messaging/inbox-stream.d.ts +37 -0
  32. package/dist/commands/messaging/inbox-stream.js +224 -0
  33. package/dist/commands/messaging/inbox-stream.js.map +1 -0
  34. package/dist/commands/messaging/session-drain.d.ts +50 -0
  35. package/dist/commands/messaging/session-drain.js +163 -0
  36. package/dist/commands/messaging/session-drain.js.map +1 -0
  37. package/dist/commands/messaging/session-scope.d.ts +93 -0
  38. package/dist/commands/messaging/session-scope.js +0 -0
  39. package/dist/commands/messaging/session-scope.js.map +1 -0
  40. package/dist/commands/messaging/sessions-http.d.ts +20 -0
  41. package/dist/commands/messaging/sessions-http.js +153 -0
  42. package/dist/commands/messaging/sessions-http.js.map +1 -0
  43. package/dist/commands/messaging/shared.d.ts +19 -1
  44. package/dist/commands/messaging/shared.js +69 -2
  45. package/dist/commands/messaging/shared.js.map +1 -1
  46. package/dist/commands/messaging/validate.d.ts +33 -1
  47. package/dist/commands/messaging/validate.js +78 -0
  48. package/dist/commands/messaging/validate.js.map +1 -1
  49. package/dist/commands/messaging/wire.d.ts +180 -3
  50. package/dist/commands/notify.js +47 -5
  51. package/dist/commands/notify.js.map +1 -1
  52. package/dist/commands/registry.js +128 -3
  53. package/dist/commands/registry.js.map +1 -1
  54. package/dist/commands/self-test.d.ts +17 -0
  55. package/dist/commands/self-test.js +472 -0
  56. package/dist/commands/self-test.js.map +1 -0
  57. package/dist/commands/session.d.ts +2 -0
  58. package/dist/commands/session.js +158 -0
  59. package/dist/commands/session.js.map +1 -0
  60. package/dist/commands/setup.js +44 -0
  61. package/dist/commands/setup.js.map +1 -1
  62. package/dist/commands/task.js +61 -11
  63. package/dist/commands/task.js.map +1 -1
  64. package/dist/commands/teach.js +84 -4
  65. package/dist/commands/teach.js.map +1 -1
  66. package/dist/commands/whoami.js +14 -1
  67. package/dist/commands/whoami.js.map +1 -1
  68. package/dist/exit-codes.d.ts +37 -1
  69. package/dist/exit-codes.js +42 -1
  70. package/dist/exit-codes.js.map +1 -1
  71. package/dist/help.js +5 -1
  72. package/dist/help.js.map +1 -1
  73. package/dist/watch-lock.js +7 -0
  74. package/dist/watch-lock.js.map +1 -1
  75. package/package.json +4 -3
@@ -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":"AAAA,yFAAyF;AACzF,EAAE;AACF,kGAAkG;AAClG,yBAAyB;AACzB,EAAE;AACF,qGAAqG;AACrG,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,kGAAkG;AAClG,qGAAqG;AACrG,kGAAkG;AAClG,2FAA2F;AAC3F,EAAE;AACF,gGAAgG;AAChG,oGAAoG;AACpG,gGAAgG;AAEhG,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D;;2EAE2E;AAC3E,MAAM,aAAa,GAAG,MAAM,CAAC;AAE7B;;0BAE0B;AAC1B,MAAM,oBAAoB,GAAG,MAAM,CAAC;AASpC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,GAAmB,EAAE,UAAqC;IACrF,MAAM,UAAU,GAAG,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC;IACnD,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1E,IAAI,QAAQ,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,QAAQ,CAChB,QAAQ,EACR,mGAAmG,QAAQ,CAAC,UAAU,CAAC,IAAI;YACzH,4BAA4B,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,2DAA2D;YACnH,oEAAoE,CACvE,CAAC;IACJ,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,eAAe,CACpC,GAAmB,EACnB,KAAa,EACb,OAAe,EACf,SAAiB;IAEjB,MAAM,GAAG,GAAG,GAAG,SAAS,GAAG,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,WAAW,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;IACvG,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,6FAA6F;IAC7F,+FAA+F;IAC/F,+DAA+D;IAC/D,IAAI,QAAmD,CAAC;IACxD,MAAM,WAAW,GAAG,CAAC,EAAU,EAAQ,EAAE;QACvC,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnD,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC;IACrB,CAAC,CAAC;IAEF,IAAI,MAAkC,CAAC;IACvC,IAAI,CAAC;QACH,WAAW,CAAC,oBAAoB,CAAC,CAAC;QAClC,IAAI,GAAiB,CAAC;QACtB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE;gBACrC,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE,MAAM,EAAE,mBAAmB,EAAE;gBAC1E,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAC1C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,iBAAiB,CAAC,GAAG,CAAC,CAAC;YACvD,MAAM,uBAAuB,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QACxF,CAAC;QACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACxC,0FAA0F;YAC1F,4FAA4F;YAC5F,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,oEAAoE,CAAC,CAAC;QACrG,CAAC;QACD,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAE1B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,KAAK,GAAuC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAE7D,SAAS,CAAC;YACR,IAAI,KAAwD,CAAC;YAC7D,IAAI,CAAC;gBACH,KAAK,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC9B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,yFAAyF;gBACzF,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACjD,CAAC;YACD,IAAI,KAAK,CAAC,IAAI;gBAAE,OAAO,CAAC,iEAAiE;YACzF,MAAM,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAEzF,8FAA8F;YAC9F,0FAA0F;YAC1F,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvC,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC;gBACf,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACjC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBAC9B,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;gBAE1B,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;oBAChB,MAAM,UAAU,GAAG,KAAK,CAAC;oBACzB,KAAK,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;oBACrB,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;wBAAE,SAAS;oBAC7E,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;oBACxE,IAAI,MAAM,KAAK,SAAS;wBAAE,SAAS;oBACnC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;wBAAE,WAAW,CAAC,MAAM,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC;oBACvE,MAAM,MAAM,CAAC;oBACb,0FAA0F;oBAC1F,uFAAuF;oBACvF,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW;wBAAE,OAAO;oBACxC,SAAS;gBACX,CAAC;gBACD,2FAA2F;gBAC3F,wFAAwF;gBACxF,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;oBAAE,SAAS;gBACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAChC,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;gBACtD,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACvE,IAAI,KAAK,KAAK,OAAO;oBAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;qBACtC,IAAI,KAAK,KAAK,MAAM;oBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAClD,2FAA2F;gBAC3F,2FAA2F;gBAC3F,kFAAkF;YACpF,CAAC;QACH,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,QAAQ,KAAK,SAAS;YAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;QACnD,6FAA6F;QAC7F,UAAU,CAAC,KAAK,EAAE,CAAC;QACnB,MAAM,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;mGACmG;AACnG,SAAS,UAAU,CAAC,KAAyB,EAAE,IAAY;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,OAAgB,CAAC;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAChG,MAAM,MAAM,GAAG,OAAkC,CAAC;IAElD,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACrB,iGAAiG;QACjG,gGAAgG;QAChG,kDAAkD;QAClD,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,IAAI,CAAC;YACzG,CAAC,CAAC,MAAM,CAAC,OAAO;YAChB,CAAC,CAAC,oBAAoB,CAAC;QACzB,OAAO;YACL,IAAI,EAAE,MAAM;YACZ,MAAM;YACN,GAAG,CAAC,OAAO,MAAM,CAAC,uBAAuB,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,MAAM,CAAC,uBAAuB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxH,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC1G,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,oBAAoB,CAAC,EAAE,CAAC;YACtE,8FAA8F;YAC9F,8FAA8F;YAC9F,6DAA6D;YAC7D,MAAM,IAAI,QAAQ,CAChB,QAAQ,EACR,yHAAyH,CAC1H,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,QAAgC,EAAE,CAAC;IACxE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAAC,GAAiB;IAChD,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACnF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtG,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrG,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACvH,MAAM,IAAI,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3G,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IACjD,CAAC;AACH,CAAC"}
@@ -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>;
@@ -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>;