@a2a-exposed/control-core 0.8.6 → 0.8.8

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/a2a-exposed)](https://www.npmjs.com/package/a2a-exposed) [![CI](https://github.com/telegraphic-dev/a2a-exposed/actions/workflows/ci.yml/badge.svg)](https://github.com/telegraphic-dev/a2a-exposed/actions/workflows/ci.yml)
4
4
 
5
- a2a-exposed gives an AI agent a public [A2A](https://a2a-protocol.org) address. A Cloudflare Worker serves the agent card and the JSON-RPC endpoint. Inbound messages become tasks in an inbox. Webhooks wake your agent. Without one, it polls, guided by next-check hints. The agent reads the inbox and replies with `npx -y a2a-exposed@latest`. The same command sends messages to other A2A agents (1.0 or 0.3). The owner pages on that inbox are `/` and `/device`.
5
+ a2a-exposed gives an AI agent a public [A2A](https://a2a-protocol.org) address. A Cloudflare Worker serves the agent card and the JSON-RPC endpoint. Inbound messages become tasks on the A2A endpoint. Webhooks wake your agent. Without one, it polls, guided by next-check hints. The agent reads the A2A endpoint and replies with `npx -y a2a-exposed@latest`. The same command sends messages to other A2A agents (1.0 or 0.3). The owner pages on that A2A endpoint are `/` and `/device`.
6
6
 
7
7
  ## Who it's for
8
8
 
@@ -18,7 +18,7 @@ Requires Node 22.18+. The skills are installed into the project the agent works
18
18
  npx -y skills add telegraphic-dev/a2a-exposed
19
19
  ```
20
20
 
21
- 2. **Deploy an inbox** in your Cloudflare account. [Self-hosting](docs/self-hosting.md) covers the login: `cf auth login`, a named profile (`--cf-profile`), an API token, and `--account-id` when that token can see more than one account.
21
+ 2. **Deploy an A2A endpoint** in your Cloudflare account. [Self-hosting](docs/self-hosting.md) covers the login: `cf auth login`, a named profile (`--cf-profile`), an API token, and `--account-id` when that token can see more than one account.
22
22
 
23
23
  ```bash
24
24
  npx -y a2a-exposed@latest init --hostname agent.example.com --agent-name "My Agent"
@@ -33,13 +33,13 @@ Requires Node 22.18+. The skills are installed into the project the agent works
33
33
  npx -y a2a-exposed@latest pair set-password --web
34
34
  ```
35
35
 
36
- `status` is read-only and prints the next step. Open the one-time link (15 minutes by default, `--ttl` from 1 to 60) and choose the approval password yourself. Another agent then runs `npx -y a2a-exposed@latest connect https://agent.example.com` (the code lasts 10 minutes). You approve that request on the `/device` page, or with `npx -y a2a-exposed@latest pair approve <code>`. Removing the inbox later is `npx -y a2a-exposed@latest uninstall` ([Self-hosting](docs/self-hosting.md#remove-an-inbox)).
36
+ `status` is read-only and prints the next step. Open the one-time link (15 minutes by default, `--ttl` from 1 to 60) and choose the approval password yourself. Another agent then runs `npx -y a2a-exposed@latest connect https://agent.example.com` (the code lasts 60 minutes). You approve that request on the `/device` page, or with `npx -y a2a-exposed@latest pair approve <code>`. Removing the A2A endpoint later is `npx -y a2a-exposed@latest uninstall` ([Self-hosting](docs/self-hosting.md#remove-an-inbox)).
37
37
 
38
38
  After the skill is in the project, you can ask the agent to set this up. It follows the setup skill. Do not run setup from a Claude Code cloud session: that environment cannot install, tunnel, or reach your Worker. Claude Code: `/plugin marketplace add telegraphic-dev/a2a-exposed`, then `/plugin install a2a-exposed@a2a-exposed`. [Getting started](docs/getting-started.md) has Node, the client table, and what to do if setup stops halfway.
39
39
 
40
40
  ## Hosted
41
41
 
42
- [a2a.exposed](https://a2a.exposed) hosts the inbox and is open to everyone. Pricing is at [a2a.exposed/pricing](https://a2a.exposed/pricing). When to use it, and the sentence to say, is in [Getting started](docs/getting-started.md#hosted-inbox).
42
+ [a2a.exposed](https://a2a.exposed) hosts the A2A endpoint and is open to everyone. Pricing is at [a2a.exposed/pricing](https://a2a.exposed/pricing). When to use it, and the sentence to say, is in [Getting started](docs/getting-started.md#hosted-inbox).
43
43
 
44
44
  ## Docs
45
45
 
@@ -47,13 +47,13 @@ After the skill is in the project, you can ask the agent to set this up. It foll
47
47
  | --- | --- |
48
48
  | [Getting started](docs/getting-started.md) | Skills, Node, first deploy, pairing |
49
49
  | [Receiving messages](docs/receiving-messages.md) | Webhooks wake your agent. Without one, it polls, guided by next-check hints |
50
- | [Self-hosting](docs/self-hosting.md) | Cloudflare inbox, workers.dev, tunnel, uninstall, public façade |
50
+ | [Self-hosting](docs/self-hosting.md) | Cloudflare A2A endpoint, workers.dev, tunnel, uninstall, public façade |
51
51
  | [CLI](docs/cli.md) | Commands, flags, config |
52
52
  | [MCP](docs/mcp.md) | Remote connector at `/mcp` |
53
53
  | [Feedback](docs/feedback.md) | Report a bug or an idea |
54
54
  | [Security](docs/security.md) | Tokens, pairing, wakes, façade |
55
55
  | [Troubleshooting](docs/troubleshooting.md) | FAQ and common failures |
56
- | [Upgrading](docs/upgrading.md) | Move an existing inbox forward |
56
+ | [Upgrading](docs/upgrading.md) | Move an existing A2A endpoint forward |
57
57
  | [Protocol](docs/protocol.md) | A2A 1.0 and 0.3, OAuth, endpoints |
58
58
 
59
59
  Changes are listed in [CHANGELOG.md](CHANGELOG.md). Agent workflows live in [`skills/`](skills/a2a-exposed/SKILL.md).
package/dist/index.js CHANGED
@@ -2097,7 +2097,45 @@ async function releaseWorking(db, inboxId, now) {
2097
2097
  const row = await readInboxById(db, inboxId);
2098
2098
  if (row?.status === "working") await patchInbox(db, inboxId, now, { status: "pending" });
2099
2099
  }
2100
+ function httpsIssuer(value) {
2101
+ const raw = value.trim();
2102
+ if (!raw || raw.length > 300) return "";
2103
+ try {
2104
+ const url = new URL(raw);
2105
+ if (url.protocol !== "https:" || url.username || url.password) return "";
2106
+ return raw;
2107
+ } catch {
2108
+ return "";
2109
+ }
2110
+ }
2111
+ function plainCredential(value, max) {
2112
+ const text = value.trim();
2113
+ if (!text || text.length > max || /[\u0000-\u001f]/.test(text)) return "";
2114
+ return text;
2115
+ }
2116
+ function subjectsText(value) {
2117
+ const list = typeof value === "string" ? value.split(",") : value;
2118
+ const subjects = list.map((item) => item.trim()).filter((item) => item && !/[\u0000-\u001f]/.test(item));
2119
+ const text = subjects.join(",");
2120
+ return text && text.length <= 2e3 ? text : "";
2121
+ }
2122
+ function inboxApprovalBindings(input) {
2123
+ if (!input) return null;
2124
+ const issuer = httpsIssuer(input.issuer);
2125
+ const clientId = plainCredential(input.clientId, 200);
2126
+ const clientSecret = plainCredential(input.clientSecret, 512);
2127
+ const allowed = subjectsText(input.allowedSubjects);
2128
+ if (!issuer || !clientId || !clientSecret || !allowed) return null;
2129
+ return [
2130
+ { type: "plain_text", name: "APPROVAL_OIDC_ISSUER", text: issuer },
2131
+ { type: "plain_text", name: "APPROVAL_OIDC_CLIENT_ID", text: clientId },
2132
+ { type: "secret_text", name: "APPROVAL_OIDC_CLIENT_SECRET", text: clientSecret },
2133
+ { type: "plain_text", name: "APPROVAL_OIDC_ALLOWED_SUBJECTS", text: allowed },
2134
+ { type: "plain_text", name: "APPROVAL_METHODS", text: "oidc" }
2135
+ ];
2136
+ }
2100
2137
  function scriptMetadata(input) {
2138
+ const approval = inboxApprovalBindings(input.approvalOidc);
2101
2139
  return {
2102
2140
  main_module: "index.js",
2103
2141
  compatibility_date: INBOX_COMPATIBILITY_DATE,
@@ -2108,7 +2146,8 @@ function scriptMetadata(input) {
2108
2146
  { type: "plain_text", name: "PAIRING_APPROVAL", text: "human" },
2109
2147
  { type: "plain_text", name: "WAKE_PRESET", text: "generic" },
2110
2148
  { type: "plain_text", name: PROVISION_TAG_NAME, text: input.scriptTag },
2111
- { type: "secret_text", name: "OWNER_TOKEN", text: input.ownerToken }
2149
+ { type: "secret_text", name: "OWNER_TOKEN", text: input.ownerToken },
2150
+ ...approval ?? []
2112
2151
  ]
2113
2152
  };
2114
2153
  }
@@ -2527,7 +2566,8 @@ async function runInboxStep(input) {
2527
2566
  script: input.script,
2528
2567
  migrations: input.migrations,
2529
2568
  grant,
2530
- permissionDelaysMs: input.permissionDelaysMs ?? PERMISSION_DELAYS_MS
2569
+ permissionDelaysMs: input.permissionDelaysMs ?? PERMISSION_DELAYS_MS,
2570
+ approvalOidc: input.approvalOidc
2531
2571
  };
2532
2572
  try {
2533
2573
  if (next !== "verify") {
@@ -2813,7 +2853,7 @@ async function runUpload(ctx) {
2813
2853
  accountId,
2814
2854
  name: workerName,
2815
2855
  script: ctx.script,
2816
- metadata: scriptMetadata({ d1Id: databaseId, publicUrl, workerName, ownerToken, scriptTag })
2856
+ metadata: scriptMetadata({ d1Id: databaseId, publicUrl, workerName, ownerToken, scriptTag, approvalOidc: ctx.approvalOidc })
2817
2857
  });
2818
2858
  if (uploaded2 !== true) {
2819
2859
  return { wizard: await failOrWait(ctx, "upload", uploaded2, "Cloudflare stops while uploading the inbox.", "upload") };
@@ -3079,6 +3119,7 @@ export {
3079
3119
  hashOwnerToken,
3080
3120
  headerSafeToken,
3081
3121
  httpsOrigin,
3122
+ inboxApprovalBindings,
3082
3123
  inboxCanProvision,
3083
3124
  listAccounts,
3084
3125
  listCloudflareInboxes,
@@ -26,8 +26,8 @@ export * from "../provision/statements.js";
26
26
  export * from "../provision/permission-wait.js";
27
27
  export * from "../provision/inbox-row.js";
28
28
  export * from "../provision/remove.js";
29
- export { INBOX_COMPATIBILITY_DATE, PERMISSION_DELAYS_MS, PERMISSION_TRIES, WIZARD_STEPS, createCloudflareAgent, dashboardLinks, fieldsFromForm, permissionRetryDue, resumeFields, runInboxStep, viewInboxWizard, } from "../provision/inbox.js";
30
- export type { InboxMigration, InboxWizard, ProvisionFields, WizardControl, WizardState, WizardStepId, WizardStepView, } from "../provision/inbox.js";
29
+ export { INBOX_COMPATIBILITY_DATE, PERMISSION_DELAYS_MS, PERMISSION_TRIES, WIZARD_STEPS, createCloudflareAgent, dashboardLinks, fieldsFromForm, permissionRetryDue, inboxApprovalBindings, resumeFields, runInboxStep, viewInboxWizard, } from "../provision/inbox.js";
30
+ export type { ApprovalOidcInput, InboxMigration, InboxWizard, ProvisionFields, WizardControl, WizardState, WizardStepId, WizardStepView, } from "../provision/inbox.js";
31
31
  export { INBOX_MIGRATIONS } from "../provision/inbox-migrations.js";
32
32
  export { CONTROL_MIGRATIONS, CONTROL_MIGRATIONS_DIR } from "./migrations.js";
33
33
  export type { ControlMigration } from "./migrations.js";
@@ -89,6 +89,31 @@ interface StoredStepNote {
89
89
  }
90
90
  /** A new Cloudflare agent. The finished ones stay. */
91
91
  export declare function createCloudflareAgent(db: Sql, userId: string, host: SetupHost, now?: number): Promise<string>;
92
+ /** Account sign-in for a Cloudflare agent. The dashboard creates the client and passes these four fields. */
93
+ export type ApprovalOidcInput = {
94
+ issuer: string;
95
+ clientId: string;
96
+ clientSecret: string;
97
+ allowedSubjects: string | readonly string[];
98
+ };
99
+ type ScriptBinding = {
100
+ type: "d1";
101
+ name: string;
102
+ id: string;
103
+ } | {
104
+ type: "plain_text";
105
+ name: string;
106
+ text: string;
107
+ } | {
108
+ type: "secret_text";
109
+ name: string;
110
+ text: string;
111
+ };
112
+ /**
113
+ * Worker bindings for account sign-in. Returns the list when the issuer is https and every field
114
+ * has a value. Otherwise returns null and the upload keeps the approval password.
115
+ */
116
+ export declare function inboxApprovalBindings(input: ApprovalOidcInput | undefined): ScriptBinding[] | null;
92
117
  export declare function fieldsFromForm(form: FormData): ProvisionFields;
93
118
  export declare function resumeFields(value: {
94
119
  name?: unknown;
@@ -136,6 +161,12 @@ export declare function runInboxStep(input: {
136
161
  fetchImpl?: typeof fetch;
137
162
  /** Pauses after a permission error. Tests pass zeros. Production waits about 15 seconds across 4 tries. */
138
163
  permissionDelaysMs?: number[];
164
+ /**
165
+ * Account sign-in for this Cloudflare agent. When issuer, client id, client secret, and allowed
166
+ * subjects are all set, the upload writes the APPROVAL_OIDC_* bindings and APPROVAL_METHODS=oidc.
167
+ * Omit it and the agent stays on the approval password.
168
+ */
169
+ approvalOidc?: ApprovalOidcInput;
139
170
  }): Promise<{
140
171
  wizard: InboxWizard;
141
172
  ownerToken?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a2a-exposed/control-core",
3
- "version": "0.8.6",
4
- "description": "Inbox provisioning, routing, and D1 migrations for a2a-exposed.",
3
+ "version": "0.8.8",
4
+ "description": "A2A endpoint provisioning, routing, and D1 migrations for a2a-exposed.",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "prepare": "node control/scripts/build-core.mjs",