a2a-exposed 0.8.39 → 0.8.40
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/lib/hosted.mjs +31 -2
- package/lib/main.mjs +12 -1
- package/package.json +1 -1
- package/worker/src/version.ts +1 -1
package/lib/hosted.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import * as C from "./config.mjs";
|
|
|
4
4
|
import { controlOrigin, deviceLogin } from "./account.mjs";
|
|
5
5
|
import { CliError, upstreamUrlProblem } from "./a2a.mjs";
|
|
6
6
|
|
|
7
|
-
export const SETUP_SCOPES = ["agents:read", "agents:create", "agents:owner-token", "wake:write", "wake:test"];
|
|
7
|
+
export const SETUP_SCOPES = ["agents:read", "agents:create", "agents:owner-token", "wake:write", "wake:test", "upstream:write", "upstream:test"];
|
|
8
8
|
const SETUP_UNSUPPORTED = "This service does not expose scoped hosted setup capabilities. Check the --control-url service origin. Use the service dashboard to create an agent and configure Wake settings, or hosted MCP create_agent/set_wake with human confirmation and secret entry. Do not run init or deploy to repair a hosted address; no setup approval was requested.";
|
|
9
9
|
const NEXT = "Run hosted capabilities --json to list scopes and presets; login --machine NAME --scopes SCOPES to approve setup.";
|
|
10
10
|
const fail = (code, next) => { const error = new CliError(next); error.result = { ok: false, code, next }; throw error; };
|
|
@@ -62,6 +62,7 @@ async function request(origin, path, { token = "", method = "GET", body, fetchIm
|
|
|
62
62
|
const code = known.includes(data.error) ? data.error : `http_${response.status}`;
|
|
63
63
|
const next = code === "taken" || code === "reserved_name" ? "Choose another --name using hosted names --name NAME --json before creating."
|
|
64
64
|
: code === "limit" ? "The account is at its agent limit. Read hosted capacity --json and review account plans."
|
|
65
|
+
: code === "conflict" && path.includes("/upstream") ? "Upstream settings changed. Run hosted upstream preview --agent NAME --json, then repeat with its explicit --version."
|
|
65
66
|
: code === "conflict" ? "Wake settings changed. Run wake preview --json, then test the draft again."
|
|
66
67
|
: code === "invalid_reference" ? "The wake reference was already used, expired or does not match this draft. Run wake prepare again with the draft inputs and a new --reference-file."
|
|
67
68
|
: code === "invalid_grant" || code === "insufficient_scope" ? NEXT
|
|
@@ -74,7 +75,7 @@ async function request(origin, path, { token = "", method = "GET", body, fetchIm
|
|
|
74
75
|
export async function capabilities(o = {}) {
|
|
75
76
|
const data = await request(originFor(o), "/api/v1/setup/capabilities");
|
|
76
77
|
if (!Array.isArray(data.scopes) || !Array.isArray(data.wake?.presets)) fail("unsupported", SETUP_UNSUPPORTED);
|
|
77
|
-
return { ok: true, code: "capabilities", next: "Select explicit scopes and a wake preset from this listing.", scopes: data.scopes.filter(s => SETUP_SCOPES.includes(s)), presets: data.wake.presets.filter(s => /^[a-z-]+$/.test(s)), preflight: data.preflight === true, secret_reference: data.wake.secret_reference === true, wake_removal: data.wake.removal === true, secure_link_create: data.wake.secure_link_create === true };
|
|
78
|
+
return { ok: true, code: "capabilities", next: "Select explicit scopes and a wake preset from this listing.", scopes: data.scopes.filter(s => SETUP_SCOPES.includes(s)), presets: data.wake.presets.filter(s => /^[a-z-]+$/.test(s)), upstream: data.upstream?.configuration === true && data.upstream?.verify === true, preflight: data.preflight === true, secret_reference: data.wake.secret_reference === true, wake_removal: data.wake.removal === true, secure_link_create: data.wake.secure_link_create === true };
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
export async function login(o = {}) {
|
|
@@ -283,6 +284,34 @@ export async function wake(sub, o = {}) {
|
|
|
283
284
|
return { ok, code, saved: result.saved === true, next: ok ? sub === "set" ? "Check the receiver for the test wake." : "The draft passed. Run wake set with the same inputs to test and save." : `Saved settings were not changed. ${recovery[code] || "Check the receiver, credentials and draft, then test again."}` };
|
|
284
285
|
}
|
|
285
286
|
|
|
287
|
+
/** Hosted upstream configuration uses only scoped setup or this agent's owner credential. */
|
|
288
|
+
export async function upstream(sub, o = {}) {
|
|
289
|
+
if (!["preview", "set", "clear", "verify"].includes(sub)) fail("usage", "Use hosted upstream preview|set|clear|verify --agent NAME --json. Select NAME with hosted list --json and scopes with hosted capabilities --json.");
|
|
290
|
+
const allowed = new Set(["agent", "json", "control-url", ...(sub === "preview" ? [] : ["version"]), ...(sub === "set" ? ["url", "upstream-token-file", "upstream-token-env", "access-client-id-file", "access-client-id-env", "access-client-secret-file", "access-client-secret-env"] : [])]);
|
|
291
|
+
if (Object.keys(o).some(key => !allowed.has(key))) fail("usage", "Use only the options for hosted upstream preview|set|clear|verify shown in --help; secret inputs belong only to set.");
|
|
292
|
+
if (sub !== "preview" && (!/^(0|[1-9][0-9]*)$/.test(String(o.version ?? "")) || !Number.isSafeInteger(Number(o.version)))) fail("version", "Pass --version N from hosted upstream preview --agent NAME --json; no version is selected automatically.");
|
|
293
|
+
let body = { version: Number(o.version) };
|
|
294
|
+
if (sub === "set") {
|
|
295
|
+
const url = String(o.url || "");
|
|
296
|
+
if (!url || upstreamUrlProblem(url)) fail("url", "Pass --url with a public HTTPS A2A address; use hosted upstream clear to remove it.");
|
|
297
|
+
body = { ...body, url, token: secret(o, "upstream-token", "UPSTREAM_TOKEN"), accessClientId: secret(o, "access-client-id", "UPSTREAM_ACCESS_CLIENT_ID"), accessClientSecret: secret(o, "access-client-secret", "UPSTREAM_ACCESS_CLIENT_SECRET") };
|
|
298
|
+
if (Boolean(body.accessClientId) !== Boolean(body.accessClientSecret)) fail("access", "Supply both Access credentials through protected files or named environment variables. A new address requires both.");
|
|
299
|
+
}
|
|
300
|
+
const { origin, token, name } = wakeCredential(o, true);
|
|
301
|
+
if (!(await capabilities({ "control-url": origin })).upstream) fail("unsupported", "This service does not expose upstream configuration. Run hosted capabilities --json before selecting an operation.");
|
|
302
|
+
const path = `/api/v1/agents/${encodeURIComponent(name)}/upstream`;
|
|
303
|
+
const current = await request(origin, path, { token });
|
|
304
|
+
if (!Number.isSafeInteger(current.version) || typeof current.configured !== "boolean") fail("invalid_response", "No valid upstream version was returned. Run hosted upstream preview --agent NAME --json before retrying.");
|
|
305
|
+
if (sub === "preview") return { ok: true, code: "upstream", version: current.version, configured: current.configured, next: "Review this version, then pass --version N to set, clear or verify. Addresses and credentials are never printed." };
|
|
306
|
+
|
|
307
|
+
if (Number(o.version) !== current.version) fail("conflict", "Upstream settings changed. Run hosted upstream preview --agent NAME --json, review the version and repeat the explicit operation.");
|
|
308
|
+
|
|
309
|
+
const result = await request(origin, sub === "verify" ? `${path}/verify` : path, { token, method: sub === "verify" ? "POST" : sub === "clear" ? "DELETE" : "PUT", body });
|
|
310
|
+
const reasons = ["reachable", "access_credentials_missing", "access_rejected", "upstream_auth_missing", "upstream_auth_rejected", "tunnel_down", "upstream_unavailable", "network", "unexpected_response", "misconfigured"];
|
|
311
|
+
const ok = result.ok === true && (sub === "verify" ? result.reason === "reachable" : sub === "clear" ? result.removed === true : result.saved === true);
|
|
312
|
+
return { ok, code: sub === "verify" ? reasons.includes(result.reason) ? result.reason : "unexpected_response" : ok ? sub === "clear" ? "removed" : "saved" : "failed", ...(Number.isSafeInteger(result.version) ? { version: result.version } : {}), next: sub === "verify" ? ok ? "The saved upstream is reachable. You may pair and send a message." : "Check the connection layer named by code, then verify again. Keep Access and agent authentication enabled." : "Run hosted upstream preview --json, then verify with its explicit version before pairing." };
|
|
313
|
+
}
|
|
314
|
+
|
|
286
315
|
export async function output(run, o = {}) {
|
|
287
316
|
try {
|
|
288
317
|
const result = await run();
|
package/lib/main.mjs
CHANGED
|
@@ -36,9 +36,19 @@ Hosted setup:
|
|
|
36
36
|
check one name anonymously before device approval; never lists other agents
|
|
37
37
|
hosted capacity --json read account room with agents:read (or agents:create); reserves nothing
|
|
38
38
|
hosted list --json list hosted agents with agents:read or agents:create before choosing an agent
|
|
39
|
+
hosted upstream preview|set|clear|verify --agent NAME --json
|
|
40
|
+
list scopes with hosted capabilities and agents with hosted list first
|
|
41
|
+
preview returns version; set/clear/verify require --version N
|
|
42
|
+
set takes --url URL and --upstream-token-file FILE|--upstream-token-env VAR,
|
|
43
|
+
--access-client-id-file FILE|--access-client-id-env VAR and
|
|
44
|
+
--access-client-secret-file FILE|--access-client-secret-env VAR
|
|
45
|
+
files require mode 600; defaults UPSTREAM_TOKEN/UPSTREAM_ACCESS_CLIENT_ID/
|
|
46
|
+
UPSTREAM_ACCESS_CLIENT_SECRET; never pass secret values as arguments
|
|
39
47
|
hosted create --name NAME --json
|
|
40
48
|
create and save the owner credential in this config directory;
|
|
41
49
|
use a separate --config-dir for each agent
|
|
50
|
+
manual fallback: human creates the address in a signed-in dashboard session,
|
|
51
|
+
then saves A2A_BASE_URL + A2A_OWNER_TOKEN in protected config or routine secrets
|
|
42
52
|
wake preview|test|set --agent NAME [--url URL] [--preset P] --json
|
|
43
53
|
hosted settings: read, test a draft, or test then save it
|
|
44
54
|
secrets: --key-file, --key-env, --key-stdin, --hmac-secret-file, --hmac-secret-env,
|
|
@@ -400,7 +410,7 @@ const deployOpts = {
|
|
|
400
410
|
"workers-dev": B, "workers-dev-subdomain": S, "upstream-token-stdin": B, "no-upstream-token": B, "event-secret-stdin": B,
|
|
401
411
|
};
|
|
402
412
|
const SPEC = {
|
|
403
|
-
hosted: { name: S, json: B, agent: S, hostname: S, origin: S, path: S, preset: S, "control-url": S, "secret-file": S, "secret-env": S, "secret-from": S, "hermes-home": S, route: S, "token-file": S, "no-run": B },
|
|
413
|
+
hosted: { url: S, version: S, "upstream-token-file": S, "upstream-token-env": S, "access-client-id-file": S, "access-client-id-env": S, "access-client-secret-file": S, "access-client-secret-env": S, name: S, json: B, agent: S, hostname: S, origin: S, path: S, preset: S, "control-url": S, "secret-file": S, "secret-env": S, "secret-from": S, "hermes-home": S, route: S, "token-file": S, "no-run": B },
|
|
404
414
|
init: { ...deployOpts, ...tunnelOpts, tunnel: B }, deploy: deployOpts,
|
|
405
415
|
wake: { ...deployOpts, json: B, url: S, "reference-file": S, "reference-env": S, clear: B, version: S, agent: S, "control-url": S, "key-header": S, "key-file": S, "key-env": S, "key-stdin": B, "hmac-secret-file": S, "hmac-secret-env": S, "access-client-id-file": S, "access-client-id-env": S, "access-client-secret-file": S, "access-client-secret-env": S }, tunnel: tunnelOpts,
|
|
406
416
|
inbox: { context: S, from: S, all: B, json: B },
|
|
@@ -526,6 +536,7 @@ export async function main(argv) {
|
|
|
526
536
|
throw new CliError(`unknown peer action ${sub} (announce|forward)`);
|
|
527
537
|
}
|
|
528
538
|
case "hosted": {
|
|
539
|
+
if (p[0] === "upstream" && p.length === 2) return hosted.output(() => hosted.upstream(p[1], o), o);
|
|
529
540
|
if (p.length === 1 && ["capabilities", "list", "create", "names", "capacity"].includes(p[0])) return hosted.output(() => hosted[p[0]](o), o);
|
|
530
541
|
if (p.length !== 2 || p[0] !== "link" || !["finish", "create"].includes(p[1])) return linkOutput(linkUsage, o);
|
|
531
542
|
return linkOutput(() => (p[1] === "create" ? createLink : finishLink)({ ...o, "control-url": o["control-url"] || C.get("CONTROL_URL") }), o);
|
package/package.json
CHANGED
package/worker/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Inbox build. Keep this equal to `version` in cli/package.json. */
|
|
2
|
-
export const VERSION = "0.8.
|
|
2
|
+
export const VERSION = "0.8.40";
|