a2a-exposed 0.8.16 → 0.8.18

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/commands.mjs CHANGED
@@ -55,6 +55,7 @@ async function inboxRows(o) {
55
55
  return followInbox(async (cursor) => {
56
56
  const qs = [];
57
57
  if (o.context) qs.push("context=" + q(o.context));
58
+ if (o.from) qs.push("from=" + q(o.from));
58
59
  if (o.all) qs.push("all=1");
59
60
  if (cursor) qs.push("cursor=" + q(cursor));
60
61
  const path = "/owner/inbox" + (qs.length ? "?" + qs.join("&") : "");
@@ -125,7 +126,9 @@ export async function reply(id, o) {
125
126
  }
126
127
 
127
128
  export async function history(ctx, o) {
128
- const rows = await owner("GET", `/owner/history/${q(ctx)}?n=${Number(o.n || 50)}`);
129
+ const qs = new URLSearchParams({ n: String(Number(o.n || 50)) });
130
+ if (o.from) qs.set("from", o.from);
131
+ const rows = await owner("GET", `/owner/history/${q(ctx)}?${qs}`);
129
132
  if (o.json) return out(rows);
130
133
  for (const e of rows) {
131
134
  const tag = [e.event, e.state || e.role].filter(Boolean).join("/");
package/lib/main.mjs CHANGED
@@ -165,9 +165,11 @@ Status
165
165
  config print config (secrets masked) and its location
166
166
 
167
167
  Inbox (owner side)
168
- inbox [--context C] [--all] [--json] unhandled inbound tasks, and a reply to a message you sent until
169
- this list is read (peer text is UNTRUSTED). A reply line says to
170
- read it with history and continue with send. Then pending pairing
168
+ inbox [--context C] [--from PEER] [--all] [--json]
169
+ unhandled inbound tasks, and a reply to a message you sent until
170
+ this list is read (peer text is UNTRUSTED). --from is one peer
171
+ (the wake hint includes it). A reply line says to read it with
172
+ history and continue with send. Then pending pairing
171
173
  requests (tell your human; the dashboard URL when MANAGE_URL is set;
172
174
  --json: rows on stdout, requests on stderr). Follows every page; past
173
175
  10000 pages it errors instead of a short list
@@ -175,7 +177,8 @@ Inbox (owner side)
175
177
  working <taskId> mark task working
176
178
  reply <taskId> (--text T | --stdin) [--state completed|input-required|rejected|failed|working]
177
179
  [--artifact] [--artifact-name N] [--force]
178
- history <contextId> [-n 50] [--json] conversation log
180
+ history <contextId> [--from PEER] [-n 50] [--json]
181
+ conversation log. --from is one peer (the wake hint includes it)
179
182
  contexts recent conversations
180
183
  export [--sql] JSON dump of this A2A endpoint on stdout (peer-token hashes and
181
184
  encrypted peer tokens are in the dump; the owner token stays
@@ -312,9 +315,9 @@ const deployOpts = {
312
315
  };
313
316
  const SPEC = {
314
317
  init: { ...deployOpts, ...tunnelOpts, tunnel: B }, deploy: deployOpts, wake: deployOpts, tunnel: tunnelOpts,
315
- inbox: { context: S, all: B, json: B },
318
+ inbox: { context: S, from: S, all: B, json: B },
316
319
  reply: { text: S, stdin: B, state: { type: "string", default: "completed" }, artifact: B, "artifact-name": { type: "string", default: "response" }, force: B },
317
- history: { n: { type: "string", short: "n", default: "50" }, json: B },
320
+ history: { from: S, n: { type: "string", short: "n", default: "50" }, json: B },
318
321
  token: { rotate: B },
319
322
  peers: { "token-env": S, "token-stdin": B },
320
323
  send: { to: S, text: S, context: S, task: S, push: B, proto: S },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2a-exposed",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
4
4
  "description": "Give any AI agent a public A2A (Agent2Agent) endpoint: a Cloudflare Worker, a wake webhook, or scheduled checks",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,25 @@
1
+ -- A wake is one peer's conversation. Two peers can use the same context id without sharing a row.
2
+ CREATE TABLE wakes_peer (
3
+ context_id TEXT NOT NULL,
4
+ peer TEXT NOT NULL DEFAULT '',
5
+ last_sent_ms INTEGER NOT NULL DEFAULT 0,
6
+ pending_json TEXT,
7
+ pending_since_ms INTEGER,
8
+ PRIMARY KEY (context_id, peer)
9
+ );
10
+ INSERT INTO wakes_peer (context_id, peer, last_sent_ms, pending_json, pending_since_ms)
11
+ SELECT
12
+ w.context_id,
13
+ COALESCE(
14
+ NULLIF(json_extract(w.pending_json, '$.from'), ''),
15
+ (SELECT t.peer FROM tasks t WHERE t.context_id = w.context_id AND t.peer IS NOT NULL AND t.peer != '' ORDER BY t.created_at, t.id LIMIT 1),
16
+ (SELECT h.peer FROM history h WHERE h.context_id = w.context_id AND h.peer IS NOT NULL AND h.peer != '' ORDER BY h.seq LIMIT 1),
17
+ ''
18
+ ),
19
+ w.last_sent_ms,
20
+ w.pending_json,
21
+ w.pending_since_ms
22
+ FROM wakes w;
23
+ DROP TABLE wakes;
24
+ ALTER TABLE wakes_peer RENAME TO wakes;
25
+ CREATE INDEX IF NOT EXISTS history_peer_ctx ON history(peer, context_id, seq);
@@ -0,0 +1,34 @@
1
+ -- One owner per context id. The first peer to insert the row keeps it (ON CONFLICT DO NOTHING).
2
+ CREATE TABLE conversation_owners (
3
+ context_id TEXT NOT NULL PRIMARY KEY,
4
+ peer TEXT NOT NULL,
5
+ created_at TEXT NOT NULL
6
+ );
7
+ INSERT INTO conversation_owners (context_id, peer, created_at)
8
+ SELECT t.context_id, t.peer, t.created_at
9
+ FROM tasks t
10
+ WHERE t.peer IS NOT NULL AND t.peer != ''
11
+ AND NOT EXISTS (
12
+ SELECT 1 FROM tasks t2
13
+ WHERE t2.context_id = t.context_id AND t2.peer IS NOT NULL AND t2.peer != ''
14
+ AND (t2.created_at < t.created_at OR (t2.created_at = t.created_at AND t2.id < t.id))
15
+ );
16
+ INSERT INTO conversation_owners (context_id, peer, created_at)
17
+ SELECT h.context_id, h.peer, h.ts
18
+ FROM history h
19
+ WHERE h.peer IS NOT NULL AND h.peer != ''
20
+ AND NOT EXISTS (SELECT 1 FROM conversation_owners o WHERE o.context_id = h.context_id)
21
+ AND NOT EXISTS (
22
+ SELECT 1 FROM history h2
23
+ WHERE h2.context_id = h.context_id AND h2.peer IS NOT NULL AND h2.peer != '' AND h2.seq < h.seq
24
+ );
25
+ INSERT INTO conversation_owners (context_id, peer, created_at)
26
+ SELECT o.context_id, o.peer, o.sent_at
27
+ FROM outbound o
28
+ WHERE o.peer IS NOT NULL AND o.peer != ''
29
+ AND NOT EXISTS (SELECT 1 FROM conversation_owners c WHERE c.context_id = o.context_id)
30
+ AND NOT EXISTS (
31
+ SELECT 1 FROM outbound o2
32
+ WHERE o2.context_id = o.context_id AND o2.peer IS NOT NULL AND o2.peer != ''
33
+ AND (o2.sent_at < o.sent_at OR (o2.sent_at = o.sent_at AND o2.task_id < o.task_id))
34
+ );
@@ -0,0 +1,13 @@
1
+ -- One row per peer. The same context id can be a separate conversation for each peer.
2
+ -- 0011's single-column primary key made a second peer's insert fail, which is how a caller
3
+ -- could tell a fresh id from one another peer already held.
4
+ CREATE TABLE conversation_owners_peer (
5
+ context_id TEXT NOT NULL,
6
+ peer TEXT NOT NULL,
7
+ created_at TEXT NOT NULL,
8
+ PRIMARY KEY (context_id, peer)
9
+ );
10
+ INSERT INTO conversation_owners_peer (context_id, peer, created_at)
11
+ SELECT context_id, peer, created_at FROM conversation_owners;
12
+ DROP TABLE conversation_owners;
13
+ ALTER TABLE conversation_owners_peer RENAME TO conversation_owners;
@@ -16,7 +16,7 @@ export const EXPORT_TABLES = [
16
16
  "peers", "tasks", "messages", "history", "outbound", "wakes",
17
17
  "rate", "wake_budget", "device_requests", "pairing_rate", "settings",
18
18
  "facade_owners", "oauth_clients", "oauth_codes", "mcp_grants",
19
- "outbound_peers", "oauth_client_metadata", "oidc_txns",
19
+ "outbound_peers", "oauth_client_metadata", "oidc_txns", "conversation_owners",
20
20
  ] as const;
21
21
 
22
22
  export type ExportTable = (typeof EXPORT_TABLES)[number];