@oh-hai/cli 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/auth/file-backend.d.ts +16 -0
- package/dist/auth/file-backend.js +98 -0
- package/dist/auth/file-backend.js.map +1 -0
- package/dist/auth/keychain.d.ts +54 -0
- package/dist/auth/keychain.js +232 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/resolve-token.d.ts +34 -0
- package/dist/auth/resolve-token.js +91 -0
- package/dist/auth/resolve-token.js.map +1 -0
- package/dist/auth/secure-write.d.ts +2 -0
- package/dist/auth/secure-write.js +30 -0
- package/dist/auth/secure-write.js.map +1 -0
- package/dist/auth/token-store.d.ts +104 -0
- package/dist/auth/token-store.js +208 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +238 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +370 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/ask.d.ts +2 -0
- package/dist/commands/ask.js +246 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/context.d.ts +72 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +237 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/flags.d.ts +25 -0
- package/dist/commands/flags.js +100 -0
- package/dist/commands/flags.js.map +1 -0
- package/dist/commands/handlers.d.ts +2 -0
- package/dist/commands/handlers.js +26 -0
- package/dist/commands/handlers.js.map +1 -0
- package/dist/commands/http.d.ts +8 -0
- package/dist/commands/http.js +19 -0
- package/dist/commands/http.js.map +1 -0
- package/dist/commands/inbox.d.ts +2 -0
- package/dist/commands/inbox.js +111 -0
- package/dist/commands/inbox.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +272 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +35 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/messaging/await.d.ts +43 -0
- package/dist/commands/messaging/await.js +125 -0
- package/dist/commands/messaging/await.js.map +1 -0
- package/dist/commands/messaging/build.d.ts +46 -0
- package/dist/commands/messaging/build.js +66 -0
- package/dist/commands/messaging/build.js.map +1 -0
- package/dist/commands/messaging/http.d.ts +22 -0
- package/dist/commands/messaging/http.js +270 -0
- package/dist/commands/messaging/http.js.map +1 -0
- package/dist/commands/messaging/identity.d.ts +29 -0
- package/dist/commands/messaging/identity.js +63 -0
- package/dist/commands/messaging/identity.js.map +1 -0
- package/dist/commands/messaging/shared.d.ts +53 -0
- package/dist/commands/messaging/shared.js +135 -0
- package/dist/commands/messaging/shared.js.map +1 -0
- package/dist/commands/messaging/state.d.ts +26 -0
- package/dist/commands/messaging/state.js +82 -0
- package/dist/commands/messaging/state.js.map +1 -0
- package/dist/commands/messaging/validate.d.ts +40 -0
- package/dist/commands/messaging/validate.js +193 -0
- package/dist/commands/messaging/validate.js.map +1 -0
- package/dist/commands/messaging/wire.d.ts +133 -0
- package/dist/commands/messaging/wire.js +16 -0
- package/dist/commands/messaging/wire.js.map +1 -0
- package/dist/commands/notify.d.ts +2 -0
- package/dist/commands/notify.js +68 -0
- package/dist/commands/notify.js.map +1 -0
- package/dist/commands/registry.d.ts +14 -0
- package/dist/commands/registry.js +144 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/stub.d.ts +1 -0
- package/dist/commands/stub.js +9 -0
- package/dist/commands/stub.js.map +1 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +223 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/whoami.d.ts +6 -0
- package/dist/commands/whoami.js +90 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config-file.d.ts +38 -0
- package/dist/config-file.js +233 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +25 -0
- package/dist/envelope.js +41 -0
- package/dist/envelope.js.map +1 -0
- package/dist/exit-codes.d.ts +51 -0
- package/dist/exit-codes.js +57 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/help.d.ts +1 -0
- package/dist/help.js +17 -0
- package/dist/help.js.map +1 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.js +18 -0
- package/dist/terminal.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Agent-owned sealed `--state` resume for the productized messaging commands (cli spec §4.4).
|
|
2
|
+
//
|
|
3
|
+
// The AES-256-GCM state seal is VENDORED crypto (AGENTS.md — do not reimplement): this module
|
|
4
|
+
// only reads the agent-owned key and wraps `@oh-hai/ma2h-core/state-seal`'s sealState/openState.
|
|
5
|
+
// Consuming the shared core at runtime is why #219 makes `@oh-hai/ma2h-core` publishable — the
|
|
6
|
+
// compiled `dist/cli.js` bin resolves the core's compiled dist. Mirrors the scripts layer
|
|
7
|
+
// (scripts/ask.ts readSealKey + scripts/agent-client.ts sealResume/openSealedState).
|
|
8
|
+
import { openState, sealState } from "@oh-hai/ma2h-core/state-seal";
|
|
9
|
+
import { CliError } from "../../envelope.js";
|
|
10
|
+
/** The env var carrying the agent's 32-byte state-seal key (base64url). Agent-runtime-provisioned,
|
|
11
|
+
* Hub-invisible, distinct from the bearer, and never embedded in `state` (spec §9.3). */
|
|
12
|
+
export const STATE_SEAL_KEY_ENV = "MA2H_STATE_SEAL_KEY";
|
|
13
|
+
/** Read + validate the agent state-seal key from the env. Returns undefined when unset; throws a
|
|
14
|
+
* usage error (exit 2) when set but not exactly 32 bytes. */
|
|
15
|
+
export function readSealKey() {
|
|
16
|
+
const raw = process.env[STATE_SEAL_KEY_ENV]?.trim();
|
|
17
|
+
if (raw === undefined || raw === "")
|
|
18
|
+
return undefined;
|
|
19
|
+
const key = Buffer.from(raw, "base64url");
|
|
20
|
+
if (key.length !== 32) {
|
|
21
|
+
throw new CliError("usage", `${STATE_SEAL_KEY_ENV} must decode to 32 bytes (base64url); got ${key.length}.`);
|
|
22
|
+
}
|
|
23
|
+
return key;
|
|
24
|
+
}
|
|
25
|
+
/** Wrap an agent-owned resume object as a sealed envelope `state` value (`{ sealed: "MA2HSEALv1.…" }`).
|
|
26
|
+
* The seal key is Hub-invisible (spec §9.3). */
|
|
27
|
+
export function sealResumeState(resume, key) {
|
|
28
|
+
return { sealed: sealState(resume, key) };
|
|
29
|
+
}
|
|
30
|
+
/** Open the agent-owned sealed `state` round-tripped in a Response (`{ sealed: "MA2HSEALv1.…" }`).
|
|
31
|
+
* Returns undefined when the Response carries no sealed state. Throws on tamper / wrong key. */
|
|
32
|
+
export function openSealedState(response, key) {
|
|
33
|
+
const sealed = response.state?.["sealed"];
|
|
34
|
+
if (typeof sealed !== "string")
|
|
35
|
+
return undefined;
|
|
36
|
+
return openState(sealed, key);
|
|
37
|
+
}
|
|
38
|
+
/** Await-side opener shared by `ask await` / `task await`: when the resolved message carries a
|
|
39
|
+
* sealed `state` AND `MA2H_STATE_SEAL_KEY` is set, open it. Returns undefined when there is no
|
|
40
|
+
* sealed blob or the key is unset (opening is opt-in — a missing key is not an error). Only the
|
|
41
|
+
* presence of a sealed blob makes the key relevant, so the key is read *after* confirming there is
|
|
42
|
+
* something to open: a set-but-malformed key then fails only the awaits that actually need to open
|
|
43
|
+
* state (a usage error, exit 2) instead of breaking every await. An open failure on a present
|
|
44
|
+
* blob (tamper / wrong key) is a generic error (non-zero) rather than a silent drop of the resume
|
|
45
|
+
* context. (This defers the key check the scripts layer does eagerly — a deliberate CLI nicety.) */
|
|
46
|
+
export function openResumeState(body) {
|
|
47
|
+
const sealed = body.response?.state?.["sealed"];
|
|
48
|
+
if (typeof sealed !== "string")
|
|
49
|
+
return undefined;
|
|
50
|
+
const key = readSealKey();
|
|
51
|
+
if (key === undefined)
|
|
52
|
+
return undefined;
|
|
53
|
+
try {
|
|
54
|
+
return openState(sealed, key);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new CliError("error", `failed to open sealed state: ${err instanceof Error ? err.message : String(err)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Submit-side resolver shared by notify / ask / task: parse the `--state` flag (must be a JSON
|
|
61
|
+
* object), require the seal key, and return the sealed envelope `state`. Returns undefined when
|
|
62
|
+
* `--state` is absent. A non-object payload or a missing key is a usage error (exit 2). */
|
|
63
|
+
export function resolveState(raw) {
|
|
64
|
+
if (raw === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
throw new CliError("usage", `--state is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
72
|
+
}
|
|
73
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
74
|
+
throw new CliError("usage", "--state must be a JSON object.");
|
|
75
|
+
}
|
|
76
|
+
const key = readSealKey();
|
|
77
|
+
if (key === undefined) {
|
|
78
|
+
throw new CliError("usage", `--state requires ${STATE_SEAL_KEY_ENV} (base64url 32-byte key) so resume state can be sealed.`);
|
|
79
|
+
}
|
|
80
|
+
return sealResumeState(parsed, key);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/commands/messaging/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Actor, AskMessage, Priority, RequestMode, ResponseOption, TaskMessage } from "./wire.js";
|
|
2
|
+
export declare const PRIORITIES: readonly Priority[];
|
|
3
|
+
export declare const REQUEST_MODES: readonly RequestMode[];
|
|
4
|
+
export declare const CALLBACK_MODES: readonly ["pull", "push"];
|
|
5
|
+
export type CallbackMode = (typeof CALLBACK_MODES)[number];
|
|
6
|
+
export declare function isPriority(value: string): value is Priority;
|
|
7
|
+
export declare function isRequestMode(value: string): value is RequestMode;
|
|
8
|
+
export declare function isCallbackMode(value: string): value is CallbackMode;
|
|
9
|
+
/** Hub-attested resolver identity `<type>:<id>` with type ∈ {human,agent,system} (spec §9.1).
|
|
10
|
+
* Anchored end-to-end (no embedded whitespace) so a malformed `--resolver` fails loudly here. */
|
|
11
|
+
export declare function isActor(value: string): value is Actor;
|
|
12
|
+
/** Normalize a push `--callback-url` to the canonical WHATWG href the Hub signs and `fetch` POSTs
|
|
13
|
+
* to (so the signed callback_url can't desync from the delivery URL), and enforce http(s) — the
|
|
14
|
+
* Hub's SSRF delivery guard rejects every other scheme, so a mailto:/file: url would pass
|
|
15
|
+
* `new URL()` but silently lose the push. A bad URL / scheme is a usage error (exit 2). */
|
|
16
|
+
export declare function normalizePushUrl(raw: string): string;
|
|
17
|
+
/** Parse `value:label` (label may itself contain ':'). Throws a usage CliError on a bad shape. */
|
|
18
|
+
export declare function parseOption(raw: string): ResponseOption;
|
|
19
|
+
/** A task/ask is resolvable only when allowed_resolvers names ≥1 concrete `human:<id>` — the
|
|
20
|
+
* resolve route is human-only and fail-closed (spec §5.4), so a message with none (or only
|
|
21
|
+
* agent:/system: resolvers) can never be marked done and sits open until expiry. */
|
|
22
|
+
export declare function hasHumanResolver(resolvers: readonly unknown[] | undefined): boolean;
|
|
23
|
+
/** Stricter than `hasHumanResolver`: requires ≥1 `human:<id>` with a CONCRETE (non-wildcard,
|
|
24
|
+
* non-empty) id. The Hub matches allowed_resolvers literally (`allowed.includes(actor)`), so a
|
|
25
|
+
* captured `human:*` can never match any real actor — the replay guard must reject it exactly as
|
|
26
|
+
* the compose path does, or the replayed ask/task is submitted unresolvable. */
|
|
27
|
+
export declare function hasConcreteHumanResolver(resolvers: readonly unknown[] | undefined): boolean;
|
|
28
|
+
/** Read the `--envelope` source: inline JSON, `@<path>` (local file), or `@-` (stdin, via the
|
|
29
|
+
* injected reader so tests never touch fd 0). */
|
|
30
|
+
export declare function readEnvelopeArg(raw: string, readStdin: () => Promise<string>): Promise<string>;
|
|
31
|
+
/** Validate a parsed value is a well-formed ask envelope before replaying it (spec §4.8) — enough
|
|
32
|
+
* to fail loudly on a wrong paste (a notify/task envelope, truncated JSON, missing idempotency
|
|
33
|
+
* key) without re-doing the Hub's full schema validation. */
|
|
34
|
+
export declare function assertAskEnvelope(value: unknown): AskMessage;
|
|
35
|
+
/** Validate a parsed value is a well-formed task envelope before replaying it (spec §4.8),
|
|
36
|
+
* including an opt-in callback's shape (push needs a valid http(s) url; else pull). */
|
|
37
|
+
export declare function assertTaskEnvelope(value: unknown): TaskMessage;
|
|
38
|
+
/** Extract `allowed_resolvers` from a replayed ask/task envelope (they live at `request.` for
|
|
39
|
+
* ask, `action.` for task) — used by the human-resolver replay guard. */
|
|
40
|
+
export declare function replayResolvers(envelope: AskMessage | TaskMessage): readonly unknown[] | undefined;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Pure validation helpers + `--envelope` replay guards for the messaging commands (spec §4.4 /
|
|
2
|
+
// §4.8). Ported from `scripts/ask.ts` + `scripts/task.ts`'s loud-failure discipline: an
|
|
3
|
+
// out-of-range mode/priority/actor, a malformed option, or a wrong-shaped replayed envelope
|
|
4
|
+
// fails LOCALLY (exit 2) rather than slipping through to a remote 422. No I/O except the
|
|
5
|
+
// `@file` read in `readEnvelopeArg` (a local file); `@-` goes through the injected stdin reader
|
|
6
|
+
// so tests stay hermetic.
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { CliError } from "../../envelope.js";
|
|
9
|
+
export const PRIORITIES = ["low", "normal", "high", "urgent"];
|
|
10
|
+
export const REQUEST_MODES = ["select", "input", "confirm"];
|
|
11
|
+
export const CALLBACK_MODES = ["pull", "push"];
|
|
12
|
+
export function isPriority(value) {
|
|
13
|
+
return PRIORITIES.includes(value);
|
|
14
|
+
}
|
|
15
|
+
export function isRequestMode(value) {
|
|
16
|
+
return REQUEST_MODES.includes(value);
|
|
17
|
+
}
|
|
18
|
+
export function isCallbackMode(value) {
|
|
19
|
+
return CALLBACK_MODES.includes(value);
|
|
20
|
+
}
|
|
21
|
+
/** Hub-attested resolver identity `<type>:<id>` with type ∈ {human,agent,system} (spec §9.1).
|
|
22
|
+
* Anchored end-to-end (no embedded whitespace) so a malformed `--resolver` fails loudly here. */
|
|
23
|
+
export function isActor(value) {
|
|
24
|
+
return /^(human|agent|system):[^\s]+$/.test(value);
|
|
25
|
+
}
|
|
26
|
+
/** True when `raw` parses as an http(s) URL — the only schemes the Hub's SSRF delivery guard
|
|
27
|
+
* accepts for a push callback, so a mailto:/file: url would silently lose the push. */
|
|
28
|
+
function isHttpUrl(raw) {
|
|
29
|
+
try {
|
|
30
|
+
const u = new URL(raw);
|
|
31
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Normalize a push `--callback-url` to the canonical WHATWG href the Hub signs and `fetch` POSTs
|
|
38
|
+
* to (so the signed callback_url can't desync from the delivery URL), and enforce http(s) — the
|
|
39
|
+
* Hub's SSRF delivery guard rejects every other scheme, so a mailto:/file: url would pass
|
|
40
|
+
* `new URL()` but silently lose the push. A bad URL / scheme is a usage error (exit 2). */
|
|
41
|
+
export function normalizePushUrl(raw) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = new URL(raw.trim());
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new CliError("usage", `--callback-url is not a valid URL: ${JSON.stringify(raw)}`);
|
|
48
|
+
}
|
|
49
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
50
|
+
throw new CliError("usage", `--callback-url must be http(s) (the Hub rejects other schemes), got ${JSON.stringify(parsed.protocol)}`);
|
|
51
|
+
}
|
|
52
|
+
return parsed.href;
|
|
53
|
+
}
|
|
54
|
+
/** Parse `value:label` (label may itself contain ':'). Throws a usage CliError on a bad shape. */
|
|
55
|
+
export function parseOption(raw) {
|
|
56
|
+
const i = raw.indexOf(":");
|
|
57
|
+
if (i < 0)
|
|
58
|
+
throw new CliError("usage", `--option must be "value:label", got "${raw}"`);
|
|
59
|
+
const value = raw.slice(0, i).trim();
|
|
60
|
+
const label = raw.slice(i + 1).trim();
|
|
61
|
+
if (value === "" || label === "") {
|
|
62
|
+
throw new CliError("usage", `--option must be "value:label" with non-empty parts, got "${raw}"`);
|
|
63
|
+
}
|
|
64
|
+
return { value, label };
|
|
65
|
+
}
|
|
66
|
+
/** A task/ask is resolvable only when allowed_resolvers names ≥1 concrete `human:<id>` — the
|
|
67
|
+
* resolve route is human-only and fail-closed (spec §5.4), so a message with none (or only
|
|
68
|
+
* agent:/system: resolvers) can never be marked done and sits open until expiry. */
|
|
69
|
+
export function hasHumanResolver(resolvers) {
|
|
70
|
+
return (resolvers ?? []).some((r) => typeof r === "string" && r.startsWith("human:"));
|
|
71
|
+
}
|
|
72
|
+
/** Stricter than `hasHumanResolver`: requires ≥1 `human:<id>` with a CONCRETE (non-wildcard,
|
|
73
|
+
* non-empty) id. The Hub matches allowed_resolvers literally (`allowed.includes(actor)`), so a
|
|
74
|
+
* captured `human:*` can never match any real actor — the replay guard must reject it exactly as
|
|
75
|
+
* the compose path does, or the replayed ask/task is submitted unresolvable. */
|
|
76
|
+
export function hasConcreteHumanResolver(resolvers) {
|
|
77
|
+
return (resolvers ?? []).some((r) => {
|
|
78
|
+
if (typeof r !== "string" || !r.startsWith("human:"))
|
|
79
|
+
return false;
|
|
80
|
+
const id = r.slice("human:".length);
|
|
81
|
+
return id !== "" && !id.includes("*");
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Read the `--envelope` source: inline JSON, `@<path>` (local file), or `@-` (stdin, via the
|
|
85
|
+
* injected reader so tests never touch fd 0). */
|
|
86
|
+
export async function readEnvelopeArg(raw, readStdin) {
|
|
87
|
+
if (raw === "@-")
|
|
88
|
+
return readStdin();
|
|
89
|
+
if (raw.startsWith("@")) {
|
|
90
|
+
const path = raw.slice(1);
|
|
91
|
+
try {
|
|
92
|
+
return readFileSync(path, "utf8");
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
// A missing/unreadable local file is a usage mistake (exit 2), not a generic error (exit 1):
|
|
96
|
+
// re-throw as a CliError so the message is actionable rather than a raw Node ENOENT.
|
|
97
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
98
|
+
throw new CliError("usage", `--envelope: could not read ${JSON.stringify(path)}: ${reason}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return raw;
|
|
102
|
+
}
|
|
103
|
+
/** Validate a parsed value is a well-formed ask envelope before replaying it (spec §4.8) — enough
|
|
104
|
+
* to fail loudly on a wrong paste (a notify/task envelope, truncated JSON, missing idempotency
|
|
105
|
+
* key) without re-doing the Hub's full schema validation. */
|
|
106
|
+
export function assertAskEnvelope(value) {
|
|
107
|
+
const env = asObject(value, "--envelope must be a JSON object (the envelope printed by 'ask submit --dry-run')");
|
|
108
|
+
if (env["type"] !== "ask") {
|
|
109
|
+
throw new CliError("usage", `--envelope must be an ask envelope (type "ask"), got type ${JSON.stringify(env["type"])}`);
|
|
110
|
+
}
|
|
111
|
+
if (typeof env["ma2h_version"] !== "string")
|
|
112
|
+
throw new CliError("usage", "--envelope is missing ma2h_version");
|
|
113
|
+
if (typeof env["idempotency_key"] !== "string" || env["idempotency_key"].trim() === "") {
|
|
114
|
+
throw new CliError("usage", "--envelope is missing the REQUIRED idempotency_key");
|
|
115
|
+
}
|
|
116
|
+
if (typeof env["agent"] !== "object" || env["agent"] === null || Array.isArray(env["agent"])) {
|
|
117
|
+
throw new CliError("usage", "--envelope agent must be an object");
|
|
118
|
+
}
|
|
119
|
+
const request = env["request"];
|
|
120
|
+
if (typeof request !== "object" || request === null || Array.isArray(request)) {
|
|
121
|
+
throw new CliError("usage", "--envelope request must be an object");
|
|
122
|
+
}
|
|
123
|
+
// Validate an ask push callback's scheme (mirrors assertTaskEnvelope): a non-http(s) push url
|
|
124
|
+
// passes new URL() but is silently dropped by the Hub's SSRF guard — fail fast locally.
|
|
125
|
+
const callback = request["callback"];
|
|
126
|
+
if (callback !== undefined) {
|
|
127
|
+
if (typeof callback !== "object" || callback === null || Array.isArray(callback)) {
|
|
128
|
+
throw new CliError("usage", "--envelope request.callback must be an object");
|
|
129
|
+
}
|
|
130
|
+
const mode = callback["mode"];
|
|
131
|
+
if (mode === "push") {
|
|
132
|
+
const url = callback["url"];
|
|
133
|
+
if (typeof url !== "string" || !isHttpUrl(url)) {
|
|
134
|
+
throw new CliError("usage", "--envelope push callback requires a valid http(s) url");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (mode !== "pull") {
|
|
138
|
+
throw new CliError("usage", `--envelope request.callback.mode must be "pull" or "push", got ${JSON.stringify(mode)}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
/** Validate a parsed value is a well-formed task envelope before replaying it (spec §4.8),
|
|
144
|
+
* including an opt-in callback's shape (push needs a valid http(s) url; else pull). */
|
|
145
|
+
export function assertTaskEnvelope(value) {
|
|
146
|
+
const env = asObject(value, "--envelope must be a JSON object (the envelope printed by 'task submit --dry-run')");
|
|
147
|
+
if (env["type"] !== "task") {
|
|
148
|
+
throw new CliError("usage", `--envelope must be a task envelope (type "task"), got type ${JSON.stringify(env["type"])}`);
|
|
149
|
+
}
|
|
150
|
+
if (typeof env["ma2h_version"] !== "string")
|
|
151
|
+
throw new CliError("usage", "--envelope is missing ma2h_version");
|
|
152
|
+
if (typeof env["idempotency_key"] !== "string" || env["idempotency_key"].trim() === "") {
|
|
153
|
+
throw new CliError("usage", "--envelope is missing the REQUIRED idempotency_key (a blank value collides on the Hub's dedup key)");
|
|
154
|
+
}
|
|
155
|
+
if (typeof env["agent"] !== "object" || env["agent"] === null || Array.isArray(env["agent"])) {
|
|
156
|
+
throw new CliError("usage", "--envelope agent must be an object");
|
|
157
|
+
}
|
|
158
|
+
const action = env["action"];
|
|
159
|
+
if (typeof action !== "object" || action === null || Array.isArray(action)) {
|
|
160
|
+
throw new CliError("usage", "--envelope action must be an object");
|
|
161
|
+
}
|
|
162
|
+
const callback = action["callback"];
|
|
163
|
+
if (callback !== undefined) {
|
|
164
|
+
if (typeof callback !== "object" || callback === null || Array.isArray(callback)) {
|
|
165
|
+
throw new CliError("usage", "--envelope action.callback must be an object");
|
|
166
|
+
}
|
|
167
|
+
const mode = callback["mode"];
|
|
168
|
+
if (mode === "push") {
|
|
169
|
+
const url = callback["url"];
|
|
170
|
+
if (typeof url !== "string" || !isHttpUrl(url)) {
|
|
171
|
+
throw new CliError("usage", "--envelope push callback requires a valid http(s) url");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if (mode !== "pull") {
|
|
175
|
+
throw new CliError("usage", `--envelope action.callback.mode must be "pull" or "push", got ${JSON.stringify(mode)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
/** Extract `allowed_resolvers` from a replayed ask/task envelope (they live at `request.` for
|
|
181
|
+
* ask, `action.` for task) — used by the human-resolver replay guard. */
|
|
182
|
+
export function replayResolvers(envelope) {
|
|
183
|
+
const holder = envelope.type === "ask" ? envelope.request : envelope.action;
|
|
184
|
+
const resolvers = holder.allowed_resolvers;
|
|
185
|
+
return Array.isArray(resolvers) ? resolvers : undefined;
|
|
186
|
+
}
|
|
187
|
+
function asObject(value, message) {
|
|
188
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
189
|
+
throw new CliError("usage", message);
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/commands/messaging/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/** Recursive JSON value — for opaque `state` and free-form input answers/schemas. */
|
|
2
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
3
|
+
[key: string]: JsonValue;
|
|
4
|
+
};
|
|
5
|
+
export type JsonObject = {
|
|
6
|
+
[key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
export type Priority = "low" | "normal" | "high" | "urgent";
|
|
9
|
+
export type Runtime = "github-actions" | "cli" | "cloud" | "desktop" | "openclaw" | "other";
|
|
10
|
+
export type RequestMode = "select" | "input" | "confirm";
|
|
11
|
+
/** Hub-attested resolver identity: `<type>:<id>` (spec §9.1). */
|
|
12
|
+
export type Actor = `${"human" | "agent" | "system"}:${string}`;
|
|
13
|
+
export interface AgentDescriptor {
|
|
14
|
+
id: string;
|
|
15
|
+
run_id: string;
|
|
16
|
+
runtime: Runtime;
|
|
17
|
+
project?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ResponseOption {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Callback, discriminated by mode (push requires a url). The CLI never composes callback
|
|
25
|
+
* `auth`, so it is omitted from the local type. */
|
|
26
|
+
export type Callback = {
|
|
27
|
+
mode: "push";
|
|
28
|
+
url: string;
|
|
29
|
+
} | {
|
|
30
|
+
mode: "pull";
|
|
31
|
+
};
|
|
32
|
+
export interface AskRequest {
|
|
33
|
+
mode: RequestMode;
|
|
34
|
+
options?: ResponseOption[];
|
|
35
|
+
schema?: JsonObject;
|
|
36
|
+
allowed_resolvers?: Actor[];
|
|
37
|
+
callback?: Callback;
|
|
38
|
+
}
|
|
39
|
+
export interface TaskAction {
|
|
40
|
+
instructions: string;
|
|
41
|
+
checklist?: {
|
|
42
|
+
text: string;
|
|
43
|
+
}[];
|
|
44
|
+
allowed_resolvers?: Actor[];
|
|
45
|
+
callback?: Callback;
|
|
46
|
+
}
|
|
47
|
+
interface BaseEnvelope {
|
|
48
|
+
ma2h_version: "0.3";
|
|
49
|
+
created_at: string;
|
|
50
|
+
agent: AgentDescriptor;
|
|
51
|
+
title: string;
|
|
52
|
+
body?: string;
|
|
53
|
+
priority?: Priority;
|
|
54
|
+
tags?: string[];
|
|
55
|
+
/** Agent-owned, agent-integrity-sealed resume blob (`{ sealed: "MA2HSEALv1.…" }`, via `--state`).
|
|
56
|
+
* Opaque to the Hub (MUST NOT inspect/log — spec §9.3). Sealed by `state.ts` on submit. */
|
|
57
|
+
state?: JsonObject;
|
|
58
|
+
}
|
|
59
|
+
export interface NotifyMessage extends BaseEnvelope {
|
|
60
|
+
type: "notify";
|
|
61
|
+
idempotency_key?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface AskMessage extends BaseEnvelope {
|
|
64
|
+
type: "ask";
|
|
65
|
+
idempotency_key: string;
|
|
66
|
+
request: AskRequest;
|
|
67
|
+
}
|
|
68
|
+
export interface TaskMessage extends BaseEnvelope {
|
|
69
|
+
type: "task";
|
|
70
|
+
idempotency_key: string;
|
|
71
|
+
action: TaskAction;
|
|
72
|
+
}
|
|
73
|
+
export type A2hMessage = NotifyMessage | AskMessage | TaskMessage;
|
|
74
|
+
export type AskResolution = "answered" | "declined" | "cancelled" | "expired";
|
|
75
|
+
export type TaskResolution = "completed" | "dismissed" | "expired";
|
|
76
|
+
export type Resolution = AskResolution | TaskResolution;
|
|
77
|
+
export type Status = "open" | "delivered" | Resolution;
|
|
78
|
+
export interface ResponseDetail {
|
|
79
|
+
/** ask only: chosen option value (string) or the input object. Absent for task. */
|
|
80
|
+
value?: string | JsonObject;
|
|
81
|
+
actor: Actor;
|
|
82
|
+
resolved_at: string;
|
|
83
|
+
comment?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface A2hResponse {
|
|
86
|
+
ma2h_version: "0.3";
|
|
87
|
+
in_reply_to: string;
|
|
88
|
+
resolution_id: string;
|
|
89
|
+
resolution: Resolution;
|
|
90
|
+
defaulted?: boolean;
|
|
91
|
+
response?: ResponseDetail;
|
|
92
|
+
state?: JsonObject;
|
|
93
|
+
}
|
|
94
|
+
export interface SubmitAck {
|
|
95
|
+
id: string;
|
|
96
|
+
status: "open" | "delivered";
|
|
97
|
+
poll_url: string;
|
|
98
|
+
review_url?: string;
|
|
99
|
+
}
|
|
100
|
+
/** GET /v1/messages/:id — the stored envelope plus its lifecycle status and terminal Response. */
|
|
101
|
+
export type GetMessageBody = A2hMessage & {
|
|
102
|
+
id: string;
|
|
103
|
+
status: Status;
|
|
104
|
+
response?: A2hResponse;
|
|
105
|
+
};
|
|
106
|
+
/** One human→agent directive drained from the agent's mailbox (spec §13.1). */
|
|
107
|
+
export interface InboundDirective {
|
|
108
|
+
ma2h_version: string;
|
|
109
|
+
type: "directive";
|
|
110
|
+
id: string;
|
|
111
|
+
from: string;
|
|
112
|
+
to: string;
|
|
113
|
+
created_at: string;
|
|
114
|
+
title: string;
|
|
115
|
+
body?: string;
|
|
116
|
+
priority?: Priority;
|
|
117
|
+
tags?: string[];
|
|
118
|
+
context?: JsonValue[];
|
|
119
|
+
expires_at?: string;
|
|
120
|
+
sensitive?: boolean;
|
|
121
|
+
}
|
|
122
|
+
/** One drained directive plus its detached `MA2H-Signature` (spec §8.7 / §9.7). Forwarded verbatim
|
|
123
|
+
* so a runtime that wants defense-in-depth can verify the signature against the Hub key. */
|
|
124
|
+
export interface InboundDelivery {
|
|
125
|
+
directive: InboundDirective;
|
|
126
|
+
signature: string;
|
|
127
|
+
}
|
|
128
|
+
/** POST /v1/inbox/ack — the ids of the directives consumed by this call (spec §14; the server's
|
|
129
|
+
* `ackDirectives` returns the id array, not a count). */
|
|
130
|
+
export interface InboxAckResult {
|
|
131
|
+
acked: string[];
|
|
132
|
+
}
|
|
133
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// The MA2H wire shapes the productized messaging commands compose and read (spec/v0.3).
|
|
2
|
+
//
|
|
3
|
+
// These MIRROR the canonical definitions in `@oh-hai/ma2h-core` (`packages/ma2h-core/src/types.ts`,
|
|
4
|
+
// and the shapes `scripts/agent-client.ts` builds), but are RE-DECLARED locally on purpose:
|
|
5
|
+
// `@oh-hai/cli` ships as a standalone published bin (`bin: dist/cli.js`, `tsconfig.build.json`
|
|
6
|
+
// sets `rootDir: "src"`). This matches the CLI's existing design — `config.ts` re-declares its own
|
|
7
|
+
// types rather than reaching out. Only the subset the CLI composes or reads lives here (no context
|
|
8
|
+
// Parts, permissions, or callback-auth refs — the CLI's push callback is a bare `{ mode, url }`).
|
|
9
|
+
// The server validates every envelope at ingest, so any drift surfaces as a 422/400, never silent
|
|
10
|
+
// corruption. As of #219 the CLI DOES consume the core's runtime crypto (`state-seal` — the
|
|
11
|
+
// vendored AES-256-GCM seal must not be re-implemented, AGENTS.md; see `state.ts`), now that
|
|
12
|
+
// `@oh-hai/ma2h-core` is npm-publishable. These wire TYPES stay locally re-declared (types are
|
|
13
|
+
// compile-time only, erased from `dist/cli.js`); a full type re-export from the core is a separate
|
|
14
|
+
// follow-up, out of #219 scope.
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire.js","sourceRoot":"","sources":["../../../src/commands/messaging/wire.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,EAAE;AACF,oGAAoG;AACpG,4FAA4F;AAC5F,+FAA+F;AAC/F,mGAAmG;AACnG,mGAAmG;AACnG,kGAAkG;AAClG,kGAAkG;AAClG,4FAA4F;AAC5F,6FAA6F;AAC7F,+FAA+F;AAC/F,mGAAmG;AACnG,gCAAgC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// `oh-hai notify` (docs/specs/cli.md §4.4) — fire-and-forget notification (status / FYI /
|
|
2
|
+
// digest). The productized sibling of `scripts/notify.ts`: the bearer is resolved account-aware
|
|
3
|
+
// (keychain/file/env, not raw env), output follows the `--json` envelope, and HTTP statuses map
|
|
4
|
+
// to §7 exit codes. No idempotency key — a notify must not be retried (a retry posts a duplicate).
|
|
5
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
6
|
+
import { parseCommandArgs } from "./flags.js";
|
|
7
|
+
import { buildNotify } from "./messaging/build.js";
|
|
8
|
+
import { submitEnvelope } from "./messaging/http.js";
|
|
9
|
+
import { resolveSubmitIdentity } from "./messaging/identity.js";
|
|
10
|
+
import { dryRunFlag, printDryRunEnvelope, printSubmitAck, resolveRunId, stringFlag } from "./messaging/shared.js";
|
|
11
|
+
import { resolveState } from "./messaging/state.js";
|
|
12
|
+
import { PRIORITIES, isPriority } from "./messaging/validate.js";
|
|
13
|
+
export async function notifyCommand(ctx) {
|
|
14
|
+
const { values } = parseCommandArgs(ctx.argv, {
|
|
15
|
+
title: { type: "string" },
|
|
16
|
+
body: { type: "string" },
|
|
17
|
+
priority: { type: "string" },
|
|
18
|
+
tag: { type: "string", multiple: true },
|
|
19
|
+
"dry-run": { type: "boolean" },
|
|
20
|
+
state: { type: "string" },
|
|
21
|
+
});
|
|
22
|
+
// Seal an optional `--state` resume blob up front (requires MA2H_STATE_SEAL_KEY) so the
|
|
23
|
+
// preview under --dry-run shows the sealed envelope. notify has no await/open leg.
|
|
24
|
+
const state = resolveState(stringFlag(values.state));
|
|
25
|
+
const title = stringFlag(values.title);
|
|
26
|
+
if (title === undefined || title.trim() === "") {
|
|
27
|
+
throw new CliError("usage", "missing required flag: --title (must be non-empty).");
|
|
28
|
+
}
|
|
29
|
+
const priority = stringFlag(values.priority);
|
|
30
|
+
if (priority !== undefined && !isPriority(priority)) {
|
|
31
|
+
throw new CliError("usage", `invalid --priority "${priority}" (expected one of: ${PRIORITIES.join(", ")}).`);
|
|
32
|
+
}
|
|
33
|
+
const body = stringFlag(values.body);
|
|
34
|
+
const tags = Array.isArray(values.tag) ? values.tag.filter((t) => typeof t === "string") : [];
|
|
35
|
+
// Strict parse (not `=== true`): the `=`-form `--dry-run=true` still previews, and a malformed
|
|
36
|
+
// value like `--dry-run=treu` fails as usage rather than falling through to a LIVE submit.
|
|
37
|
+
const dryRun = dryRunFlag(values["dry-run"]);
|
|
38
|
+
// Under --dry-run neither a token nor a resolved account is required — the envelope is only
|
|
39
|
+
// previewed. Use a placeholder agent id so the composed envelope stays inspectable. A real
|
|
40
|
+
// submit resolves the bearer + account up front (auth/usage errors before composing).
|
|
41
|
+
let agentId = ctx.config.account ?? "oh-hai/notify-bot";
|
|
42
|
+
let token = ""; // reassigned on the real path below; dry-run returns before it's used
|
|
43
|
+
if (!dryRun) {
|
|
44
|
+
const identity = await resolveSubmitIdentity(ctx);
|
|
45
|
+
agentId = identity.agentId;
|
|
46
|
+
token = identity.token;
|
|
47
|
+
}
|
|
48
|
+
const notify = buildNotify({
|
|
49
|
+
agent: { id: agentId, run_id: resolveRunId() },
|
|
50
|
+
title,
|
|
51
|
+
...(body !== undefined ? { body } : {}),
|
|
52
|
+
...(priority !== undefined ? { priority } : {}),
|
|
53
|
+
...(tags.length > 0 ? { tags } : {}),
|
|
54
|
+
...(state !== undefined ? { state } : {}),
|
|
55
|
+
});
|
|
56
|
+
if (dryRun) {
|
|
57
|
+
printDryRunEnvelope(ctx, "notify", notify);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const ack = await submitEnvelope(ctx, token, notify);
|
|
61
|
+
if (ctx.json) {
|
|
62
|
+
ctx.io.log(serializeEnvelope(buildOk("notify", { id: ack.id })));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
printSubmitAck(ctx, "notify posted", ack);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/commands/notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CommandSpec {
|
|
2
|
+
/** The command token, e.g. "notify" or "agents". */
|
|
3
|
+
name: string;
|
|
4
|
+
/** One-line summary for the top-level help. */
|
|
5
|
+
summary: string;
|
|
6
|
+
/** The downstream issue that implements this command's body. */
|
|
7
|
+
issue: number;
|
|
8
|
+
/** Valid subcommands, if any (e.g. ask → submit | await). */
|
|
9
|
+
subcommands: string[];
|
|
10
|
+
/** Full `--help` text for this command. */
|
|
11
|
+
help: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const COMMANDS: readonly CommandSpec[];
|
|
14
|
+
export declare function findCommand(name: string): CommandSpec | undefined;
|