@oh-hai/cli 0.4.3 → 0.4.5
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 +21 -0
- package/dist/auth/token-store.d.ts +17 -0
- package/dist/auth/token-store.js +21 -0
- package/dist/auth/token-store.js.map +1 -1
- package/dist/commands/fleet.js +37 -29
- package/dist/commands/fleet.js.map +1 -1
- package/dist/commands/handlers.js +2 -0
- package/dist/commands/handlers.js.map +1 -1
- package/dist/commands/login.js +83 -3
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/mail/http.d.ts +47 -0
- package/dist/commands/mail/http.js +106 -0
- package/dist/commands/mail/http.js.map +1 -0
- package/dist/commands/mail/identity.d.ts +15 -0
- package/dist/commands/mail/identity.js +135 -0
- package/dist/commands/mail/identity.js.map +1 -0
- package/dist/commands/mail.d.ts +2 -0
- package/dist/commands/mail.js +232 -0
- package/dist/commands/mail.js.map +1 -0
- package/dist/commands/messaging/capability.d.ts +0 -1
- package/dist/commands/messaging/capability.js +12 -13
- package/dist/commands/messaging/capability.js.map +1 -1
- package/dist/commands/messaging/http.d.ts +9 -1
- package/dist/commands/messaging/http.js +2 -2
- package/dist/commands/messaging/http.js.map +1 -1
- package/dist/commands/messaging/sessions-http.d.ts +16 -3
- package/dist/commands/messaging/sessions-http.js +15 -4
- package/dist/commands/messaging/sessions-http.js.map +1 -1
- package/dist/commands/registry.js +27 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/whoami.d.ts +14 -1
- package/dist/commands/whoami.js +36 -5
- package/dist/commands/whoami.js.map +1 -1
- package/dist/help.js +1 -0
- package/dist/help.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Hub calls for the `oh-hai mail` surface — the account owner's own inbox (issue #874).
|
|
2
|
+
//
|
|
3
|
+
// Every function here takes a HUMAN session bearer. Nothing in this module decides whether the
|
|
4
|
+
// caller is entitled to use it: that is `identity.ts`'s pre-flight gate, which runs before any of
|
|
5
|
+
// these are reached. Re-checking here would invite the response-inspection shape the gate exists to
|
|
6
|
+
// avoid — see that module's header.
|
|
7
|
+
//
|
|
8
|
+
// Status mapping, transport errors, and the error envelope are REUSED from the messaging layer
|
|
9
|
+
// rather than reimplemented: a second copy of the status table drifts, and a drifted copy maps a
|
|
10
|
+
// 404 or a 409 to the wrong exit code, which is exactly the machine contract callers branch on.
|
|
11
|
+
import { throwTransportError } from "../http.js";
|
|
12
|
+
import { readErrorEnvelope, readJson, redactToken, requestTimeoutMs, statusToCliError } from "../messaging/http.js";
|
|
13
|
+
import { redactDeep } from "../messaging/sessions-http.js";
|
|
14
|
+
import { CliError } from "../../envelope.js";
|
|
15
|
+
function isMailRow(v) {
|
|
16
|
+
const r = v;
|
|
17
|
+
return typeof r === "object" && r !== null && typeof r.id === "string" && typeof r.type === "string" && typeof r.status === "string";
|
|
18
|
+
}
|
|
19
|
+
async function get(ctx, token, path) {
|
|
20
|
+
const url = `${ctx.config.baseUrl}${path}`;
|
|
21
|
+
let res;
|
|
22
|
+
try {
|
|
23
|
+
res = await ctx.runtime.fetchImpl(url, {
|
|
24
|
+
method: "GET",
|
|
25
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
26
|
+
signal: AbortSignal.timeout(requestTimeoutMs(ctx)),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
31
|
+
}
|
|
32
|
+
if (res.status < 200 || res.status >= 300) {
|
|
33
|
+
const { code, message } = await readErrorEnvelope(res);
|
|
34
|
+
throw statusToCliError(res.status, redactToken(message, token), code);
|
|
35
|
+
}
|
|
36
|
+
// REDACT THE BEARER FROM THE SUCCESS BODY TOO — not just from error messages (§5.4).
|
|
37
|
+
// A buggy or hostile Hub can echo the Authorization value into any field, and this surface
|
|
38
|
+
// serializes rows VERBATIM under `--json` (deliberately, so a multi-line body survives) — so an
|
|
39
|
+
// echoed bearer would land on stdout intact. The sibling `listMessages` already round-trips its
|
|
40
|
+
// rows through the same redactor; the human inbox path must not be the one that skips it.
|
|
41
|
+
// Fails CLOSED: a re-parse failure raises a server error rather than returning un-redacted data.
|
|
42
|
+
return redactDeep(await readJson(res), token, "message");
|
|
43
|
+
}
|
|
44
|
+
/** List the account inbox — full message bodies, not an index (see MailRow). `view` selects the
|
|
45
|
+
* triage feed; `archived` is an explicit opt-in. */
|
|
46
|
+
export async function listInbox(ctx, token, opts) {
|
|
47
|
+
const query = new URLSearchParams();
|
|
48
|
+
if (opts.limit !== undefined)
|
|
49
|
+
query.set("limit", String(opts.limit));
|
|
50
|
+
if (opts.offset !== undefined)
|
|
51
|
+
query.set("offset", String(opts.offset));
|
|
52
|
+
// Only the archived view carries a param; `active` is the absence of one.
|
|
53
|
+
if (opts.view === "archived")
|
|
54
|
+
query.set("archived", "true");
|
|
55
|
+
const suffix = query.toString();
|
|
56
|
+
const body = await get(ctx, token, `/v1/messages${suffix ? `?${suffix}` : ""}`);
|
|
57
|
+
const messages = body.messages;
|
|
58
|
+
if (!Array.isArray(messages) || !messages.every(isMailRow)) {
|
|
59
|
+
// A malformed body is a SERVER error, never a silently empty inbox — an empty table would read
|
|
60
|
+
// as "nothing to triage" and hide a Hub or proxy regression behind good news.
|
|
61
|
+
throw new CliError("server", "the Hub returned a malformed message list (expected { messages: [{ id, type, status }] }).");
|
|
62
|
+
}
|
|
63
|
+
return messages;
|
|
64
|
+
}
|
|
65
|
+
/** Pull one message by id.
|
|
66
|
+
*
|
|
67
|
+
* VALIDATED LIKE THE LIST PATH, and for the same reason. `readJson` collapses a malformed 2xx —
|
|
68
|
+
* non-JSON, an array, `{}` — to an empty object, so an unvalidated detail read would exit 0 and
|
|
69
|
+
* print `{ message: {} }` (or a row of em dashes), presenting a Hub/proxy contract failure as a
|
|
70
|
+
* successful read of an empty message. The list path already refuses that; leaving the detail path
|
|
71
|
+
* permissive made the two disagree about what a valid response is. */
|
|
72
|
+
export async function readMessage(ctx, token, id) {
|
|
73
|
+
const body = await get(ctx, token, `/v1/messages/${encodeURIComponent(id)}`);
|
|
74
|
+
if (!isMailRow(body)) {
|
|
75
|
+
throw new CliError("server", "the Hub returned a malformed message (expected { id, type, status }).");
|
|
76
|
+
}
|
|
77
|
+
// AND IT MUST BE THE MESSAGE THAT WAS ASKED FOR. A well-shaped body for a DIFFERENT id passes the
|
|
78
|
+
// shape check and would exit 0 having shown the wrong message — the caller then archives, quotes
|
|
79
|
+
// or acts on something they never requested. The agent-side `pollMessage` validates this echo for
|
|
80
|
+
// the same reason; a shape check alone is not an identity check.
|
|
81
|
+
if (body.id !== id) {
|
|
82
|
+
throw new CliError("server", "the Hub returned a different message than the one requested.");
|
|
83
|
+
}
|
|
84
|
+
return body;
|
|
85
|
+
}
|
|
86
|
+
/** Apply one triage verb to one message. Returns nothing — the Hub answers 2xx with no body worth
|
|
87
|
+
* rendering, and inventing one would imply state this command did not read back. */
|
|
88
|
+
export async function triage(ctx, token, id, verb) {
|
|
89
|
+
const url = `${ctx.config.baseUrl}/v1/messages/${encodeURIComponent(id)}/${verb}`;
|
|
90
|
+
let res;
|
|
91
|
+
try {
|
|
92
|
+
res = await ctx.runtime.fetchImpl(url, {
|
|
93
|
+
method: "POST",
|
|
94
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
95
|
+
signal: AbortSignal.timeout(requestTimeoutMs(ctx)),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
100
|
+
}
|
|
101
|
+
if (res.status < 200 || res.status >= 300) {
|
|
102
|
+
const { code, message } = await readErrorEnvelope(res);
|
|
103
|
+
throw statusToCliError(res.status, redactToken(message, token), code);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/commands/mail/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CommandContext } from "../context.js";
|
|
2
|
+
/** The human identity a `mail` command runs as. */
|
|
3
|
+
export interface MailIdentity {
|
|
4
|
+
/** The account owner's session bearer. */
|
|
5
|
+
token: string;
|
|
6
|
+
/** The `<userId>` half of the stored `human:<userId>` id — never the raw stored key. */
|
|
7
|
+
userId: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Resolve the human identity for a `mail` command, or refuse.
|
|
11
|
+
*
|
|
12
|
+
* Called before any request is composed. Throws rather than returning a discriminated result: a
|
|
13
|
+
* caller cannot then forget to branch, which is the failure this whole module exists to prevent.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveMailIdentity(ctx: CommandContext): Promise<MailIdentity>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Identity resolution for the `oh-hai mail` surface — the account OWNER's own inbox (issue #874).
|
|
2
|
+
//
|
|
3
|
+
// WHY THE REFUSAL IS PRE-FLIGHT, AND WHY IT CANNOT BE A RESPONSE CHECK.
|
|
4
|
+
// `GET /v1/messages` is DUAL-AUDIENCE: with a human session bearer it returns the account-wide
|
|
5
|
+
// inbox; with an AGENT bearer it SUCCEEDS and returns that agent's own submitted messages. It does
|
|
6
|
+
// not error for the wrong audience — it returns different data with the same shape and the same
|
|
7
|
+
// 200. So a guard that inspects the RESPONSE is unsound by construction, and the sibling
|
|
8
|
+
// agent-side guard in `messaging/http.ts` says exactly that in its own comment:
|
|
9
|
+
//
|
|
10
|
+
// a heuristic, not a proof of the agent branch: a single-agent or empty account has no
|
|
11
|
+
// foreign row to trip on
|
|
12
|
+
//
|
|
13
|
+
// That hole is widest precisely where it matters most — a fresh or single-agent account — and its
|
|
14
|
+
// failure mode is silent: a plausible, well-formed, WRONG inbox with nothing signalling it.
|
|
15
|
+
// Deciding from the RESOLVED IDENTITY, before a request is composed, has no such hole: an agent
|
|
16
|
+
// identity never reaches the network here at all. Every `mail` command therefore calls
|
|
17
|
+
// `resolveMailIdentity` FIRST, and nothing in this module consults response data.
|
|
18
|
+
//
|
|
19
|
+
// FAIL CLOSED. An unresolvable identity is refused as not-human rather than attempted
|
|
20
|
+
// unauthenticated — "I could not tell" is never permission to send.
|
|
21
|
+
import { resolveIdentity } from "../../auth/resolve-token.js";
|
|
22
|
+
import { humanUserId, isHumanId } from "../../auth/token-store.js";
|
|
23
|
+
import { CliError } from "../../envelope.js";
|
|
24
|
+
import { sanitizeForTerminal } from "../messaging/shared.js";
|
|
25
|
+
import { classifyProbeStatus, probeGet, readActor, readJsonBody } from "../whoami.js";
|
|
26
|
+
/** How to get one, appended to every refusal so the error is actionable rather than a diagnosis. */
|
|
27
|
+
const REMEDY = "`oh-hai mail` reads YOUR account inbox, so it needs a human identity: run `oh-hai login --human` " +
|
|
28
|
+
"(or select one with --account human:<id>). To read an agent's own submitted messages instead, use `oh-hai messages list`.";
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the human identity for a `mail` command, or refuse.
|
|
31
|
+
*
|
|
32
|
+
* Called before any request is composed. Throws rather than returning a discriminated result: a
|
|
33
|
+
* caller cannot then forget to branch, which is the failure this whole module exists to prevent.
|
|
34
|
+
*/
|
|
35
|
+
export async function resolveMailIdentity(ctx) {
|
|
36
|
+
// FIRST, AND BEFORE THE ID IS EVEN CONSULTED: refuse an env bearer.
|
|
37
|
+
//
|
|
38
|
+
// `MA2H_AGENT_TOKEN` is an AGENT credential by name and by contract, and — the part that makes
|
|
39
|
+
// this a hole rather than a nicety — it is NOT BOUND TO THE CONFIGURED ACCOUNT. `resolveIdentity`
|
|
40
|
+
// returns it verbatim alongside whatever `--account` says (the same unbinding `requireWatchIdentity`
|
|
41
|
+
// documents for the drain lock). So without this arm:
|
|
42
|
+
//
|
|
43
|
+
// MA2H_AGENT_TOKEN=<agent bearer> oh-hai mail list --account human:usr_1
|
|
44
|
+
//
|
|
45
|
+
// presents a human-SHAPED id with an AGENT token behind it. The id test below would pass, the
|
|
46
|
+
// request would go out, and the Hub would answer with that agent's own submissions — the exact
|
|
47
|
+
// plausible-but-wrong inbox this module exists to prevent, reached through the env path instead
|
|
48
|
+
// of the store. A human identity must be STORE-resolved, where the token was looked up BY the
|
|
49
|
+
// human id and the two are therefore bound.
|
|
50
|
+
if (ctx.config.tokenSource === "env") {
|
|
51
|
+
throw new CliError("usage", "MA2H_AGENT_TOKEN is set, and it is an AGENT credential that is not bound to --account — it cannot be used as a human identity. " +
|
|
52
|
+
"Unset it and run `oh-hai login --human`, so the session token is stored under (and resolved by) your human id.");
|
|
53
|
+
}
|
|
54
|
+
const store = await ctx.runtime.openStore();
|
|
55
|
+
const { account, token } = await resolveIdentity(ctx.config, store);
|
|
56
|
+
// Order matters: classify the IDENTITY before asking whether a token exists. An agent identity
|
|
57
|
+
// that happens to have no stored token must still read as "wrong identity", not as "log in" —
|
|
58
|
+
// sending the caller to re-login would have them authenticate as the agent again and arrive back
|
|
59
|
+
// here, having been told the wrong thing twice.
|
|
60
|
+
if (account === undefined) {
|
|
61
|
+
throw new CliError("usage", `no identity is selected, so this command cannot tell whose inbox to read. ${REMEDY}`);
|
|
62
|
+
}
|
|
63
|
+
if (!isHumanId(account)) {
|
|
64
|
+
// SANITIZE THE ID BEFORE IT REACHES A TERMINAL. A device-code login stores the agent id the HUB
|
|
65
|
+
// chose, verbatim — `runDeviceCodeFlow` accepts any non-empty `agent_id` — so a custom or
|
|
66
|
+
// compromised Hub can plant control characters in it, and this message goes straight to stderr.
|
|
67
|
+
// The login and whoami display paths already sanitize for exactly this; the table rendering in
|
|
68
|
+
// mail.ts does too. The ERROR path was the one place still interpolating it raw.
|
|
69
|
+
throw new CliError("usage", `the selected identity "${sanitizeForTerminal(account)}" is an AGENT, not a human. ${REMEDY}`);
|
|
70
|
+
}
|
|
71
|
+
if (token === undefined) {
|
|
72
|
+
throw new CliError("auth", `no stored token for "${sanitizeForTerminal(account)}" — run \`oh-hai login --human\`.`);
|
|
73
|
+
}
|
|
74
|
+
// `humanUserId` re-tests the prefix rather than slicing on the strength of the check above, so
|
|
75
|
+
// this stays correct if the two are ever separated.
|
|
76
|
+
const userId = humanUserId(account);
|
|
77
|
+
if (userId === undefined || userId === "") {
|
|
78
|
+
throw new CliError("usage", `the selected identity "${sanitizeForTerminal(account)}" carries no user id. ${REMEDY}`);
|
|
79
|
+
}
|
|
80
|
+
// ATTEST THE CREDENTIAL. The checks above establish that the stored ID claims to be human; they
|
|
81
|
+
// cannot establish that the TOKEN is. The `human:` prefix is only reserved as of this change, and
|
|
82
|
+
// it never was on the server: `MA2H_AGENT_TOKENS` splits on `=` with no reserved-prefix rule, and
|
|
83
|
+
// an older CLI accepted `login --token-stdin --account human:bot` with any bearer. So a
|
|
84
|
+
// credential stored BEFORE this change — or configured on the Hub — can carry a human-shaped id
|
|
85
|
+
// over an AGENT token, and every local check would wave it through.
|
|
86
|
+
//
|
|
87
|
+
// This is the same rule as the two refusals above, applied honestly: an id-based check is sound
|
|
88
|
+
// only where the id IMPLIES the credential. History means it does not, so ask the Hub who this
|
|
89
|
+
// token actually is and believe THAT.
|
|
90
|
+
//
|
|
91
|
+
// ORDERING IS DELIBERATE. The local refusals run FIRST, so the common wrong-identity case still
|
|
92
|
+
// costs no network call at all and the "an agent identity never reaches the network" property
|
|
93
|
+
// holds for it. This probe runs only for a credential that already looks human, and it targets
|
|
94
|
+
// `/auth/whoami` — an identity endpoint that discloses nothing about the inbox — so the INBOX is
|
|
95
|
+
// still never touched by a non-human caller.
|
|
96
|
+
const res = await probeGet(ctx, "/auth/whoami", token);
|
|
97
|
+
if (res.status !== 200)
|
|
98
|
+
classifyProbeStatus(res.status);
|
|
99
|
+
const actor = readActor(await readJsonBody(res));
|
|
100
|
+
if (actor === undefined) {
|
|
101
|
+
throw new CliError("server", "the Hub did not attest an identity for this credential (200 without an actor); refusing rather than assuming it is yours.");
|
|
102
|
+
}
|
|
103
|
+
// NEVER ECHO AN ACTOR THAT CONTAINS THE CREDENTIAL. `sanitizeForTerminal` strips controls; it does
|
|
104
|
+
// not redact a PRINTABLE bearer. A Hub answering `agent:<the-token>` would otherwise have its
|
|
105
|
+
// actor interpolated into the refusal below and written to stderr — the §5.4 never-print contract
|
|
106
|
+
// broken by the very check that exists to protect the surface. The login arm refuses this exact
|
|
107
|
+
// shape; adding the probe here without the same refusal reproduced the hole one file over.
|
|
108
|
+
if (actor.includes(token)) {
|
|
109
|
+
throw new CliError("server", "the Hub echoed the presented credential back inside its identity response; refusing rather than reporting it.");
|
|
110
|
+
}
|
|
111
|
+
// THE ATTESTED ACTOR MUST BE **THIS** IDENTITY, not merely *a* human one.
|
|
112
|
+
//
|
|
113
|
+
// "Is it human?" is not the question. An older CLI accepted `--token-stdin --account human:A` with
|
|
114
|
+
// ANY bearer, so a store can hold human B's valid session token under the key `human:A`. The Hub
|
|
115
|
+
// then attests `human:B` — genuinely human — and a human-only check passes, after which
|
|
116
|
+
// `--account human:A` lists, reads and ARCHIVES B's inbox while every message claims to run as A.
|
|
117
|
+
// A cross-account read, reached through a check that looked like it had closed the hole.
|
|
118
|
+
//
|
|
119
|
+
// This is the third variant of one rule in this PR, and the general form is the one to hold:
|
|
120
|
+
// the credential must be attested as the EXACT identity being acted on, not as a member of the
|
|
121
|
+
// right class.
|
|
122
|
+
if (actor !== account) {
|
|
123
|
+
throw new CliError("usage", `the stored credential under "${sanitizeForTerminal(account)}" is attested by the Hub as a DIFFERENT identity. ` +
|
|
124
|
+
"It was stored before the `human:` prefix was reserved, or under the wrong key. Re-run `oh-hai login --human` to replace it — " +
|
|
125
|
+
"the attested id is not shown here because it is not yours to learn from a mismatched key.");
|
|
126
|
+
}
|
|
127
|
+
// NOTE: no separate "is the actor human?" arm here, deliberately. The equality check above already
|
|
128
|
+
// implies it — `account` passed `isHumanId` earlier, and `actor` must equal `account` — so such a
|
|
129
|
+
// branch could never execute. Leaving one would imply a live guard that is doing nothing, which is
|
|
130
|
+
// how a later reader concludes the class is covered when the equality check is what covers it.
|
|
131
|
+
// Equal to `account` by the check above; read from the ATTESTED value so the Hub remains the
|
|
132
|
+
// source of truth rather than the local key.
|
|
133
|
+
return { token, userId: humanUserId(actor) ?? userId };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../../src/commands/mail/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// `oh-hai mail` (cli spec §4.16) — the ACCOUNT OWNER's own inbox, read and triaged from a terminal
|
|
2
|
+
// (issue #874).
|
|
3
|
+
//
|
|
4
|
+
// A SEPARATE VERB FROM `inbox`, DELIBERATELY. `oh-hai inbox` is the AGENT's mailbox drain (the
|
|
5
|
+
// human→agent leg). Dispatching one noun on the resolved identity would put the human/agent
|
|
6
|
+
// decision in a runtime branch — the same shape as the failure this surface is built to prevent —
|
|
7
|
+
// so the two are separate namespaces instead, and a wrong identity reaches a command that cannot
|
|
8
|
+
// serve it at all.
|
|
9
|
+
//
|
|
10
|
+
// EVERY SUBCOMMAND RESOLVES ITS IDENTITY FIRST. `resolveMailIdentity` refuses an agent identity
|
|
11
|
+
// before a request is composed; nothing here inspects a response to decide entitlement. See
|
|
12
|
+
// `mail/identity.ts` for why the response cannot answer that question.
|
|
13
|
+
//
|
|
14
|
+
// Output posture: under `--json` rows keep their STRUCTURE and their newlines (a multi-line body
|
|
15
|
+
// must survive, which is why they are not run through `sanitizeForTerminal` — that strips C0 and
|
|
16
|
+
// would destroy them), while the human table renders only single-line index fields, each
|
|
17
|
+
// `sanitizeForTerminal`'d against a hostile Hub.
|
|
18
|
+
//
|
|
19
|
+
// `JSON.stringify` IS NOT A SANITIZER, and an earlier revision of this comment claimed it was:
|
|
20
|
+
// it escapes the C0 range but passes **C1** (U+0080–U+009F, including U+009B CSI) and **DEL**
|
|
21
|
+
// through RAW. A JSON envelope printed into a terminal therefore carries a live injection vector.
|
|
22
|
+
// `agents list --json` already strips these; this surface must too — see `stripJsonUnsafeControls`.
|
|
23
|
+
import { buildOk, CliError, serializeEnvelope } from "../envelope.js";
|
|
24
|
+
import { ExitCode } from "../exit-codes.js";
|
|
25
|
+
import { flagOnStrict, parseCommandArgs } from "./flags.js";
|
|
26
|
+
import { resolveMailIdentity } from "./mail/identity.js";
|
|
27
|
+
import { listInbox, readMessage, triage } from "./mail/http.js";
|
|
28
|
+
import { nonNegativeInt, positiveInt, sanitizeForTerminal, stringFlag } from "./messaging/shared.js";
|
|
29
|
+
const SUBCOMMANDS = ["list", "read", "archive", "unarchive"];
|
|
30
|
+
const OPTIONS = {
|
|
31
|
+
limit: { type: "string" },
|
|
32
|
+
offset: { type: "string" },
|
|
33
|
+
archived: { type: "boolean" },
|
|
34
|
+
};
|
|
35
|
+
const DEFAULT_LIMIT = 50;
|
|
36
|
+
/** The Hub's server-side clamps on `GET /v1/messages`. Mirrored so the paging hint is computed
|
|
37
|
+
* against the limit/offset the Hub ACTUALLY applied — a request above these is clamped server-side,
|
|
38
|
+
* so a full clamped page compared against the raw request would wrongly report "no more" and a
|
|
39
|
+
* scripted pager would stop early. Mirrors `messages.ts`. */
|
|
40
|
+
const LIST_LIMIT_MAX = 200;
|
|
41
|
+
const LIST_OFFSET_MAX = 10_000;
|
|
42
|
+
/** A parsing ceiling — an absurd `--limit`/`--offset` is a usage error (exit 2) before any call. */
|
|
43
|
+
const MAX_LIST_INT = 1_000_000;
|
|
44
|
+
/** Strip the control characters `JSON.stringify` does NOT escape — **C1** (U+0080–U+009F, e.g.
|
|
45
|
+
* U+009B CSI) and **DEL** (U+007F) — from every string in a value, recursively.
|
|
46
|
+
*
|
|
47
|
+
* Deliberately NOT `sanitizeForTerminal`: that also strips C0, which includes the newlines a
|
|
48
|
+
* message body legitimately contains, and preserving those is the entire reason this surface emits
|
|
49
|
+
* rows with their structure intact. C0 needs no stripping here because `JSON.stringify` escapes it
|
|
50
|
+
* (`\n`, `\u001b`), so it can never reach a terminal as a control. C1 and DEL are the gap. */
|
|
51
|
+
function stripJsonUnsafeControls(value) {
|
|
52
|
+
if (typeof value === "string") {
|
|
53
|
+
let out = "";
|
|
54
|
+
for (const ch of value) {
|
|
55
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
56
|
+
if (code !== 0x7f && !(code >= 0x80 && code <= 0x9f))
|
|
57
|
+
out += ch;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value))
|
|
62
|
+
return value.map((v) => stripJsonUnsafeControls(v));
|
|
63
|
+
if (value !== null && typeof value === "object") {
|
|
64
|
+
const o = {};
|
|
65
|
+
for (const [k, v] of Object.entries(value)) {
|
|
66
|
+
o[stripJsonUnsafeControls(k)] = stripJsonUnsafeControls(v);
|
|
67
|
+
}
|
|
68
|
+
return o;
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
const EM_DASH = "—";
|
|
73
|
+
export async function mailCommand(ctx) {
|
|
74
|
+
// Operands: the subcommand plus, for the triage verbs, one or more message ids.
|
|
75
|
+
const parsed = parseCommandArgs(ctx.argv, OPTIONS, { maxOperands: 64 });
|
|
76
|
+
const sub = parsed.positionals[1];
|
|
77
|
+
if (sub === undefined) {
|
|
78
|
+
throw new CliError("usage", `missing subcommand: expected one of ${SUBCOMMANDS.join(" | ")}.`);
|
|
79
|
+
}
|
|
80
|
+
if (!SUBCOMMANDS.includes(sub)) {
|
|
81
|
+
throw new CliError("usage", `unknown subcommand "${sub}": expected one of ${SUBCOMMANDS.join(" | ")}.`);
|
|
82
|
+
}
|
|
83
|
+
const ids = parsed.positionals.slice(2);
|
|
84
|
+
const flags = parsed.values;
|
|
85
|
+
// Flags are validated BEFORE credentials are touched (matches notify/messages): a bad invocation
|
|
86
|
+
// is exit 2 without opening the keychain.
|
|
87
|
+
const requestedLimit = flags.limit !== undefined ? positiveInt(stringFlag(flags.limit), "--limit", "a positive integer", MAX_LIST_INT) : DEFAULT_LIMIT;
|
|
88
|
+
const requestedOffset = flags.offset !== undefined ? nonNegativeInt(stringFlag(flags.offset), "--offset", "a non-negative integer", MAX_LIST_INT) : undefined;
|
|
89
|
+
// ARITY FIRST — before the credential lookup, not after.
|
|
90
|
+
// A malformed invocation (`mail read` with no id) is a USAGE error, and reporting it as an
|
|
91
|
+
// identity/auth error on a machine with no human identity selected sends the caller to fix the
|
|
92
|
+
// wrong thing entirely. It also opened — and could prompt — the OS keychain for an invocation
|
|
93
|
+
// that was never going to make a request. This does NOT weaken the gate below: arity is a purely
|
|
94
|
+
// local check, so the identity refusal still precedes every Hub call.
|
|
95
|
+
// `list` takes NO operands. Without this, `mail list message_1` opened credentials, contacted the
|
|
96
|
+
// Hub, and returned the WHOLE inbox while silently ignoring the argument — a caller who meant
|
|
97
|
+
// `mail read message_1` gets a plausible result for a question they did not ask.
|
|
98
|
+
if (sub === "list" && ids.length !== 0) {
|
|
99
|
+
throw new CliError("usage", "`oh-hai mail list` takes no message ids — did you mean `oh-hai mail read <id>`?");
|
|
100
|
+
}
|
|
101
|
+
if (sub === "read" && ids.length !== 1) {
|
|
102
|
+
throw new CliError("usage", "`oh-hai mail read` takes exactly one message id.");
|
|
103
|
+
}
|
|
104
|
+
if ((sub === "archive" || sub === "unarchive") && ids.length === 0) {
|
|
105
|
+
throw new CliError("usage", `\`oh-hai mail ${sub}\` takes one or more message ids.`);
|
|
106
|
+
}
|
|
107
|
+
// THE GATE. Before any request is composed, for every subcommand.
|
|
108
|
+
const { token } = await resolveMailIdentity(ctx);
|
|
109
|
+
if (sub === "list") {
|
|
110
|
+
// `flagOnStrict`, not `flagOn` and certainly not `=== true`. Two failure modes, one shape:
|
|
111
|
+
// under `strict: false` the supported `--archived=true` form arrives as the STRING "true", so an
|
|
112
|
+
// identity test reads it as off; and a TYPO (`--archived=treu`) is an unrecognized value that
|
|
113
|
+
// `flagOn` also reads as off. Both silently serve the ACTIVE inbox for an explicit request for
|
|
114
|
+
// the archived one — a plausible wrong view rather than an error, which is the failure this
|
|
115
|
+
// whole surface is built to avoid. `flagOnStrict` exists for exactly this: a value it does not
|
|
116
|
+
// recognize is a usage error, not a silent "off".
|
|
117
|
+
await mailList(ctx, token, requestedLimit, requestedOffset, flagOnStrict(flags.archived, "--archived"));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (sub === "read") {
|
|
121
|
+
await mailRead(ctx, token, ids[0]);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
return mailTriage(ctx, token, sub, ids);
|
|
125
|
+
}
|
|
126
|
+
async function mailList(ctx, token, requestedLimit, requestedOffset, archived) {
|
|
127
|
+
const limit = Math.min(requestedLimit, LIST_LIMIT_MAX);
|
|
128
|
+
const offset = requestedOffset !== undefined ? Math.min(requestedOffset, LIST_OFFSET_MAX) : undefined;
|
|
129
|
+
const view = archived ? "archived" : "active";
|
|
130
|
+
const messages = await listInbox(ctx, token, { limit, ...(offset !== undefined ? { offset } : {}), view });
|
|
131
|
+
const effectiveOffset = offset ?? 0;
|
|
132
|
+
const nextOffset = Math.min(effectiveOffset + messages.length, LIST_OFFSET_MAX);
|
|
133
|
+
const hasMore = messages.length === limit && effectiveOffset < LIST_OFFSET_MAX;
|
|
134
|
+
if (ctx.json) {
|
|
135
|
+
ctx.io.log(serializeEnvelope(buildOk("mail.list", stripJsonUnsafeControls({ messages, count: messages.length, view, has_more: hasMore, next_offset: nextOffset }))));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (messages.length === 0) {
|
|
139
|
+
ctx.io.log(archived ? "No archived messages." : "No messages.");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const rows = messages.map(toRow);
|
|
143
|
+
const display = [{ id: "ID", type: "TYPE", status: "STATUS", title: "TITLE", created: "CREATED" }, ...rows];
|
|
144
|
+
const idW = colWidth(display, "id");
|
|
145
|
+
const typeW = colWidth(display, "type");
|
|
146
|
+
const statusW = colWidth(display, "status");
|
|
147
|
+
const titleW = colWidth(display, "title");
|
|
148
|
+
for (const r of display) {
|
|
149
|
+
ctx.io.log(`${r.id.padEnd(idW)} ${r.type.padEnd(typeW)} ${r.status.padEnd(statusW)} ${r.title.padEnd(titleW)} ${r.created}`);
|
|
150
|
+
}
|
|
151
|
+
if (hasMore)
|
|
152
|
+
ctx.io.err(`(more — re-run with --offset ${nextOffset})`);
|
|
153
|
+
}
|
|
154
|
+
async function mailRead(ctx, token, id) {
|
|
155
|
+
const message = await readMessage(ctx, token, id);
|
|
156
|
+
if (ctx.json) {
|
|
157
|
+
ctx.io.log(serializeEnvelope(buildOk("mail.read", stripJsonUnsafeControls({ message }))));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// The human view prints the index fields plus the body, each sanitized. The body is the one
|
|
161
|
+
// multi-line field, so it is printed last and on its own lines rather than squeezed into a table.
|
|
162
|
+
const row = toRow(message);
|
|
163
|
+
ctx.io.log(`${row.id} ${row.type} ${row.status}`);
|
|
164
|
+
ctx.io.log(`title: ${row.title}`);
|
|
165
|
+
ctx.io.log(`created: ${row.created}`);
|
|
166
|
+
const body = typeof message.body === "string" ? message.body : undefined;
|
|
167
|
+
if (body !== undefined) {
|
|
168
|
+
ctx.io.log("");
|
|
169
|
+
for (const line of body.split("\n"))
|
|
170
|
+
ctx.io.log(sanitizeForTerminal(line));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function mailTriage(ctx, token, verb, ids) {
|
|
174
|
+
if (ids.length === 0)
|
|
175
|
+
throw new CliError("usage", `\`oh-hai mail ${verb}\` takes one or more message ids.`);
|
|
176
|
+
// Per-id outcome, deliberately: collapsing a partial failure into one status would report a batch
|
|
177
|
+
// as failed when most of it succeeded, and leave the caller unable to tell which ids to retry.
|
|
178
|
+
const results = [];
|
|
179
|
+
for (const id of ids) {
|
|
180
|
+
try {
|
|
181
|
+
await triage(ctx, token, id, verb);
|
|
182
|
+
results.push({ id, ok: true });
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// ONLY A GENUINELY PER-MESSAGE CONDITION STAYS PER-ID — an ALLOWLIST, not a denylist.
|
|
186
|
+
//
|
|
187
|
+
// An earlier revision rethrew just `auth` and `network`, which fixed two cells of a family and
|
|
188
|
+
// left the rest: a timeout still collapsed to exit 1 instead of the timeout tier, a Hub 5xx
|
|
189
|
+
// lost the server tier, and the loop re-attempted every id against a condition that could not
|
|
190
|
+
// improve. The failure classes are open-ended, so enumerating the batch-wide ones can never be
|
|
191
|
+
// complete; enumerating the PER-ID ones can, because they are the only classes that say
|
|
192
|
+
// something about THIS message rather than about the request as a whole.
|
|
193
|
+
// The allowlist, spelled with the §7 codes that actually exist: a message that is absent, in
|
|
194
|
+
// a conflicting state, or named by a request the Hub rejected as malformed. Everything else —
|
|
195
|
+
// auth, network, server, timeout, and every code not yet invented — is about the request as a
|
|
196
|
+
// whole and stops the batch.
|
|
197
|
+
const PER_MESSAGE = new Set(["not_found", "conflict", "bad_request", "validation_error"]);
|
|
198
|
+
const perMessage = error instanceof CliError && PER_MESSAGE.has(error.code);
|
|
199
|
+
if (!perMessage)
|
|
200
|
+
throw error;
|
|
201
|
+
results.push({ id, ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const failed = results.filter((r) => !r.ok);
|
|
205
|
+
if (ctx.json) {
|
|
206
|
+
ctx.io.log(serializeEnvelope(buildOk(`mail.${verb}`, stripJsonUnsafeControls({ results, count: results.length, failed: failed.length }))));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
for (const r of results) {
|
|
210
|
+
if (r.ok)
|
|
211
|
+
ctx.io.log(`${verb}d ${sanitizeForTerminal(r.id)}`);
|
|
212
|
+
else
|
|
213
|
+
ctx.io.err(`FAILED ${sanitizeForTerminal(r.id)}: ${sanitizeForTerminal(r.error ?? "unknown error")}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
// RETURN a non-zero code rather than THROWING, because stdout is already committed.
|
|
217
|
+
// Throwing here would make the dispatcher emit a SECOND `--json` envelope after the ok one, so a
|
|
218
|
+
// consumer reading the single-envelope contract (§8) gets two contradictory documents and
|
|
219
|
+
// commonly fails to parse — and the per-id results, the whole reason for reporting them, are
|
|
220
|
+
// absent from the error envelope that would arrive last. `doctor` returns its code for exactly
|
|
221
|
+
// this reason; the handler contract is `Promise<void | number>`.
|
|
222
|
+
return failed.length > 0 ? ExitCode.ERROR : ExitCode.SUCCESS;
|
|
223
|
+
}
|
|
224
|
+
/** Project a Hub row to sanitized, single-line index fields; absent fields render as an em dash. */
|
|
225
|
+
function toRow(m) {
|
|
226
|
+
const one = (v) => (typeof v === "string" && v !== "" ? sanitizeForTerminal(v) : EM_DASH);
|
|
227
|
+
return { id: one(m.id), type: one(m.type), status: one(m.status), title: one(m.title), created: one(m.created_at) };
|
|
228
|
+
}
|
|
229
|
+
function colWidth(rows, key) {
|
|
230
|
+
return rows.reduce((w, r) => Math.max(w, r[key].length), 0);
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=mail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/commands/mail.ts"],"names":[],"mappings":"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"}
|
|
@@ -75,20 +75,19 @@ export async function fetchCapability(ctx) {
|
|
|
75
75
|
return undefined;
|
|
76
76
|
}
|
|
77
77
|
const doc = body;
|
|
78
|
-
// `
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// that
|
|
78
|
+
// `sessions` is read for its `enabled` flag (the §8.7.1 session-drain gate below), so a value that
|
|
79
|
+
// is not an object is dropped rather than cast — a non-object would make `sessions?.enabled` throw
|
|
80
|
+
// or silently read undefined off a string.
|
|
81
|
+
//
|
|
82
|
+
// The per-field `agent_list_visibility` sanitizer that used to live here is GONE with #860: it
|
|
83
|
+
// existed only because `fleet ls` ACTED on that field. The Hub still advertises it — it states the
|
|
84
|
+
// deployment ceiling — but it is only half the §16.4 answer, so the CLI reads the authenticated
|
|
85
|
+
// `scope` on `GET /v1/sessions` instead and ignores this field entirely. Nothing here reads it, so
|
|
86
|
+
// there is nothing to sanitize.
|
|
83
87
|
const sessions = doc.sessions;
|
|
84
|
-
if (sessions
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
delete sessions.agent_list_visibility;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
else if (sessions !== undefined) {
|
|
91
|
-
delete doc.sessions;
|
|
88
|
+
if (sessions === null || typeof sessions !== "object" || Array.isArray(sessions)) {
|
|
89
|
+
if (sessions !== undefined)
|
|
90
|
+
delete doc.sessions;
|
|
92
91
|
}
|
|
93
92
|
const capability = doc;
|
|
94
93
|
CACHE.set(key, capability);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../src/commands/messaging/capability.ts"],"names":[],"mappings":"AAAA,+FAA+F;AAC/F,0CAA0C;AAC1C,EAAE;AACF,sGAAsG;AACtG,mGAAmG;AACnG,oGAAoG;AACpG,mGAAmG;AACnG,mGAAmG;AACnG,kGAAkG;AAClG,EAAE;AACF,qGAAqG;AACrG,sGAAsG;AACtG,mGAAmG;AACnG,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,kDAAkD;AAClD,EAAE;AACF,sGAAsG;AACtG,sFAAsF;AAEtF,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../../../src/commands/messaging/capability.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type HubTouchpoint } from "@oh-hai/ma2h-core/errors";
|
|
2
2
|
import { CliError } from "../../envelope.js";
|
|
3
|
-
import type { CommandContext } from "../context.js";
|
|
3
|
+
import type { CommandContext, HttpResponse } from "../context.js";
|
|
4
4
|
import type { GetMessageBody, InboundDelivery, SubmitAck } from "./wire.js";
|
|
5
5
|
/** POST /v1/inbox/ack — the ids of the directives consumed by this call (spec §14; the server's
|
|
6
6
|
* `ackDirectives` returns the id array, not a count). A TRANSPORT wrapper shape of this Hub's
|
|
@@ -67,3 +67,11 @@ export declare function ackInbox(ctx: CommandContext, token: string, ids: string
|
|
|
67
67
|
* the vendored §8.5 vocabulary — see `cliErrorCode`. The server message is stripped of terminal
|
|
68
68
|
* control chars (the top-level catch prints it). */
|
|
69
69
|
export declare function statusToCliError(status: number, message: string | undefined, hubCode?: string, hint?: string, touchpoint?: HubTouchpoint): CliError;
|
|
70
|
+
/** Parse a response body as a JSON object, defensively — a fake without `json()`, a non-JSON
|
|
71
|
+
* body, or a non-object all collapse to `{}`, so callers read fields off a plain record. */
|
|
72
|
+
export declare function readJson(res: HttpResponse): Promise<Record<string, unknown>>;
|
|
73
|
+
/** Read the A2H error envelope `{ error: { code, message } }` from a non-2xx response. */
|
|
74
|
+
export declare function readErrorEnvelope(res: HttpResponse): Promise<{
|
|
75
|
+
code: string | undefined;
|
|
76
|
+
message: string | undefined;
|
|
77
|
+
}>;
|