@loomwork/plugin-a2a 0.0.0-stage → 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +8 -0
  3. package/README.md +80 -2
  4. package/cards.d.ts +1 -0
  5. package/dist/A2AClientEffect.d.ts +11 -0
  6. package/dist/A2AClientEffect.d.ts.map +1 -0
  7. package/dist/A2AClientEffect.js +89 -0
  8. package/dist/A2AClientEffect.js.map +1 -0
  9. package/dist/A2AEffect.d.ts +13 -0
  10. package/dist/A2AEffect.d.ts.map +1 -0
  11. package/dist/A2AEffect.js +20 -0
  12. package/dist/A2AEffect.js.map +1 -0
  13. package/dist/A2APeerClient.d.ts +126 -0
  14. package/dist/A2APeerClient.d.ts.map +1 -0
  15. package/dist/A2APeerClient.js +167 -0
  16. package/dist/A2APeerClient.js.map +1 -0
  17. package/dist/A2AServer.d.ts +78 -0
  18. package/dist/A2AServer.d.ts.map +1 -0
  19. package/dist/A2AServer.js +239 -0
  20. package/dist/A2AServer.js.map +1 -0
  21. package/dist/AgentExecutor.d.ts +89 -0
  22. package/dist/AgentExecutor.d.ts.map +1 -0
  23. package/dist/AgentExecutor.js +126 -0
  24. package/dist/AgentExecutor.js.map +1 -0
  25. package/dist/index.d.ts +12 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +20 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/toAgentCard.d.ts +11 -0
  30. package/dist/toAgentCard.d.ts.map +1 -0
  31. package/dist/toAgentCard.js +19 -0
  32. package/dist/toAgentCard.js.map +1 -0
  33. package/dist/types.d.ts +41 -0
  34. package/dist/types.d.ts.map +1 -0
  35. package/dist/types.js +3 -0
  36. package/dist/types.js.map +1 -0
  37. package/package.json +65 -3
  38. package/src/A2AClientEffect.ts +109 -0
  39. package/src/A2AEffect.ts +16 -0
  40. package/src/A2APeerClient.ts +298 -0
  41. package/src/A2AServer.ts +309 -0
  42. package/src/AgentExecutor.ts +192 -0
  43. package/src/index.ts +35 -0
  44. package/src/toAgentCard.ts +31 -0
  45. package/src/types.ts +41 -0
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.A2APeerClient = void 0;
4
+ exports.fetchAgentCard = fetchAgentCard;
5
+ const classify_1 = require("@loomwork/classify");
6
+ const core_1 = require("@loomwork/core");
7
+ const core_2 = require("@loomwork/core");
8
+ const zod_1 = require("zod");
9
+ const A2AClientEffect_1 = require("./A2AClientEffect");
10
+ const A2AEffect_1 = require("./A2AEffect");
11
+ const peerInputSchema = zod_1.z.object({
12
+ prompt: zod_1.z.string(),
13
+ context: zod_1.z.unknown().optional(),
14
+ });
15
+ // `content` validates BOTH shapes because one schema backs both flag settings.
16
+ // It is the runtime contract; the conditional type is what narrows it per caller.
17
+ const peerOutputSchema = zod_1.z.object({
18
+ content: zod_1.z.union([
19
+ zod_1.z.string(),
20
+ zod_1.z.object({ __loomTainted: zod_1.z.object({ origin: zod_1.z.string() }), value: zod_1.z.string() }),
21
+ ]),
22
+ raw: zod_1.z.unknown(),
23
+ });
24
+ /** Fetch a remote agent's AgentCard from its well-known discovery endpoint. */
25
+ async function fetchAgentCard(url, headers = {}) {
26
+ const resp = await fetch(`${url}/.well-known/agent.json`, {
27
+ headers: { accept: "application/json", ...headers },
28
+ });
29
+ if (!resp.ok) {
30
+ throw new Error(`A2A discovery failed ${resp.status}: ${await resp.text()}`);
31
+ }
32
+ return (await resp.json());
33
+ }
34
+ class A2APeerClient {
35
+ config;
36
+ card;
37
+ constructor(config) {
38
+ this.config = config;
39
+ }
40
+ /** Fetch and cache the remote AgentCard. */
41
+ async discover() {
42
+ if (this.card === undefined) {
43
+ this.card = await fetchAgentCard(this.config.url, this.config.headers);
44
+ }
45
+ return this.card;
46
+ }
47
+ /** Discover the peer and build its Loom tool definition + effect (no registration). */
48
+ async map() {
49
+ const card = await this.discover();
50
+ const toolId = this.config.toolId ?? slug(card.name);
51
+ const effect = this.buildEffect(toolId);
52
+ const definition = (0, core_1.tool$)({
53
+ id: toolId,
54
+ name: card.name,
55
+ description: describe(card),
56
+ kind: "a2a",
57
+ // R22 (ADR 0125 D7) — built from a REMOTE agent card: tool search must not rank or show its text.
58
+ origin: "remote",
59
+ ...(this.config.trustDescriptions === true && { trustJsonSchemaText: true }),
60
+ // loom's own static schema (not the card's): its `.describe()` text is loom-authored, so it is kept.
61
+ schemaOrigin: "local",
62
+ inputSchema: peerInputSchema,
63
+ outputSchema: peerOutputSchema,
64
+ });
65
+ return { card, definition, effect };
66
+ }
67
+ /**
68
+ * Discover the peer, register it as an effect, and return the tool definition.
69
+ * Pass a registry to also expose the peer to the planner/intent layer.
70
+ */
71
+ async registerInto(effectRuntime, registry) {
72
+ const { card, definition, effect } = await this.map();
73
+ effectRuntime.register(definition.id, effect);
74
+ if (registry !== undefined) {
75
+ const remoteAgent = (0, core_2.agent$)({
76
+ id: definition.id,
77
+ name: card.name,
78
+ ...(card.description !== undefined && { description: card.description }),
79
+ tools: [definition],
80
+ version: card.version,
81
+ capabilities: card.capabilities,
82
+ skills: card.skills,
83
+ });
84
+ registry.register(remoteAgent, toRegisteredCard(card));
85
+ }
86
+ return definition;
87
+ }
88
+ /** Adapter effect: friendly input -> A2ATask -> underlying client -> flattened output. */
89
+ buildEffect(toolId) {
90
+ const clientOptions = {
91
+ ...(this.config.headers !== undefined && { headers: this.config.headers }),
92
+ ...(this.config.timeoutMs !== undefined && { timeoutMs: this.config.timeoutMs }),
93
+ ...(this.config.pollIntervalMs !== undefined && {
94
+ pollIntervalMs: this.config.pollIntervalMs,
95
+ }),
96
+ };
97
+ const client = (0, A2AClientEffect_1.createA2AClientEffect)(this.config.url, clientOptions);
98
+ return new A2AEffect_1.A2AEffect(async (input, ctx) => {
99
+ const task = {
100
+ // Deterministic id derived from the execution so replays stay stable.
101
+ // A transient call stamped as an execution (ctx.decide's scope) shares
102
+ // (executionId, stepId) with its siblings; callKey keeps their ids distinct.
103
+ id: `${ctx.executionId}:${ctx.stepId}${ctx.callKey !== undefined ? `:${ctx.callKey}` : ""}`,
104
+ message: { role: "user", parts: [{ type: "text", text: input.prompt }] },
105
+ ...(input.context !== undefined && { metadata: { context: input.context } }),
106
+ };
107
+ const result = await client.execute(task, ctx);
108
+ const content = artifactText(result);
109
+ // Taint at INGRESS — the wrapper IS the step output, so it is what
110
+ // STEP_COMPLETED records and what a replay hands back. The cast is the
111
+ // one place the conditional return type cannot be proved structurally.
112
+ if (this.config.taintResults !== true) {
113
+ return { content, raw: result };
114
+ }
115
+ const origin = `a2a:${toolId}`;
116
+ const { artifacts, error, ...state } = result;
117
+ return {
118
+ content: (0, classify_1.tainted)(content, origin),
119
+ raw: {
120
+ ...state,
121
+ ...(artifacts !== undefined && { artifacts: (0, classify_1.tainted)(artifacts, origin) }),
122
+ ...(error !== undefined && { error: (0, classify_1.tainted)(error, origin) }),
123
+ },
124
+ };
125
+ });
126
+ }
127
+ }
128
+ exports.A2APeerClient = A2APeerClient;
129
+ /** Flatten all text parts across all artifacts of a completed/failed A2A result. */
130
+ function artifactText(result) {
131
+ if (result.error !== undefined) {
132
+ return result.error;
133
+ }
134
+ return (result.artifacts ?? [])
135
+ .flatMap((artifact) => artifact.parts.map((part) => part.text))
136
+ .join("\n");
137
+ }
138
+ /** Build an LLM-facing tool description from the card's summary and skills. */
139
+ function describe(card) {
140
+ const base = card.description ?? card.name;
141
+ if (card.skills.length === 0) {
142
+ return base;
143
+ }
144
+ const skills = card.skills.map((s) => s.name).join(", ");
145
+ return `${base} (skills: ${skills})`;
146
+ }
147
+ function toRegisteredCard(card) {
148
+ return {
149
+ name: card.name,
150
+ ...(card.description !== undefined && { description: card.description }),
151
+ url: card.url,
152
+ version: card.version,
153
+ capabilities: card.capabilities,
154
+ skills: card.skills.map((s) => ({
155
+ id: s.id,
156
+ name: s.name,
157
+ ...(s.description !== undefined && { description: s.description }),
158
+ })),
159
+ };
160
+ }
161
+ function slug(name) {
162
+ return (name
163
+ .toLowerCase()
164
+ .replace(/[^a-z0-9]+/g, "-")
165
+ .replace(/^-+|-+$/g, "") || "a2a-peer");
166
+ }
167
+ //# sourceMappingURL=A2APeerClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"A2APeerClient.js","sourceRoot":"","sources":["../src/A2APeerClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import * as http from "node:http";
2
+ import type { PrincipalChain } from "@loomwork/effect-runtime";
3
+ import type { RegisteredFlow, WorkflowRuntime } from "@loomwork/workflow-runtime";
4
+ import type { AgentCard } from "./types";
5
+ export interface A2AServerOptions {
6
+ port?: number;
7
+ /**
8
+ * R12 — the interface to bind. Default `"127.0.0.1"` (loopback only): a server is reachable
9
+ * from this machine and nothing else until you widen it deliberately (`"0.0.0.0"`, `"::"`, or
10
+ * a specific interface address). Binding all interfaces was the old default.
11
+ */
12
+ host?: string;
13
+ /**
14
+ * R12 — the URL the agent card advertises, when it differs from the bind address (a server bound
15
+ * to `"0.0.0.0"` would otherwise advertise `localhost`, which no remote peer can reach). Set it
16
+ * to the externally reachable base URL (reverse proxy / load balancer). Default: derived from
17
+ * `host` and the bound port.
18
+ */
19
+ publicUrl?: string;
20
+ /**
21
+ * R12 — authenticate AND identify the caller in one step: return the caller's
22
+ * {@link PrincipalChain}, or `undefined` to refuse the request (401). Derive it from connection
23
+ * facts only (verified bearer token, mTLS peer) — never from the task body. This is the
24
+ * recommended gate: it also scopes task ownership to the principal (see below).
25
+ *
26
+ * The principal MUST carry a non-empty string `actor` (task ownership is keyed on it); any other
27
+ * principal is refused with 401 and a one-time warning. When both this and
28
+ * {@link resolvePrincipal} are set, THIS wins and `resolvePrincipal` is ignored.
29
+ */
30
+ authenticate?: (req: http.IncomingMessage) => PrincipalChain | undefined | Promise<PrincipalChain | undefined>;
31
+ /**
32
+ * Gate the task routes (`POST /tasks/send`, `GET /tasks/:id`). Return `true` to allow. The
33
+ * agent-card route stays open. A boolean carries no identity, so every caller that passes it
34
+ * shares ONE task namespace unless `resolvePrincipal`/`authenticate` says who they are.
35
+ *
36
+ * R12 — the server REFUSES TO BE CREATED with neither `authenticate` nor `authorize` unless
37
+ * {@link insecure} is `true`.
38
+ */
39
+ authorize?: (req: http.IncomingMessage) => boolean | Promise<boolean>;
40
+ /**
41
+ * R12 — explicit opt-out of authentication: any caller that can reach the socket can submit
42
+ * tasks. Logs a loud warning at creation. Intended for local demos and tests; combine with the
43
+ * loopback default. Refused when {@link strict} is set.
44
+ */
45
+ insecure?: boolean;
46
+ /**
47
+ * R12 — `defineConfig`'s `security.strict`. Under strict the server is a config error unless it
48
+ * authenticates AND resolves a principal; `insecure: true` is refused; and a caller that
49
+ * resolves to no principal is refused (401). DEFAULTS from the runtime you pass
50
+ * (`workflowRuntime.requiresPrincipal`), so a strict runtime makes the server strict without
51
+ * this flag; set it explicitly to override.
52
+ */
53
+ strict?: boolean;
54
+ /** Where the insecure-mode warning goes. Default `console.warn`. */
55
+ warn?: (message: string) => void;
56
+ /**
57
+ * R3 — the AUTHENTICATED principal behind a request, derived from the same connection facts
58
+ * `authorize` checked (mTLS peer, verified bearer token, signed header) — NEVER from the task
59
+ * body. Applied to the runtime call for every task route (`/tasks/send`, `GET /tasks/:id`).
60
+ * Without it every task is anonymous, which a runtime that fails closed (`security.strict`)
61
+ * refuses (`ExecutionDeniedError`): an A2A server is an edge, and under strict an edge must
62
+ * say who is calling. Returning `undefined` for a request means "anonymous".
63
+ */
64
+ resolvePrincipal?: (req: http.IncomingMessage) => PrincipalChain | undefined | Promise<PrincipalChain | undefined>;
65
+ /**
66
+ * Called with the real error behind a 500, alongside the `errorId` the caller was given.
67
+ * The response itself carries no internal detail, so this is the only place it surfaces —
68
+ * wire it to your logger or the detail is simply lost.
69
+ */
70
+ onError?: (errorId: string, err: unknown) => void;
71
+ }
72
+ export interface A2AServer {
73
+ start(): Promise<void>;
74
+ stop(): Promise<void>;
75
+ readonly url: string;
76
+ }
77
+ export declare function createA2AServer(workflowRuntime: WorkflowRuntime, flow: RegisteredFlow, agentCard: Omit<AgentCard, "url">, options?: A2AServerOptions): A2AServer;
78
+ //# sourceMappingURL=A2AServer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"A2AServer.d.ts","sourceRoot":"","sources":["../src/A2AServer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElF,OAAO,KAAK,EAAW,SAAS,EAAE,MAAM,SAAS,CAAC;AAElD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;OASG;IACH,YAAY,CAAC,EAAE,CACb,GAAG,EAAE,IAAI,CAAC,eAAe,KACtB,cAAc,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACtE;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,CACjB,GAAG,EAAE,IAAI,CAAC,eAAe,KACtB,cAAc,GAAG,SAAS,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IACtE;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAyCD,wBAAgB,eAAe,CAC7B,eAAe,EAAE,eAAe,EAChC,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,EACjC,OAAO,GAAE,gBAAqB,GAC7B,SAAS,CAkLX"}
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createA2AServer = createA2AServer;
37
+ const node_crypto_1 = require("node:crypto");
38
+ const http = __importStar(require("node:http"));
39
+ const core_1 = require("@loomwork/core");
40
+ const AgentExecutor_1 = require("./AgentExecutor");
41
+ // Cap request bodies so an unauthenticated caller can't OOM the process by streaming a
42
+ // huge (or endless) body into memory before parse. Matches the isolate broker's 10 MB cap.
43
+ const MAX_BODY_BYTES = 10 * 1024 * 1024;
44
+ // `task.id` becomes the executionId, which is embedded in event ids and carried in the
45
+ // Postgres NOTIFY payload (itself capped at ~8 KB). Generous for a ULID or a UUID.
46
+ const MAX_TASK_ID_LENGTH = 200;
47
+ function parseBody(req) {
48
+ return new Promise((resolve, reject) => {
49
+ const chunks = [];
50
+ let size = 0;
51
+ req.on("data", (chunk) => {
52
+ size += chunk.length;
53
+ if (size > MAX_BODY_BYTES) {
54
+ req.destroy();
55
+ reject(new core_1.BadRequestError(`request body exceeds ${MAX_BODY_BYTES} bytes`));
56
+ return;
57
+ }
58
+ chunks.push(chunk);
59
+ });
60
+ req.on("end", () => {
61
+ try {
62
+ const raw = Buffer.concat(chunks);
63
+ resolve(JSON.parse(raw.toString("utf-8")));
64
+ }
65
+ catch {
66
+ // The caller's own malformed JSON — safe to name, and useless to hide.
67
+ reject(new core_1.BadRequestError("request body is not valid JSON"));
68
+ }
69
+ });
70
+ req.on("error", (err) => reject(err));
71
+ });
72
+ }
73
+ function json(res, status, body) {
74
+ const payload = JSON.stringify(body);
75
+ res.writeHead(status, { "content-type": "application/json" });
76
+ res.end(payload);
77
+ }
78
+ function createA2AServer(workflowRuntime, flow, agentCard, options = {}) {
79
+ const port = options.port ?? 3001;
80
+ const host = options.host ?? "127.0.0.1";
81
+ const authenticated = options.authenticate !== undefined || options.authorize !== undefined;
82
+ const strict = options.strict ?? workflowRuntime.requiresPrincipal === true;
83
+ if (strict) {
84
+ if (options.insecure === true) {
85
+ throw new Error("createA2AServer: `insecure: true` is refused under security.strict — authenticate callers.");
86
+ }
87
+ if (options.authenticate === undefined && options.resolvePrincipal === undefined) {
88
+ throw new Error("createA2AServer: security.strict requires `authenticate` (or `resolvePrincipal`) — every task must carry the caller's principal.");
89
+ }
90
+ }
91
+ if (!authenticated && options.insecure !== true) {
92
+ throw new Error("createA2AServer: refusing to create an unauthenticated server. Supply `authenticate` (recommended) or `authorize`, or pass `insecure: true` to opt out explicitly.");
93
+ }
94
+ if (!authenticated) {
95
+ (options.warn ?? ((m) => console.warn(m)))(`[loom] WARNING: A2A server is running with insecure: true — NO authentication; any caller that can reach ${host}:${port} can submit and read tasks.`);
96
+ }
97
+ const urlHost = host === "0.0.0.0" || host === "::" ? "localhost" : host.includes(":") ? `[${host}]` : host;
98
+ let resolvedUrl = `http://${urlHost}:${port}`;
99
+ const warn = options.warn ?? ((m) => console.warn(m));
100
+ let warnedNoActor = false;
101
+ // The whole A2A layer is this one adapter; the server just moves bytes to it. Task ids are
102
+ // namespaced by principal (R12): the client's id is only a label within its own namespace.
103
+ const executor = new AgentExecutor_1.LoomAgentExecutor(workflowRuntime, flow, { namespaceByPrincipal: true });
104
+ const server = http.createServer((req, res) => {
105
+ void (async () => {
106
+ const url = req.url ?? "/";
107
+ const method = req.method ?? "GET";
108
+ try {
109
+ if (method === "GET" && url === "/.well-known/agent.json") {
110
+ json(res, 200, { ...agentCard, url: options.publicUrl ?? resolvedUrl });
111
+ return;
112
+ }
113
+ // Gate the task routes when an authorizer is wired; the agent card stays open.
114
+ if (options.authorize !== undefined && url !== "/.well-known/agent.json") {
115
+ if (!(await options.authorize(req))) {
116
+ json(res, 401, { error: "unauthorized" });
117
+ return;
118
+ }
119
+ }
120
+ // R12 — authenticate: identity or refusal.
121
+ let principal;
122
+ if (options.authenticate !== undefined) {
123
+ principal = await options.authenticate(req);
124
+ if (principal === undefined) {
125
+ json(res, 401, { error: "unauthorized" });
126
+ return;
127
+ }
128
+ }
129
+ // R3 — who is calling, resolved once per request, after `authorize`.
130
+ principal ??= await options.resolvePrincipal?.(req);
131
+ // R12 — fail closed: a principal that cannot be given its own namespace (no actor) must
132
+ // not be pooled with other such callers; under strict an unidentified caller is refused.
133
+ if (principal !== undefined && !(0, AgentExecutor_1.hasActor)(principal)) {
134
+ if (!warnedNoActor) {
135
+ warnedNoActor = true;
136
+ warn("[loom] A2A server: refusing a caller whose principal has no non-empty string `actor` — `authenticate`/`resolvePrincipal` must return an `actor` (task ownership is keyed on it).");
137
+ }
138
+ json(res, 401, { error: "unauthorized" });
139
+ return;
140
+ }
141
+ if (principal === undefined && strict) {
142
+ json(res, 401, { error: "unauthorized" });
143
+ return;
144
+ }
145
+ // The task namespace: the principal when known; one shared bucket for callers that were
146
+ // authorized but not identified; "anonymous" under `insecure`.
147
+ const call = {
148
+ ...(principal !== undefined && { principal }),
149
+ ...(principal === undefined && authenticated && { namespace: "authorized" }),
150
+ };
151
+ if (method === "POST" && url === "/tasks/send") {
152
+ const task = (await parseBody(req));
153
+ // `task.id` becomes the executionId VERBATIM, so it is screened here rather than
154
+ // only deep in the kernel — a rejected id should read as a 400 from the adapter,
155
+ // not surface as a 500 from the runtime.
156
+ if (typeof task?.id !== "string" || task.id.length === 0) {
157
+ json(res, 400, { error: "task.id must be a non-empty string" });
158
+ return;
159
+ }
160
+ // The kernel reserves `__loom_*` for its own control streams (retention lifecycle,
161
+ // erasure). An execution created there both corrupts that audit trail and becomes
162
+ // invisible to every enumerator, so retention never reclaims it.
163
+ if (task.id.startsWith("__loom_")) {
164
+ json(res, 400, { error: 'task.id must not use the reserved "__loom_" prefix' });
165
+ return;
166
+ }
167
+ // Bound it: the id is embedded in every event id and travels in the Postgres
168
+ // NOTIFY payload, which is itself capped — an unbounded id turns into failing
169
+ // appends rather than a rejected request.
170
+ if (task.id.length > MAX_TASK_ID_LENGTH) {
171
+ json(res, 400, { error: `task.id exceeds ${MAX_TASK_ID_LENGTH} characters` });
172
+ return;
173
+ }
174
+ const { id } = await executor.submit(task, call);
175
+ json(res, 202, { id });
176
+ return;
177
+ }
178
+ const taskMatch = /^\/tasks\/([^/]+)$/.exec(url);
179
+ if (method === "GET" && taskMatch !== null) {
180
+ const rawTaskId = taskMatch[1];
181
+ if (rawTaskId === undefined) {
182
+ json(res, 400, { error: "missing task id" });
183
+ return;
184
+ }
185
+ // The path segment is percent-encoded on the wire (a client MUST encode an id
186
+ // containing `/` or other reserved characters, or URL normalization upstream of this
187
+ // server — e.g. `fetch`'s own dot-segment collapsing — could otherwise turn an id like
188
+ // `../admin` into a request for a different route entirely). Decode it back to the
189
+ // real task id before it reaches the executor.
190
+ let taskId;
191
+ try {
192
+ taskId = decodeURIComponent(rawTaskId);
193
+ }
194
+ catch {
195
+ json(res, 400, { error: "task id is not validly percent-encoded" });
196
+ return;
197
+ }
198
+ const result = await executor.get(taskId, call);
199
+ if (result === null) {
200
+ json(res, 404, { error: `task ${taskId} not found` });
201
+ return;
202
+ }
203
+ json(res, 200, result);
204
+ return;
205
+ }
206
+ json(res, 404, { error: "not found" });
207
+ }
208
+ catch (err) {
209
+ const exposed = (0, core_1.exposeError)(err, node_crypto_1.randomUUID);
210
+ if (exposed.internal !== undefined) {
211
+ options.onError?.(exposed.body.errorId, exposed.internal);
212
+ }
213
+ json(res, exposed.status, exposed.body);
214
+ }
215
+ })();
216
+ });
217
+ return {
218
+ get url() {
219
+ return resolvedUrl;
220
+ },
221
+ start() {
222
+ return new Promise((resolve) => {
223
+ server.listen(port, host, () => {
224
+ const addr = server.address();
225
+ if (addr !== null && typeof addr === "object") {
226
+ resolvedUrl = `http://${urlHost}:${addr.port}`;
227
+ }
228
+ resolve();
229
+ });
230
+ });
231
+ },
232
+ stop() {
233
+ return new Promise((resolve, reject) => {
234
+ server.close((err) => (err !== undefined ? reject(err) : resolve()));
235
+ });
236
+ },
237
+ };
238
+ }
239
+ //# sourceMappingURL=A2AServer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"A2AServer.js","sourceRoot":"","sources":["../src/A2AServer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6HA,0CAuLC;AApTD,6CAAyC;AACzC,gDAAkC;AAClC,yCAA8D;AAG9D,mDAA8D;AAiF9D,uFAAuF;AACvF,2FAA2F;AAC3F,MAAM,cAAc,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AACxC,uFAAuF;AACvF,mFAAmF;AACnF,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,SAAS,SAAS,CAAC,GAAyB;IAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAiB,EAAE,EAAE;YACnC,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC;YACrB,IAAI,IAAI,GAAG,cAAc,EAAE,CAAC;gBAC1B,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,sBAAe,CAAC,wBAAwB,cAAc,QAAQ,CAAC,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAClC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAY,CAAC,CAAC;YACxD,CAAC;YAAC,MAAM,CAAC;gBACP,uEAAuE;gBACvE,MAAM,CAAC,IAAI,sBAAe,CAAC,gCAAgC,CAAC,CAAC,CAAC;YAChE,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,GAAwB,EAAE,MAAc,EAAE,IAAa;IACnE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;IAC9D,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACnB,CAAC;AAED,SAAgB,eAAe,CAC7B,eAAgC,EAChC,IAAoB,EACpB,SAAiC,EACjC,UAA4B,EAAE;IAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,WAAW,CAAC;IACzC,MAAM,aAAa,GAAG,OAAO,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;IAC5F,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,eAAe,CAAC,iBAAiB,KAAK,IAAI,CAAC;IAC5E,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,4FAA4F,CAC7F,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,IAAI,OAAO,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACjF,MAAM,IAAI,KAAK,CACb,kIAAkI,CACnI,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,aAAa,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,oKAAoK,CACrK,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAChD,4GAA4G,IAAI,IAAI,IAAI,6BAA6B,CACtJ,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GACX,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9F,IAAI,WAAW,GAAG,UAAU,OAAO,IAAI,IAAI,EAAE,CAAC;IAC9C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9D,IAAI,aAAa,GAAG,KAAK,CAAC;IAE1B,2FAA2F;IAC3F,2FAA2F;IAC3F,MAAM,QAAQ,GAAG,IAAI,iCAAiB,CAAC,eAAe,EAAE,IAAI,EAAE,EAAE,oBAAoB,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9F,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAyB,EAAE,GAAwB,EAAQ,EAAE;QAC7F,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;YAC3B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;YAEnC,IAAI,CAAC;gBACH,IAAI,MAAM,KAAK,KAAK,IAAI,GAAG,KAAK,yBAAyB,EAAE,CAAC;oBAC1D,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,SAAS,EAAE,GAAG,EAAE,OAAO,CAAC,SAAS,IAAI,WAAW,EAAE,CAAC,CAAC;oBACxE,OAAO;gBACT,CAAC;gBAED,+EAA+E;gBAC/E,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,GAAG,KAAK,yBAAyB,EAAE,CAAC;oBACzE,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;wBACpC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;wBAC1C,OAAO;oBACT,CAAC;gBACH,CAAC;gBAED,2CAA2C;gBAC3C,IAAI,SAAqC,CAAC;gBAC1C,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;oBACvC,SAAS,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;oBAC5C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;wBAC5B,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;wBAC1C,OAAO;oBACT,CAAC;gBACH,CAAC;gBACD,qEAAqE;gBACrE,SAAS,KAAK,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAC;gBACpD,wFAAwF;gBACxF,yFAAyF;gBACzF,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,IAAA,wBAAQ,EAAC,SAAS,CAAC,EAAE,CAAC;oBACpD,IAAI,CAAC,aAAa,EAAE,CAAC;wBACnB,aAAa,GAAG,IAAI,CAAC;wBACrB,IAAI,CACF,kLAAkL,CACnL,CAAC;oBACJ,CAAC;oBACD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;oBAC1C,OAAO;gBACT,CAAC;gBACD,IAAI,SAAS,KAAK,SAAS,IAAI,MAAM,EAAE,CAAC;oBACtC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;oBAC1C,OAAO;gBACT,CAAC;gBACD,wFAAwF;gBACxF,+DAA+D;gBAC/D,MAAM,IAAI,GAAG;oBACX,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,CAAC;oBAC7C,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI,aAAa,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;iBAC7E,CAAC;gBAEF,IAAI,MAAM,KAAK,MAAM,IAAI,GAAG,KAAK,aAAa,EAAE,CAAC;oBAC/C,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,CAAC,GAAG,CAAC,CAAY,CAAC;oBAC/C,iFAAiF;oBACjF,iFAAiF;oBACjF,yCAAyC;oBACzC,IAAI,OAAO,IAAI,EAAE,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACzD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC,CAAC;wBAChE,OAAO;oBACT,CAAC;oBACD,mFAAmF;oBACnF,kFAAkF;oBAClF,iEAAiE;oBACjE,IAAI,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;wBAClC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,oDAAoD,EAAE,CAAC,CAAC;wBAChF,OAAO;oBACT,CAAC;oBACD,6EAA6E;oBAC7E,8EAA8E;oBAC9E,0CAA0C;oBAC1C,IAAI,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;wBACxC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,kBAAkB,aAAa,EAAE,CAAC,CAAC;wBAC9E,OAAO;oBACT,CAAC;oBACD,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBACjD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;oBACvB,OAAO;gBACT,CAAC;gBAED,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACjD,IAAI,MAAM,KAAK,KAAK,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBAC3C,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;oBAC/B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;wBAC5B,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;wBAC7C,OAAO;oBACT,CAAC;oBACD,8EAA8E;oBAC9E,qFAAqF;oBACrF,uFAAuF;oBACvF,mFAAmF;oBACnF,+CAA+C;oBAC/C,IAAI,MAAc,CAAC;oBACnB,IAAI,CAAC;wBACH,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;oBACzC,CAAC;oBAAC,MAAM,CAAC;wBACP,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC,CAAC;wBACpE,OAAO;oBACT,CAAC;oBACD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;oBAChD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;wBACpB,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,MAAM,YAAY,EAAE,CAAC,CAAC;wBACtD,OAAO;oBACT,CAAC;oBACD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;oBACvB,OAAO;gBACT,CAAC;gBAED,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;YACzC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,IAAA,kBAAW,EAAC,GAAG,EAAE,wBAAU,CAAC,CAAC;gBAC7C,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;oBACnC,OAAO,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAiB,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACtE,CAAC;gBACD,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC,CAAC,EAAE,CAAC;IACP,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,IAAI,GAAG;YACL,OAAO,WAAW,CAAC;QACrB,CAAC;QACD,KAAK;YACH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE;oBAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC9B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;wBAC9C,WAAW,GAAG,UAAU,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBACjD,CAAC;oBACD,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC;QACD,IAAI;YACF,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACrC,MAAM,CAAC,KAAK,CAAC,CAAC,GAAW,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC/E,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { ExecutionState } from "@loomwork/core";
2
+ import type { PrincipalChain } from "@loomwork/effect-runtime";
3
+ import type { RegisteredFlow, ResumeEvent, WorkflowRuntime } from "@loomwork/workflow-runtime";
4
+ import type { A2ATask, A2ATaskResult, A2ATaskStatus } from "./types";
5
+ /**
6
+ * The A2A task lifecycle, expressed as a small executor interface. Its states map
7
+ * one-to-one onto Loom's execution states, so the entire A2A layer reduces to a
8
+ * single adapter over the runtime:
9
+ *
10
+ * submit / working → runtime.start → running
11
+ * input-required → SuspendError / status "suspended" (approval / agent-surface form)
12
+ * provideInput → runtime.resume(executionId, resumeEvent)
13
+ * completed / failed → terminal ExecutionState
14
+ *
15
+ * A transport (the in-house HTTP server below, or an external @a2a-js/sdk server)
16
+ * only has to move bytes to/from this interface.
17
+ */
18
+ export interface AgentExecutor {
19
+ submit(task: A2ATask, call?: ExecutorCall): Promise<{
20
+ id: string;
21
+ }>;
22
+ /** Poll a task's status/result. `null` ⇒ unknown task (→ 404). */
23
+ get(taskId: string, call?: ExecutorCall): Promise<A2ATaskResult | null>;
24
+ /** Deliver the input an `input-required` (suspended) task is waiting on. */
25
+ provideInput(taskId: string, event: ResumeEvent, call?: ExecutorCall): Promise<{
26
+ id: string;
27
+ }>;
28
+ }
29
+ /**
30
+ * R3 — per-call context from the transport. `principal` is the AUTHENTICATED identity of whoever
31
+ * made the request (resolved by the transport from its connection, never from the task body);
32
+ * the executor applies it to the runtime call so a runtime that fails closed (`security.strict`)
33
+ * authorizes the task as that caller. Absent ⇒ anonymous, which such a runtime refuses.
34
+ */
35
+ export interface ExecutorCall {
36
+ principal?: PrincipalChain;
37
+ /**
38
+ * R12 — the task namespace when no principal identifies the caller (e.g. a boolean `authorize`
39
+ * gate). Ignored when `principal` is present (the principal's identity wins). Only meaningful
40
+ * on an executor created with `namespaceByPrincipal`.
41
+ */
42
+ namespace?: string;
43
+ }
44
+ export interface LoomAgentExecutorOptions {
45
+ /**
46
+ * R12 — scope task ids to the caller. The execution id the runtime sees is
47
+ * `a2a.<namespace of caller>.<client task id>`: a caller can only ever name executions in its
48
+ * OWN namespace, so it cannot read, resume or collide with another caller's task by guessing
49
+ * or choosing an id (the lookup simply lands on a different, nonexistent execution). The
50
+ * client-visible id stays the one it chose. Default `false` for direct (in-process) use of the
51
+ * executor, where the id is the embedder's own; the HTTP server turns it on.
52
+ */
53
+ namespaceByPrincipal?: boolean;
54
+ }
55
+ /** R12 — does this principal carry the minimum identity a per-caller namespace needs? */
56
+ export declare function hasActor(principal: PrincipalChain): boolean;
57
+ /**
58
+ * R12 — a stable, opaque key for a principal; the namespace its tasks live in. Two callers with
59
+ * different identity can never address each other's execution ids, because the id the runtime
60
+ * sees embeds this key. Tenant + actor + onBehalfOf are the identity; a delegation token is not
61
+ * (it is a credential, and rotating it must not orphan the caller's tasks).
62
+ */
63
+ export declare function principalNamespace(principal: PrincipalChain | undefined): string;
64
+ /** Map a Loom execution status onto an A2A task status. */
65
+ export declare function executionStatusToA2A(status: string): A2ATaskStatus;
66
+ /** Fold a terminal/interim ExecutionState into the A2A task result shape. */
67
+ export declare function toTaskResult(taskId: string, state: ExecutionState): A2ATaskResult;
68
+ /**
69
+ * The single adapter: drives one Loom flow as an A2A agent. It is STATELESS —
70
+ * `get` rebuilds status from the durable log via `runtime.loadState` rather than
71
+ * holding a live handle, so the server can be horizontally scaled or restarted
72
+ * without losing in-flight tasks (the same statelessness the remote-worker model
73
+ * relies on).
74
+ */
75
+ export declare class LoomAgentExecutor implements AgentExecutor {
76
+ private readonly runtime;
77
+ private readonly flow;
78
+ private readonly options;
79
+ constructor(runtime: WorkflowRuntime, flow: RegisteredFlow, options?: LoomAgentExecutorOptions);
80
+ private idFor;
81
+ submit(task: A2ATask, call?: ExecutorCall): Promise<{
82
+ id: string;
83
+ }>;
84
+ get(taskId: string, call?: ExecutorCall): Promise<A2ATaskResult | null>;
85
+ provideInput(taskId: string, event: ResumeEvent, call?: ExecutorCall): Promise<{
86
+ id: string;
87
+ }>;
88
+ }
89
+ //# sourceMappingURL=AgentExecutor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentExecutor.d.ts","sourceRoot":"","sources":["../src/AgentExecutor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAErE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,kEAAkE;IAClE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACxE,4EAA4E;IAC5E,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChG;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC;;;;;;;OAOG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,yFAAyF;AACzF,wBAAgB,QAAQ,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,cAAc,GAAG,SAAS,GAAG,MAAM,CAgBhF;AAkBD,2DAA2D;AAC3D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAYlE;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,cAAc,GAAG,aAAa,CAgBjF;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,aAAa;IAEnD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAFP,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,cAAc,EACpB,OAAO,GAAE,wBAA6B;IAGzD,OAAO,CAAC,KAAK;IAMP,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IAsBvE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAM3E,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,WAAW,EAClB,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;CAK3B"}