@hivemagnus/ardent 0.0.0-stage → 1.0.1
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/LICENSE +202 -0
- package/README.md +274 -2
- package/dist/a2a-client.d.ts +35 -0
- package/dist/a2a-client.js +172 -0
- package/dist/a2a-hermes-alias.d.ts +3 -0
- package/dist/a2a-hermes-alias.js +57 -0
- package/dist/a2a-idempotency.d.ts +13 -0
- package/dist/a2a-idempotency.js +86 -0
- package/dist/a2a-peers.d.ts +28 -0
- package/dist/a2a-peers.js +108 -0
- package/dist/a2a-push-inbox.d.ts +22 -0
- package/dist/a2a-push-inbox.js +84 -0
- package/dist/a2a-push.d.ts +27 -0
- package/dist/a2a-push.js +188 -0
- package/dist/a2a-server.d.ts +37 -0
- package/dist/a2a-server.js +186 -0
- package/dist/a2a-store.d.ts +10 -0
- package/dist/a2a-store.js +122 -0
- package/dist/a2a.d.ts +19 -0
- package/dist/a2a.js +466 -0
- package/dist/api.d.ts +6 -0
- package/dist/api.js +381 -0
- package/dist/artifacts.d.ts +15 -0
- package/dist/artifacts.js +41 -0
- package/dist/backend-events.d.ts +49 -0
- package/dist/backend-events.js +8 -0
- package/dist/channels.d.ts +52 -0
- package/dist/channels.js +184 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +845 -0
- package/dist/codex-output-budget.d.ts +17 -0
- package/dist/codex-output-budget.js +31 -0
- package/dist/codex-output-budget.test.d.ts +1 -0
- package/dist/codex-output-budget.test.js +29 -0
- package/dist/codex.d.ts +82 -0
- package/dist/codex.js +431 -0
- package/dist/config.d.ts +171 -0
- package/dist/config.js +244 -0
- package/dist/context-checkpoints.d.ts +49 -0
- package/dist/context-checkpoints.js +248 -0
- package/dist/context.d.ts +16 -0
- package/dist/context.js +101 -0
- package/dist/gateway-lock.d.ts +18 -0
- package/dist/gateway-lock.js +104 -0
- package/dist/hermes-compat.d.ts +15 -0
- package/dist/hermes-compat.js +62 -0
- package/dist/identity.d.ts +15 -0
- package/dist/identity.js +52 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +8 -0
- package/dist/job-engine.d.ts +222 -0
- package/dist/job-engine.js +497 -0
- package/dist/job-news-handlers.d.ts +54 -0
- package/dist/job-news-handlers.js +153 -0
- package/dist/job-ownership.d.ts +9 -0
- package/dist/job-ownership.js +17 -0
- package/dist/json-schema.d.ts +9 -0
- package/dist/json-schema.js +20 -0
- package/dist/llamacpp.d.ts +49 -0
- package/dist/llamacpp.js +216 -0
- package/dist/magnus-control.d.ts +3 -0
- package/dist/magnus-control.js +99 -0
- package/dist/magnus-node-dependency-impact.d.ts +1 -0
- package/dist/magnus-node-dependency-impact.js +126 -0
- package/dist/magnus-owner-reads.d.ts +4 -0
- package/dist/magnus-owner-reads.js +99 -0
- package/dist/magnus-readiness.d.ts +1 -0
- package/dist/magnus-readiness.js +108 -0
- package/dist/mattermost.d.ts +18 -0
- package/dist/mattermost.js +303 -0
- package/dist/memory-tools.d.ts +5 -0
- package/dist/memory-tools.js +49 -0
- package/dist/memory.d.ts +40 -0
- package/dist/memory.js +128 -0
- package/dist/model-policy.d.ts +64 -0
- package/dist/model-policy.js +56 -0
- package/dist/news.d.ts +10 -0
- package/dist/news.js +291 -0
- package/dist/owner-read.d.ts +32 -0
- package/dist/owner-read.js +130 -0
- package/dist/plugins.d.ts +74 -0
- package/dist/plugins.js +115 -0
- package/dist/research.d.ts +110 -0
- package/dist/research.js +164 -0
- package/dist/runtime.d.ts +33 -0
- package/dist/runtime.js +665 -0
- package/dist/skills.d.ts +13 -0
- package/dist/skills.js +87 -0
- package/dist/store.d.ts +142 -0
- package/dist/store.js +322 -0
- package/dist/team-artifacts.d.ts +138 -0
- package/dist/team-artifacts.js +241 -0
- package/dist/team-child.d.ts +21 -0
- package/dist/team-child.js +130 -0
- package/dist/team-contract.d.ts +116 -0
- package/dist/team-contract.js +84 -0
- package/dist/team-control.d.ts +8 -0
- package/dist/team-control.js +152 -0
- package/dist/telegram.d.ts +35 -0
- package/dist/telegram.js +284 -0
- package/dist/tools.d.ts +48 -0
- package/dist/tools.js +58 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/model-policy.default.json +20 -0
- package/package.json +84 -4
- package/profiles/assistant/IDENTITY.md +9 -0
- package/profiles/assistant/SOUL.md +8 -0
- package/profiles/assistant/config.yaml +88 -0
- package/profiles/assistant/peers.yaml +12 -0
- package/profiles/assistant/skills/grounded-answers/SKILL.md +18 -0
- package/protocol/LICENSE-A2A.txt +202 -0
- package/protocol/README.md +12 -0
- package/protocol/a2a.proto +811 -0
- package/protocol/lock.json +9 -0
package/dist/a2a-push.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import { StreamResponse as StreamCodec, TaskPushNotificationConfig as PushCodec } from '@a2a-js/sdk';
|
|
4
|
+
import { RequestMalformedError } from '@a2a-js/sdk/errors';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const Row = z.object({ config_json: z.string(), wire_version: z.string() });
|
|
7
|
+
const Delivery = z.object({ state: z.enum(['queued', 'sent', 'failed', 'unknown']) });
|
|
8
|
+
function scope(context) {
|
|
9
|
+
const user = context.user;
|
|
10
|
+
if (!user?.isAuthenticated || !user.userName)
|
|
11
|
+
throw new Error('Authenticated A2A caller required');
|
|
12
|
+
return { tenant: context.tenant ?? '', caller: user.userName };
|
|
13
|
+
}
|
|
14
|
+
function validatedUrl(raw, peer) {
|
|
15
|
+
if (raw.length > 2048)
|
|
16
|
+
throw new RequestMalformedError('A2A callback URL is too long');
|
|
17
|
+
let callback;
|
|
18
|
+
try {
|
|
19
|
+
callback = new URL(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new RequestMalformedError('A2A callback URL is invalid');
|
|
23
|
+
}
|
|
24
|
+
const home = new URL(peer.url);
|
|
25
|
+
const port = Number(callback.port || (callback.protocol === 'https:' ? 443 : 80));
|
|
26
|
+
const homePort = Number(home.port || (home.protocol === 'https:' ? 443 : 80));
|
|
27
|
+
if (!['http:', 'https:'].includes(callback.protocol) || callback.username || callback.password || callback.hash || callback.search ||
|
|
28
|
+
callback.hostname !== home.hostname || !new Set([homePort, ...peer.callback_ports]).has(port))
|
|
29
|
+
throw new RequestMalformedError('A2A callback URL is outside the configured peer destination');
|
|
30
|
+
if (callback.protocol === 'http:' && (!peer.allow_private_callbacks || !isIP(callback.hostname)))
|
|
31
|
+
throw new RequestMalformedError('HTTP callback requires an explicit trusted IP peer');
|
|
32
|
+
if (home.protocol === 'https:' && callback.protocol !== 'https:')
|
|
33
|
+
throw new RequestMalformedError('A2A callback cannot downgrade a secure peer');
|
|
34
|
+
return callback.href;
|
|
35
|
+
}
|
|
36
|
+
export class SqliteA2APushStore {
|
|
37
|
+
db;
|
|
38
|
+
peers;
|
|
39
|
+
constructor(db, peers) {
|
|
40
|
+
this.db = db;
|
|
41
|
+
this.peers = peers;
|
|
42
|
+
db.exec(`CREATE TABLE IF NOT EXISTS a2a_push_configs (
|
|
43
|
+
task_id TEXT NOT NULL, tenant TEXT NOT NULL, caller TEXT NOT NULL, id TEXT NOT NULL,
|
|
44
|
+
config_json TEXT NOT NULL, wire_version TEXT NOT NULL, created_ms INTEGER NOT NULL,
|
|
45
|
+
PRIMARY KEY(task_id,tenant,caller,id));
|
|
46
|
+
CREATE TABLE IF NOT EXISTS a2a_push_deliveries (
|
|
47
|
+
id TEXT PRIMARY KEY, task_id TEXT NOT NULL, tenant TEXT NOT NULL, caller TEXT NOT NULL,
|
|
48
|
+
config_id TEXT NOT NULL, state TEXT NOT NULL, http_status INTEGER, error TEXT,
|
|
49
|
+
created_ms INTEGER NOT NULL, updated_ms INTEGER NOT NULL);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS a2a_push_delivery_task ON a2a_push_deliveries(task_id,tenant,caller);`);
|
|
51
|
+
const columns = z.array(z.object({ name: z.string() })).parse(db.pragma('table_info(a2a_push_deliveries)'));
|
|
52
|
+
if (!columns.some(column => column.name === 'error'))
|
|
53
|
+
db.exec('ALTER TABLE a2a_push_deliveries ADD COLUMN error TEXT');
|
|
54
|
+
db.prepare("UPDATE a2a_push_deliveries SET state='unknown',updated_ms=? WHERE state='queued'").run(Date.now());
|
|
55
|
+
}
|
|
56
|
+
async save(taskId, context, config) {
|
|
57
|
+
const owner = scope(context);
|
|
58
|
+
const peer = this.peers.find(item => item.id === owner.caller && item.enabled);
|
|
59
|
+
if (!peer)
|
|
60
|
+
throw new RequestMalformedError('A2A callback caller is not a configured peer');
|
|
61
|
+
if (config.taskId !== taskId || (config.tenant && config.tenant !== owner.tenant))
|
|
62
|
+
throw new RequestMalformedError('A2A callback task or tenant mismatch');
|
|
63
|
+
const url = validatedUrl(config.url, peer);
|
|
64
|
+
if (config.token.length > 512 || (config.authentication && (config.authentication.scheme.toLowerCase() !== 'bearer' || config.authentication.credentials.length > 512)))
|
|
65
|
+
throw new RequestMalformedError('A2A callback authentication is unsupported');
|
|
66
|
+
if (!config.id)
|
|
67
|
+
config.id = randomUUID();
|
|
68
|
+
if (!/^[A-Za-z0-9._-]{1,128}$/.test(config.id))
|
|
69
|
+
throw new RequestMalformedError('A2A callback ID is invalid');
|
|
70
|
+
const count = z.object({ count: z.number() }).parse(this.db.prepare('SELECT COUNT(*) AS count FROM a2a_push_configs WHERE task_id=? AND tenant=? AND caller=?').get(taskId, owner.tenant, owner.caller)).count;
|
|
71
|
+
const exists = this.db.prepare('SELECT 1 FROM a2a_push_configs WHERE task_id=? AND tenant=? AND caller=? AND id=?').get(taskId, owner.tenant, owner.caller, config.id);
|
|
72
|
+
if (!exists && count >= 5)
|
|
73
|
+
throw new RequestMalformedError('A2A callback limit reached');
|
|
74
|
+
const stored = PushCodec.toJSON({ ...config, url, tenant: owner.tenant });
|
|
75
|
+
this.db.prepare(`INSERT INTO a2a_push_configs(task_id,tenant,caller,id,config_json,wire_version,created_ms) VALUES (?,?,?,?,?,?,?)
|
|
76
|
+
ON CONFLICT(task_id,tenant,caller,id) DO UPDATE SET config_json=excluded.config_json,wire_version=excluded.wire_version`)
|
|
77
|
+
.run(taskId, owner.tenant, owner.caller, config.id, JSON.stringify(stored), context.requestedVersion || '1.0', Date.now());
|
|
78
|
+
}
|
|
79
|
+
async load(taskId, context) {
|
|
80
|
+
return (await this.loadWithMetadata(taskId, context)).map(item => item.config);
|
|
81
|
+
}
|
|
82
|
+
async loadWithMetadata(taskId, context) {
|
|
83
|
+
const owner = scope(context);
|
|
84
|
+
return this.db.prepare('SELECT config_json,wire_version FROM a2a_push_configs WHERE task_id=? AND tenant=? AND caller=? ORDER BY created_ms,id')
|
|
85
|
+
.all(taskId, owner.tenant, owner.caller).map(row => {
|
|
86
|
+
const parsed = Row.parse(row);
|
|
87
|
+
const value = JSON.parse(parsed.config_json);
|
|
88
|
+
return { config: PushCodec.fromJSON(value), wireVersion: parsed.wire_version };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async delete(taskId, context, configId) {
|
|
92
|
+
const owner = scope(context);
|
|
93
|
+
if (configId)
|
|
94
|
+
this.db.prepare('DELETE FROM a2a_push_configs WHERE task_id=? AND tenant=? AND caller=? AND id=?').run(taskId, owner.tenant, owner.caller, configId);
|
|
95
|
+
else
|
|
96
|
+
this.db.prepare('DELETE FROM a2a_push_configs WHERE task_id=? AND tenant=? AND caller=?').run(taskId, owner.tenant, owner.caller);
|
|
97
|
+
}
|
|
98
|
+
claimDelivery(id, taskId, context, configId) {
|
|
99
|
+
const owner = scope(context);
|
|
100
|
+
const result = this.db.prepare("INSERT OR IGNORE INTO a2a_push_deliveries(id,task_id,tenant,caller,config_id,state,http_status,error,created_ms,updated_ms) VALUES (?,?,?,?,?,'queued',NULL,NULL,?,?)")
|
|
101
|
+
.run(id, taskId, owner.tenant, owner.caller, configId, Date.now(), Date.now());
|
|
102
|
+
return result.changes === 1;
|
|
103
|
+
}
|
|
104
|
+
deliveryState(id) {
|
|
105
|
+
const row = this.db.prepare('SELECT state FROM a2a_push_deliveries WHERE id=?').get(id);
|
|
106
|
+
return row ? Delivery.parse(row).state : undefined;
|
|
107
|
+
}
|
|
108
|
+
setDelivery(id, state, httpStatus, error) {
|
|
109
|
+
this.db.prepare('UPDATE a2a_push_deliveries SET state=?,http_status=?,error=?,updated_ms=? WHERE id=?').run(state, httpStatus ?? null, error?.slice(0, 300) ?? null, Date.now(), id);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Thrown by a `beforeDelivery` hook to refuse a push deterministically (recorded as failed, not unknown). */
|
|
113
|
+
export class DeliveryVetoError extends Error {
|
|
114
|
+
}
|
|
115
|
+
export class SafeA2APushSender {
|
|
116
|
+
store;
|
|
117
|
+
beforeDelivery;
|
|
118
|
+
#chains = new Map();
|
|
119
|
+
constructor(store, beforeDelivery) {
|
|
120
|
+
this.store = store;
|
|
121
|
+
this.beforeDelivery = beforeDelivery;
|
|
122
|
+
}
|
|
123
|
+
async drain() {
|
|
124
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
125
|
+
await Promise.allSettled([...this.#chains.values()]);
|
|
126
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
127
|
+
if (!this.#chains.size)
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throw new Error('A2A push delivery remains unsettled');
|
|
131
|
+
}
|
|
132
|
+
async send(event, context, _task) {
|
|
133
|
+
const payload = event.payload;
|
|
134
|
+
if (!payload)
|
|
135
|
+
throw new Error('A2A push event is missing payload');
|
|
136
|
+
const taskId = payload.$case === 'task' ? payload.value.id : payload.value.taskId;
|
|
137
|
+
if (!taskId)
|
|
138
|
+
return;
|
|
139
|
+
const prior = this.#chains.get(taskId) ?? Promise.resolve();
|
|
140
|
+
const next = prior.catch(() => { }).then(async () => {
|
|
141
|
+
const body = JSON.stringify(StreamCodec.toJSON(event));
|
|
142
|
+
if (Buffer.byteLength(body) > 2 * 1024 * 1024)
|
|
143
|
+
return;
|
|
144
|
+
const owner = scope(context);
|
|
145
|
+
const configs = await this.store.loadWithMetadata(taskId, context);
|
|
146
|
+
for (const { config, wireVersion } of configs) {
|
|
147
|
+
if (wireVersion !== '1.0')
|
|
148
|
+
continue;
|
|
149
|
+
const id = createHash('sha256').update(JSON.stringify([owner.tenant, owner.caller, taskId, config.id, body])).digest('hex');
|
|
150
|
+
if (!this.store.claimDelivery(id, taskId, context, config.id))
|
|
151
|
+
continue;
|
|
152
|
+
const headers = { 'Content-Type': 'application/a2a+json', 'A2A-Version': '1.0' };
|
|
153
|
+
if (config.authentication)
|
|
154
|
+
headers['Authorization'] = `${config.authentication.scheme} ${config.authentication.credentials}`;
|
|
155
|
+
else if (config.token)
|
|
156
|
+
headers['X-A2A-Notification-Token'] = config.token;
|
|
157
|
+
try {
|
|
158
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
159
|
+
this.beforeDelivery?.(taskId);
|
|
160
|
+
const response = await fetch(config.url, { method: 'POST', headers, body, redirect: 'error', signal: AbortSignal.timeout(5000) });
|
|
161
|
+
if (response.ok) {
|
|
162
|
+
this.store.setDelivery(id, 'sent', response.status);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (response.status === 429 && attempt < 2) {
|
|
166
|
+
const seconds = Number(response.headers.get('retry-after') ?? '1');
|
|
167
|
+
await new Promise(resolve => setTimeout(resolve, Number.isFinite(seconds) ? Math.min(5, Math.max(1, seconds)) * 1000 : 1000));
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
this.store.setDelivery(id, response.status >= 500 ? 'unknown' : 'failed', response.status);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
this.store.setDelivery(id, error instanceof DeliveryVetoError ? 'failed' : 'unknown', undefined, error instanceof Error ? error.message : 'Callback outcome unknown');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
this.#chains.set(taskId, next);
|
|
180
|
+
try {
|
|
181
|
+
await next;
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
if (this.#chains.get(taskId) === next)
|
|
185
|
+
this.#chains.delete(taskId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Express } from 'express';
|
|
2
|
+
import { Server as GrpcServer } from '@grpc/grpc-js';
|
|
3
|
+
import { AgentCard } from '@a2a-js/sdk';
|
|
4
|
+
import type { ArdentConfig } from './config.js';
|
|
5
|
+
import { SafeA2APushSender } from './a2a-push.js';
|
|
6
|
+
import { A2APushInbox } from './a2a-push-inbox.js';
|
|
7
|
+
import type { ArdentRuntime } from './runtime.js';
|
|
8
|
+
import { hermesTeamCard } from './hermes-compat.js';
|
|
9
|
+
export type A2ASurface = {
|
|
10
|
+
app: Express;
|
|
11
|
+
grpcServer: GrpcServer;
|
|
12
|
+
card: AgentCard;
|
|
13
|
+
grpcAddress: string;
|
|
14
|
+
grpcListenPort: number;
|
|
15
|
+
pushSender: SafeA2APushSender;
|
|
16
|
+
pushInbox: A2APushInbox;
|
|
17
|
+
};
|
|
18
|
+
/** Refresh only discovery freshness; the reviewed runtime identity and digests stay pinned to this process. */
|
|
19
|
+
export declare function freshHermesTeamCard(source: Awaited<ReturnType<typeof hermesTeamCard>>, now?: number): {
|
|
20
|
+
metadata: {
|
|
21
|
+
hermesTeam: {
|
|
22
|
+
discovery: {
|
|
23
|
+
observedAt: number;
|
|
24
|
+
expiresAt: number;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
description: string;
|
|
29
|
+
skills: Array<{
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
tags: string[];
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
export declare function createA2ASurface(runtime: ArdentRuntime, config: ArdentConfig, env?: NodeJS.ProcessEnv): Promise<A2ASurface>;
|
|
37
|
+
export declare function startA2AGrpc(surface: A2ASurface, host?: string): Promise<void>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { Server as GrpcServer, ServerCredentials } from '@grpc/grpc-js';
|
|
4
|
+
import { A2A_PROTOCOL_VERSION, AGENT_CARD_PATH, AgentCard } from '@a2a-js/sdk';
|
|
5
|
+
import { jsonRpcHandler, restHandler } from '@a2a-js/sdk/server/express';
|
|
6
|
+
import { legacyAgentCardRouter } from '@a2a-js/sdk/compat/v0_3/server/express';
|
|
7
|
+
import { A2AService, grpcService } from '@a2a-js/sdk/server/grpc';
|
|
8
|
+
import { resolveReference } from './config.js';
|
|
9
|
+
import { loadPeers } from './a2a-peers.js';
|
|
10
|
+
import { ArdentA2AExecutor } from './a2a.js';
|
|
11
|
+
import { SqliteA2ATaskStore } from './a2a-store.js';
|
|
12
|
+
import { SafeA2APushSender, SqliteA2APushStore } from './a2a-push.js';
|
|
13
|
+
import { A2APushInbox } from './a2a-push-inbox.js';
|
|
14
|
+
import { hermesTeamCard } from './hermes-compat.js';
|
|
15
|
+
import { IdempotentA2AHandler } from './a2a-idempotency.js';
|
|
16
|
+
import { hermesAlias } from './a2a-hermes-alias.js';
|
|
17
|
+
import { ARDENT_VERSION } from './version.js';
|
|
18
|
+
import { TeamArtifacts } from './team-artifacts.js';
|
|
19
|
+
/** Refresh only discovery freshness; the reviewed runtime identity and digests stay pinned to this process. */
|
|
20
|
+
export function freshHermesTeamCard(source, now = Date.now()) {
|
|
21
|
+
const team = z.object({ discovery: z.object({ observedAt: z.number().int(), expiresAt: z.number().int() }).passthrough() })
|
|
22
|
+
.passthrough().parse(source.metadata.hermesTeam);
|
|
23
|
+
if (!Number.isSafeInteger(now) || now < 0)
|
|
24
|
+
throw new Error('Team-card clock is invalid');
|
|
25
|
+
const observedAt = Math.floor(now / 1000);
|
|
26
|
+
return { ...source, metadata: { hermesTeam: { ...team,
|
|
27
|
+
discovery: { ...team.discovery, observedAt, expiresAt: observedAt + 300 } } } };
|
|
28
|
+
}
|
|
29
|
+
export async function createA2ASurface(runtime, config, env = process.env) {
|
|
30
|
+
const publicAddress = resolveReference(config.a2a.public_url, env);
|
|
31
|
+
if (!publicAddress)
|
|
32
|
+
throw new Error('A2A public URL is unconfigured');
|
|
33
|
+
const publicUrl = new URL(publicAddress);
|
|
34
|
+
if (!['http:', 'https:'].includes(publicUrl.protocol) || publicUrl.username || publicUrl.password)
|
|
35
|
+
throw new Error('Invalid A2A public URL');
|
|
36
|
+
const { authenticate, peers } = await loadPeers(runtime.profileDir, config, env);
|
|
37
|
+
const teamCard = config.a2a.hermes_compat ? await hermesTeamCard(runtime, config) : undefined;
|
|
38
|
+
const grpcPort = Number(publicUrl.port || (publicUrl.protocol === 'https:' ? 443 : 80)) + 1;
|
|
39
|
+
if (grpcPort > 65535)
|
|
40
|
+
throw new Error('A2A gRPC port is unavailable');
|
|
41
|
+
const listenPort = config.a2a.listen_port ?? Number(publicUrl.port || (publicUrl.protocol === 'https:' ? 443 : 80));
|
|
42
|
+
if (listenPort >= 65535)
|
|
43
|
+
throw new Error('A2A local gRPC port is unavailable');
|
|
44
|
+
const grpcAddress = `${publicUrl.hostname}:${grpcPort}`;
|
|
45
|
+
const jsonRpcUrl = new URL('/a2a/jsonrpc', publicUrl).href;
|
|
46
|
+
const restUrl = new URL('/a2a/rest', publicUrl).href;
|
|
47
|
+
const interfaces = [];
|
|
48
|
+
for (const binding of config.a2a.bindings) {
|
|
49
|
+
if (binding === 'jsonrpc')
|
|
50
|
+
interfaces.push({ url: jsonRpcUrl, protocolBinding: 'JSONRPC', protocolVersion: A2A_PROTOCOL_VERSION, tenant: '' });
|
|
51
|
+
else if (binding === 'http-json')
|
|
52
|
+
interfaces.push({ url: restUrl, protocolBinding: 'HTTP+JSON', protocolVersion: A2A_PROTOCOL_VERSION, tenant: '' });
|
|
53
|
+
else
|
|
54
|
+
interfaces.push({ url: grpcAddress, protocolBinding: 'GRPC', protocolVersion: A2A_PROTOCOL_VERSION, tenant: '' });
|
|
55
|
+
}
|
|
56
|
+
if (config.a2a.bindings.includes('jsonrpc'))
|
|
57
|
+
interfaces.push({ url: jsonRpcUrl, protocolBinding: 'JSONRPC', protocolVersion: '0.3', tenant: '' });
|
|
58
|
+
const bearerRequirement = [{ schemes: { Bearer: { list: [] } } }];
|
|
59
|
+
const skill = config.a2a.skill ?? { id: 'general-assistance', name: 'General assistance',
|
|
60
|
+
description: 'Answer bounded tasks with the tools this profile enables', tags: ['assistant'], example: 'Summarize the latest update on this topic' };
|
|
61
|
+
const card = {
|
|
62
|
+
name: config.name,
|
|
63
|
+
description: teamCard?.description ?? skill.description,
|
|
64
|
+
supportedInterfaces: interfaces,
|
|
65
|
+
provider: { organization: 'Magnus', url: 'https://github.com/ChefTaco/magnus' },
|
|
66
|
+
version: ARDENT_VERSION,
|
|
67
|
+
capabilities: { streaming: true, pushNotifications: true, extendedAgentCard: true, extensions: [] },
|
|
68
|
+
securitySchemes: { Bearer: { scheme: { $case: 'httpAuthSecurityScheme', value: { description: 'Peer bearer token', scheme: 'bearer', bearerFormat: '' } } } },
|
|
69
|
+
securityRequirements: bearerRequirement,
|
|
70
|
+
defaultInputModes: ['text/plain', 'text/markdown', 'application/json'],
|
|
71
|
+
defaultOutputModes: ['text/plain', 'application/json'],
|
|
72
|
+
skills: [{ id: skill.id, name: skill.name, description: skill.description, tags: [...skill.tags], examples: skill.example ? [skill.example] : [], inputModes: ['text/plain'], outputModes: ['text/plain', 'application/json'], securityRequirements: bearerRequirement }],
|
|
73
|
+
documentationUrl: '', signatures: []
|
|
74
|
+
};
|
|
75
|
+
const pushStore = new SqliteA2APushStore(runtime.store.db, peers);
|
|
76
|
+
const pushSender = new SafeA2APushSender(pushStore);
|
|
77
|
+
const pushInbox = new A2APushInbox(runtime.store.db, peers, publicAddress);
|
|
78
|
+
const artifactTransfers = new TeamArtifacts(runtime, config, env);
|
|
79
|
+
const taskStore = new SqliteA2ATaskStore(runtime.store.db);
|
|
80
|
+
const handler = new IdempotentA2AHandler(runtime.store.db, taskStore, card, taskStore, new ArdentA2AExecutor(runtime, env), undefined, pushStore, pushSender, async (context) => {
|
|
81
|
+
const peerId = context.user?.userName;
|
|
82
|
+
if (!context.user?.isAuthenticated || !peerId || !peers.some(peer => peer.id === peerId && peer.enabled))
|
|
83
|
+
throw new Error('Authenticated configured peer required');
|
|
84
|
+
return {
|
|
85
|
+
...card,
|
|
86
|
+
description: `${card.description}. Authenticated peer ${peerId} may submit bounded ${config.profile} tasks.`,
|
|
87
|
+
skills: [...card.skills, { id: `${config.profile}-team-result`, name: 'Validated team result', description: 'Return a mission-bound specialist result for an authenticated team envelope', tags: ['team', config.profile], examples: ['Return a scoped result with evidence'], inputModes: ['text/plain', 'application/json'], outputModes: ['application/json'], securityRequirements: bearerRequirement }]
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
const app = express();
|
|
91
|
+
app.get('/team/artifacts/:id', async (req, res) => {
|
|
92
|
+
const peer = authenticate(req.header('Authorization'));
|
|
93
|
+
if (!peer) {
|
|
94
|
+
res.status(401).json({ error: 'Authenticated A2A peer required' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await artifactTransfers.serve(peer, req.params.id ?? '', String(req.query.context ?? ''));
|
|
99
|
+
if (result.status !== 200 || !result.bytes || !result.descriptor) {
|
|
100
|
+
res.status(result.status).json({ error: result.status === 410 ? 'Artifact export revoked or expired' : 'Artifact unavailable' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const descriptor = result.descriptor;
|
|
104
|
+
res.status(200).set({ 'Content-Type': descriptor.mime, 'Content-Length': String(result.bytes.byteLength),
|
|
105
|
+
'X-Artifact-Name': encodeURIComponent(descriptor.name), 'X-Hermes-Task-Id': descriptor.producer_task_id,
|
|
106
|
+
'X-Hermes-Artifact-Manifest': Buffer.from(JSON.stringify(descriptor.artifact_manifest)).toString('base64url'),
|
|
107
|
+
'X-Content-Type-Options': 'nosniff', 'Cache-Control': 'private, no-store' }).end(Buffer.from(result.bytes));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
res.status(503).json({ error: 'Artifact export state is unavailable' });
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
app.post('/a2a/push/:peerId', express.json({ limit: '2mb', type: ['application/json', 'application/a2a+json'] }), (req, res) => {
|
|
114
|
+
try {
|
|
115
|
+
const result = pushInbox.accept(req.params.peerId ?? '', req.header('X-A2A-Notification-Token'), req.body);
|
|
116
|
+
res.status(202).json({ accepted: true, duplicate: result.duplicate });
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : 'Invalid A2A push';
|
|
120
|
+
res.status(/unauthorized/i.test(message) ? 401 : /exceeds/i.test(message) ? 413 : 400).json({ error: message.slice(0, 200) });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Serve the explicit v0.3 compatibility card only for legacy-version
|
|
124
|
+
// discovery. The SDK's v1 Express card helper stringifies internal oneofs,
|
|
125
|
+
// so the canonical v1 card goes through the generated protobuf JSON codec.
|
|
126
|
+
app.get(`/${AGENT_CARD_PATH}`, (req, res, next) => {
|
|
127
|
+
if (req.header('A2A-Version')?.startsWith('0.3')) {
|
|
128
|
+
next();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const encoded = AgentCard.toJSON(card);
|
|
132
|
+
if (!encoded || typeof encoded !== 'object' || Array.isArray(encoded))
|
|
133
|
+
throw new Error('A2A Agent Card encoding failed');
|
|
134
|
+
res.json(encoded);
|
|
135
|
+
});
|
|
136
|
+
app.use(`/${AGENT_CARD_PATH}`, legacyAgentCardRouter({ agentCardProvider: handler }));
|
|
137
|
+
if (teamCard)
|
|
138
|
+
app.get('/.well-known/hermes-team-card.json', (req, res) => {
|
|
139
|
+
if (!authenticate(req.header('Authorization'))) {
|
|
140
|
+
res.status(401).json({ error: 'Authenticated configured peer required' });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const encoded = AgentCard.toJSON(card);
|
|
144
|
+
if (!encoded || typeof encoded !== 'object' || Array.isArray(encoded))
|
|
145
|
+
throw new Error('A2A Agent Card encoding failed');
|
|
146
|
+
res.set('Cache-Control', 'private, no-store').json({ ...encoded, ...freshHermesTeamCard(teamCard) });
|
|
147
|
+
});
|
|
148
|
+
const auth = (req, res, next) => {
|
|
149
|
+
if (!authenticate(req.header('Authorization'))) {
|
|
150
|
+
res.status(401).json({ error: 'Authenticated A2A peer required' });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
next();
|
|
154
|
+
};
|
|
155
|
+
const userBuilder = async (req) => {
|
|
156
|
+
const id = authenticate(req.header('Authorization'));
|
|
157
|
+
return { isAuthenticated: Boolean(id), userName: id ?? '' };
|
|
158
|
+
};
|
|
159
|
+
const aliases = config.a2a.hermes_compat ? hermesAlias : (_req, _res, next) => next();
|
|
160
|
+
if (config.a2a.bindings.includes('jsonrpc'))
|
|
161
|
+
app.use('/a2a/jsonrpc', auth, express.json({ limit: '1mb' }), aliases, jsonRpcHandler({ requestHandler: handler, userBuilder, legacyCompat: { enabled: true } }));
|
|
162
|
+
if (config.a2a.bindings.includes('http-json'))
|
|
163
|
+
app.use('/a2a/rest', auth, restHandler({ requestHandler: handler, userBuilder }));
|
|
164
|
+
// Hermes Agent's team router posts to the peer origin itself. Keep that
|
|
165
|
+
// optional compatibility route separate from the advertised v1 interface.
|
|
166
|
+
if (config.a2a.hermes_compat && config.a2a.bindings.includes('jsonrpc')) {
|
|
167
|
+
const rootCompat = express.Router();
|
|
168
|
+
rootCompat.use((req, _res, next) => { if (!req.header('A2A-Version'))
|
|
169
|
+
req.headers['a2a-version'] = '1.0'; next(); });
|
|
170
|
+
rootCompat.use(auth, express.json({ limit: '1mb' }), hermesAlias, jsonRpcHandler({ requestHandler: handler, userBuilder, legacyCompat: { enabled: true } }));
|
|
171
|
+
app.use('/', (req, res, next) => req.path === '/' ? rootCompat(req, res, next) : next());
|
|
172
|
+
}
|
|
173
|
+
const grpcServer = new GrpcServer();
|
|
174
|
+
if (config.a2a.bindings.includes('grpc'))
|
|
175
|
+
grpcServer.addService(A2AService, grpcService({ requestHandler: handler, userBuilder: async (call) => {
|
|
176
|
+
const raw = call.metadata.get('authorization')[0];
|
|
177
|
+
const id = authenticate(typeof raw === 'string' ? raw : undefined);
|
|
178
|
+
return { isAuthenticated: Boolean(id), userName: id ?? '' };
|
|
179
|
+
} }));
|
|
180
|
+
return { app, grpcServer, card, grpcAddress, grpcListenPort: listenPort + 1, pushSender, pushInbox };
|
|
181
|
+
}
|
|
182
|
+
export async function startA2AGrpc(surface, host = '127.0.0.1') {
|
|
183
|
+
await new Promise((resolve, reject) => {
|
|
184
|
+
surface.grpcServer.bindAsync(`${host}:${surface.grpcListenPort}`, ServerCredentials.createInsecure(), error => error ? reject(error) : resolve());
|
|
185
|
+
});
|
|
186
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ListTasksRequest, type ListTasksResponse, type Task } from '@a2a-js/sdk';
|
|
2
|
+
import type { ServerCallContext, TaskStore as A2ATaskStore } from '@a2a-js/sdk/server';
|
|
3
|
+
import type Database from 'better-sqlite3';
|
|
4
|
+
export declare class SqliteA2ATaskStore implements A2ATaskStore {
|
|
5
|
+
readonly db: Database.Database;
|
|
6
|
+
constructor(db: Database.Database);
|
|
7
|
+
save(task: Task, context: ServerCallContext): Promise<void>;
|
|
8
|
+
load(taskId: string, context: ServerCallContext): Promise<Task | undefined>;
|
|
9
|
+
list(params: ListTasksRequest, context: ServerCallContext): Promise<ListTasksResponse>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Task as TaskCodec, TaskState } from '@a2a-js/sdk';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const StoredTask = z.object({ id: z.string().min(1), contextId: z.string(), status: z.object({ state: z.string(), timestamp: z.string().optional() }).nullable().optional(), artifacts: z.array(z.unknown()).optional(), history: z.array(z.unknown()).optional() }).passthrough();
|
|
4
|
+
const Row = z.object({ task_json: z.string() });
|
|
5
|
+
const Cursor = z.object({ timestamp: z.string(), id: z.string() });
|
|
6
|
+
function owner(context) {
|
|
7
|
+
const user = context.user;
|
|
8
|
+
if (!user?.isAuthenticated || !user.userName)
|
|
9
|
+
throw new Error('Authenticated A2A caller required');
|
|
10
|
+
return { tenant: context.tenant ?? '', caller: user.userName };
|
|
11
|
+
}
|
|
12
|
+
function readTask(json) {
|
|
13
|
+
const raw = JSON.parse(json);
|
|
14
|
+
StoredTask.parse(raw);
|
|
15
|
+
return TaskCodec.fromJSON(raw);
|
|
16
|
+
}
|
|
17
|
+
export class SqliteA2ATaskStore {
|
|
18
|
+
db;
|
|
19
|
+
constructor(db) {
|
|
20
|
+
this.db = db;
|
|
21
|
+
db.exec(`CREATE TABLE IF NOT EXISTS a2a_tasks (
|
|
22
|
+
id TEXT PRIMARY KEY, tenant TEXT NOT NULL, caller TEXT NOT NULL,
|
|
23
|
+
context_id TEXT NOT NULL, status TEXT NOT NULL, status_timestamp TEXT NOT NULL,
|
|
24
|
+
task_json TEXT NOT NULL, updated_ms INTEGER NOT NULL);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS a2a_tasks_scope ON a2a_tasks(tenant, caller, status_timestamp DESC, id DESC);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS a2a_tasks_scope_context ON a2a_tasks(tenant, caller, context_id, status_timestamp DESC, id DESC);`);
|
|
27
|
+
db.exec(`CREATE TABLE IF NOT EXISTS a2a_request_receipts (
|
|
28
|
+
tenant TEXT NOT NULL, caller TEXT NOT NULL, message_id TEXT NOT NULL,
|
|
29
|
+
request_sha256 TEXT NOT NULL, task_id TEXT, created_ms INTEGER NOT NULL,
|
|
30
|
+
PRIMARY KEY(tenant, caller, message_id));`);
|
|
31
|
+
}
|
|
32
|
+
async save(task, context) {
|
|
33
|
+
const scope = owner(context);
|
|
34
|
+
if (!task.id || !task.contextId)
|
|
35
|
+
throw new Error('A2A task id/context required');
|
|
36
|
+
const prior = this.db.prepare('SELECT tenant, caller FROM a2a_tasks WHERE id=?').get(task.id);
|
|
37
|
+
if (prior) {
|
|
38
|
+
const bound = z.object({ tenant: z.string(), caller: z.string() }).parse(prior);
|
|
39
|
+
if (bound.tenant !== scope.tenant || bound.caller !== scope.caller)
|
|
40
|
+
throw new Error('A2A task owner mismatch');
|
|
41
|
+
}
|
|
42
|
+
this.db.prepare(`INSERT INTO a2a_tasks(id,tenant,caller,context_id,status,status_timestamp,task_json,updated_ms)
|
|
43
|
+
VALUES (?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET context_id=excluded.context_id,status=excluded.status,
|
|
44
|
+
status_timestamp=excluded.status_timestamp,task_json=excluded.task_json,updated_ms=excluded.updated_ms`)
|
|
45
|
+
.run(task.id, scope.tenant, scope.caller, task.contextId, task.status?.state ?? TaskState.TASK_STATE_UNSPECIFIED, task.status?.timestamp ?? '', JSON.stringify(TaskCodec.toJSON(task)), Date.now());
|
|
46
|
+
const newest = task.history?.at(-1)?.messageId;
|
|
47
|
+
if (newest)
|
|
48
|
+
this.db.prepare('UPDATE a2a_request_receipts SET task_id=? WHERE tenant=? AND caller=? AND message_id=? AND (task_id IS NULL OR task_id=?)')
|
|
49
|
+
.run(task.id, scope.tenant, scope.caller, newest, task.id);
|
|
50
|
+
}
|
|
51
|
+
async load(taskId, context) {
|
|
52
|
+
const scope = owner(context);
|
|
53
|
+
const row = this.db.prepare('SELECT task_json FROM a2a_tasks WHERE id=? AND tenant=? AND caller=?').get(taskId, scope.tenant, scope.caller);
|
|
54
|
+
return row ? readTask(Row.parse(row).task_json) : undefined;
|
|
55
|
+
}
|
|
56
|
+
async list(params, context) {
|
|
57
|
+
const scope = owner(context);
|
|
58
|
+
const size = params.pageSize ?? 50;
|
|
59
|
+
if (!Number.isInteger(size) || size < 1 || size > 100)
|
|
60
|
+
throw new Error('Invalid A2A pageSize');
|
|
61
|
+
// The context and timestamp columns are written from the same task as
|
|
62
|
+
// task_json, so these SQL filters, ordering and cursor match the former
|
|
63
|
+
// in-memory evaluation. The status column's encoding depends on the SQLite
|
|
64
|
+
// driver's number binding, so a status filter is still applied to parsed tasks.
|
|
65
|
+
const where = ['tenant=?', 'caller=?'];
|
|
66
|
+
const values = [scope.tenant, scope.caller];
|
|
67
|
+
if (params.contextId) {
|
|
68
|
+
where.push('context_id=?');
|
|
69
|
+
values.push(params.contextId);
|
|
70
|
+
}
|
|
71
|
+
if (params.statusTimestampAfter) {
|
|
72
|
+
where.push("status_timestamp!='' AND status_timestamp>?");
|
|
73
|
+
values.push(params.statusTimestampAfter);
|
|
74
|
+
}
|
|
75
|
+
const filter = where.join(' AND ');
|
|
76
|
+
let cursor;
|
|
77
|
+
if (params.pageToken) {
|
|
78
|
+
try {
|
|
79
|
+
cursor = Cursor.parse(JSON.parse(Buffer.from(params.pageToken, 'base64url').toString('utf8')));
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new Error('Invalid A2A pageToken');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let page;
|
|
86
|
+
let total;
|
|
87
|
+
let more;
|
|
88
|
+
if (params.status !== undefined && params.status !== TaskState.TASK_STATE_UNSPECIFIED) {
|
|
89
|
+
const filtered = this.db.prepare(`SELECT task_json FROM a2a_tasks WHERE ${filter} ORDER BY status_timestamp DESC,id DESC`).all(...values)
|
|
90
|
+
.map(row => readTask(Row.parse(row).task_json)).filter(task => task.status?.state === params.status);
|
|
91
|
+
let start = 0;
|
|
92
|
+
if (cursor) {
|
|
93
|
+
const found = filtered.findIndex(task => task.id === cursor.id && (task.status?.timestamp ?? '') === cursor.timestamp);
|
|
94
|
+
if (found < 0)
|
|
95
|
+
throw new Error('A2A page cursor was not found');
|
|
96
|
+
start = found + 1;
|
|
97
|
+
}
|
|
98
|
+
page = filtered.slice(start, start + size);
|
|
99
|
+
total = filtered.length;
|
|
100
|
+
more = start + size < filtered.length;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
total = z.object({ total: z.number().int() }).parse(this.db.prepare(`SELECT count(*) AS total FROM a2a_tasks WHERE ${filter}`).get(...values)).total;
|
|
104
|
+
let after = '';
|
|
105
|
+
const afterValues = [];
|
|
106
|
+
if (cursor) {
|
|
107
|
+
if (!this.db.prepare(`SELECT 1 FROM a2a_tasks WHERE ${filter} AND id=? AND status_timestamp=?`).get(...values, cursor.id, cursor.timestamp))
|
|
108
|
+
throw new Error('A2A page cursor was not found');
|
|
109
|
+
after = ' AND (status_timestamp<? OR (status_timestamp=? AND id<?))';
|
|
110
|
+
afterValues.push(cursor.timestamp, cursor.timestamp, cursor.id);
|
|
111
|
+
}
|
|
112
|
+
const rows = this.db.prepare(`SELECT task_json FROM a2a_tasks WHERE ${filter}${after} ORDER BY status_timestamp DESC,id DESC LIMIT ?`)
|
|
113
|
+
.all(...values, ...afterValues, size + 1).map(row => readTask(Row.parse(row).task_json));
|
|
114
|
+
page = rows.slice(0, size);
|
|
115
|
+
more = rows.length > size;
|
|
116
|
+
}
|
|
117
|
+
const last = page.at(-1);
|
|
118
|
+
const nextPageToken = last && more
|
|
119
|
+
? Buffer.from(JSON.stringify({ timestamp: last.status?.timestamp ?? '', id: last.id })).toString('base64url') : '';
|
|
120
|
+
return { tasks: page.map(task => params.includeArtifacts ? task : { ...task, artifacts: [] }), nextPageToken, pageSize: size, totalSize: total };
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/a2a.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type AgentExecutor, type ExecutionEventBus, type RequestContext } from '@a2a-js/sdk/server';
|
|
2
|
+
import type { ArdentRuntime } from './runtime.js';
|
|
3
|
+
/** An extracted article is an owner read when its connector result binds the
|
|
4
|
+
* exact request URL, source URL, retrieval time, and content digest. It remains
|
|
5
|
+
* unverified reporting, but its URL can support a read-only team check. */
|
|
6
|
+
export declare function observedOwnerSourceReference(name: string, value: Record<string, unknown>): string | null;
|
|
7
|
+
export declare function observedOwnerToolResult(detail: unknown): {
|
|
8
|
+
name: string;
|
|
9
|
+
value: Record<string, unknown>;
|
|
10
|
+
reference: string | null;
|
|
11
|
+
} | null;
|
|
12
|
+
export declare class ArdentA2AExecutor implements AgentExecutor {
|
|
13
|
+
#private;
|
|
14
|
+
readonly runtime: ArdentRuntime;
|
|
15
|
+
readonly env: NodeJS.ProcessEnv;
|
|
16
|
+
constructor(runtime: ArdentRuntime, env?: NodeJS.ProcessEnv);
|
|
17
|
+
execute(request: RequestContext, bus: ExecutionEventBus): Promise<void>;
|
|
18
|
+
cancelTask(taskId: string, bus: ExecutionEventBus): Promise<void>;
|
|
19
|
+
}
|