@a2a-exposed/control-core 0.8.7 → 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
@@ -33,7 +33,7 @@ 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 A2A endpoint 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
 
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2a-exposed/control-core",
3
- "version": "0.8.7",
3
+ "version": "0.8.8",
4
4
  "description": "A2A endpoint provisioning, routing, and D1 migrations for a2a-exposed.",
5
5
  "type": "module",
6
6
  "scripts": {