@hanphone/dsh-a2a 0.2.0 → 0.3.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/README.md +106 -106
- package/README.zh.md +50 -74
- package/cordis.patch.yml +4 -7
- package/lib/client.js +186 -73
- package/lib/index.js +1275 -868
- package/lib/types/api.d.ts +30 -23
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/api.js +56 -23
- package/lib/types/api.js.map +1 -1
- package/lib/types/client/index.d.ts +50 -19
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/commands.d.ts +4 -2
- package/lib/types/commands.d.ts.map +1 -1
- package/lib/types/commands.js +59 -46
- package/lib/types/commands.js.map +1 -1
- package/lib/types/events.d.ts +32 -9
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/index.d.ts +21 -32
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +177 -272
- package/lib/types/index.js.map +1 -1
- package/lib/types/outbound/calls.d.ts.map +1 -1
- package/lib/types/outbound/calls.js +8 -6
- package/lib/types/outbound/calls.js.map +1 -1
- package/lib/types/protocol.d.ts +174 -109
- package/lib/types/protocol.d.ts.map +1 -1
- package/lib/types/protocol.js +58 -34
- package/lib/types/protocol.js.map +1 -1
- package/lib/types/server/a2a-server.d.ts +4 -0
- package/lib/types/server/a2a-server.d.ts.map +1 -1
- package/lib/types/server/a2a-server.js +6 -2
- package/lib/types/server/a2a-server.js.map +1 -1
- package/lib/types/server/card.d.ts +6 -30
- package/lib/types/server/card.d.ts.map +1 -1
- package/lib/types/server/card.js +13 -49
- package/lib/types/server/card.js.map +1 -1
- package/lib/types/server/exec/agent-runtime.d.ts +20 -0
- package/lib/types/server/exec/agent-runtime.d.ts.map +1 -1
- package/lib/types/server/exec/agent-runtime.js +2 -1
- package/lib/types/server/exec/agent-runtime.js.map +1 -1
- package/lib/types/server/routes.d.ts +5 -1
- package/lib/types/server/routes.d.ts.map +1 -1
- package/lib/types/server/routes.js +9 -5
- package/lib/types/server/routes.js.map +1 -1
- package/lib/types/server/store.d.ts +32 -0
- package/lib/types/server/store.d.ts.map +1 -1
- package/lib/types/server/store.js +10 -0
- package/lib/types/server/store.js.map +1 -1
- package/lib/types/servers/inbound-manager.d.ts +154 -0
- package/lib/types/servers/inbound-manager.d.ts.map +1 -0
- package/lib/types/servers/inbound-manager.js +335 -0
- package/lib/types/servers/inbound-manager.js.map +1 -0
- package/lib/types/servers/outbound-manager.d.ts +129 -0
- package/lib/types/servers/outbound-manager.d.ts.map +1 -0
- package/lib/types/servers/outbound-manager.js +238 -0
- package/lib/types/servers/outbound-manager.js.map +1 -0
- package/lib/types/service.d.ts +104 -40
- package/lib/types/service.d.ts.map +1 -1
- package/lib/types/service.js +36 -27
- package/lib/types/service.js.map +1 -1
- package/package.json +2 -2
- package/src/api.ts +65 -53
- package/src/client/index.ts +272 -148
- package/src/commands.ts +59 -43
- package/src/events.ts +14 -7
- package/src/index.ts +203 -310
- package/src/outbound/calls.ts +8 -6
- package/src/protocol.ts +204 -95
- package/src/server/a2a-server.ts +9 -2
- package/src/server/card.ts +13 -62
- package/src/server/exec/agent-runtime.ts +20 -2
- package/src/server/routes.ts +6 -4
- package/src/server/store.ts +42 -3
- package/src/servers/inbound-manager.ts +416 -0
- package/src/servers/outbound-manager.ts +289 -0
- package/src/service.ts +125 -40
- package/lib/tsconfig.client.tsbuildinfo +0 -1
- package/lib/tsconfig.tsbuildinfo +0 -1
package/lib/types/index.d.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @hanphone/dsh-a2a — Cordis plugin entry.
|
|
3
3
|
*
|
|
4
|
-
* Mounts the A2A v1.0 dual-end plugin
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Mounts the A2A v1.0.1 dual-end plugin as a MULTI-INSTANCE composition: an
|
|
5
|
+
* arbitrary set of inbound A2A servers (each a preset-bound session pool, its
|
|
6
|
+
* own endpoint + AgentCard + creator-declared skills + auth) and an arbitrary
|
|
7
|
+
* set of outbound A2A connections (each a remote URL + preset + timeout).
|
|
8
|
+
* Instances are persisted in the `a2a` domain (`inbound_servers` /
|
|
9
|
+
* `outbound_servers` tables) and created/edited/started/stopped entirely from
|
|
10
|
+
* the GUI — the plugin `Config` is minimal and instance setup is not
|
|
11
|
+
* patch-config driven. The protocol surface is aligned with the official A2A
|
|
12
|
+
* v1.0.1 spec (see docs/design.md).
|
|
13
|
+
*
|
|
14
|
+
* Optional services are probed rather than injected so a composition lacking
|
|
15
|
+
* one half idles just that half; only the storage domain is required.
|
|
11
16
|
*
|
|
12
17
|
* Function-plugin export shape: named `name`/`inject`/`Config`/`apply`, no
|
|
13
18
|
* default export (mixing forms makes the Loader drop the namespace).
|
|
@@ -15,39 +20,23 @@
|
|
|
15
20
|
*/
|
|
16
21
|
import type { Context } from '@deepseek-ai/cordis';
|
|
17
22
|
import z from '@deepseek-ai/schemastery';
|
|
18
|
-
import { type
|
|
19
|
-
import type { AgentSkill, TaskState } from './protocol.ts';
|
|
23
|
+
import { type OpResult } from './service.ts';
|
|
20
24
|
export declare const name = "a2a";
|
|
21
25
|
/** Only the storage domain is required; the other halves are probed. */
|
|
22
26
|
export declare const inject: readonly ["storageDomain"];
|
|
27
|
+
/** Minimal host-level config; per-instance setup happens in the GUI/domain. */
|
|
23
28
|
export interface A2AConfig {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
baseUrl?: string;
|
|
31
|
-
endpointPath: string;
|
|
32
|
-
/** Environment variable name for the inbound bearer token; absent = anonymous. */
|
|
33
|
-
authTokenEnv?: string;
|
|
34
|
-
skills: {
|
|
35
|
-
ids: string[];
|
|
36
|
-
exclude: string[];
|
|
37
|
-
};
|
|
38
|
-
executors: Record<string, 'session' | 'subagent'>;
|
|
39
|
-
subagentProvider: string;
|
|
40
|
-
};
|
|
41
|
-
client: {
|
|
42
|
-
agents: OutboundAgentSpec[];
|
|
43
|
-
toolPrefix: string;
|
|
44
|
-
};
|
|
29
|
+
/** Advertised base URL for every inbound card; absent lets a default stand. */
|
|
30
|
+
baseUrl?: string;
|
|
31
|
+
/** Subagent driver provider for the inbound subagent executors. */
|
|
32
|
+
subagentProvider: string;
|
|
33
|
+
/** Default outbound connection timeout (ms). */
|
|
34
|
+
defaultTimeoutMs: number;
|
|
45
35
|
}
|
|
46
36
|
/** Loader-validated config schema (defaults applied by the Loader). */
|
|
47
37
|
export declare const Config: z<A2AConfig>;
|
|
48
38
|
export declare function apply(ctx: Context, config: A2AConfig): void;
|
|
49
|
-
export type {
|
|
50
|
-
export type { AgentSkill };
|
|
39
|
+
export type { OpResult };
|
|
51
40
|
export { A2AServer } from './server/a2a-server.ts';
|
|
52
41
|
export { A2AClient, A2AError } from './outbound/calls.ts';
|
|
53
42
|
export { A2AService } from './service.ts';
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAQxC,OAAO,EAA8G,KAAK,QAAQ,EAAmC,MAAM,cAAc,CAAA;AAKzL,eAAO,MAAM,IAAI,QAAQ,CAAA;AAEzB,wEAAwE;AACxE,eAAO,MAAM,MAAM,4BAA6B,CAAA;AAEhD,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAA;IACxB,gDAAgD;IAChD,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,uEAAuE;AACvE,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,SAAS,CAI9B,CAAA;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,QAuFpD;AAoKD,YAAY,EAAE,QAAQ,EAAE,CAAA;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA"}
|
package/lib/types/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @hanphone/dsh-a2a — Cordis plugin entry.
|
|
3
3
|
*
|
|
4
|
-
* Mounts the A2A v1.0 dual-end plugin
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* Mounts the A2A v1.0.1 dual-end plugin as a MULTI-INSTANCE composition: an
|
|
5
|
+
* arbitrary set of inbound A2A servers (each a preset-bound session pool, its
|
|
6
|
+
* own endpoint + AgentCard + creator-declared skills + auth) and an arbitrary
|
|
7
|
+
* set of outbound A2A connections (each a remote URL + preset + timeout).
|
|
8
|
+
* Instances are persisted in the `a2a` domain (`inbound_servers` /
|
|
9
|
+
* `outbound_servers` tables) and created/edited/started/stopped entirely from
|
|
10
|
+
* the GUI — the plugin `Config` is minimal and instance setup is not
|
|
11
|
+
* patch-config driven. The protocol surface is aligned with the official A2A
|
|
12
|
+
* v1.0.1 spec (see docs/design.md).
|
|
13
|
+
*
|
|
14
|
+
* Optional services are probed rather than injected so a composition lacking
|
|
15
|
+
* one half idles just that half; only the storage domain is required.
|
|
11
16
|
*
|
|
12
17
|
* Function-plugin export shape: named `name`/`inject`/`Config`/`apply`, no
|
|
13
18
|
* default export (mixing forms makes the Loader drop the namespace).
|
|
@@ -17,189 +22,75 @@ import z from '@deepseek-ai/schemastery';
|
|
|
17
22
|
import { homedir } from 'node:os';
|
|
18
23
|
import { join } from 'node:path';
|
|
19
24
|
import { mkdirSync } from 'node:fs';
|
|
20
|
-
import { deriveSkills, buildCard } from "./server/card.js";
|
|
21
25
|
import { openDomain, DomainTaskStore } from "./server/store.js";
|
|
22
|
-
import {
|
|
23
|
-
import { cardOptionsFor, readIdentity, rebuildCardWithIdentity, writeIdentity } from "./server/identity.js";
|
|
24
|
-
import { ExecutorSet } from "./server/executor.js";
|
|
25
|
-
import { ContextSessionPool, probeService } from "./server/exec/agent-runtime.js";
|
|
26
|
-
import { createSessionExecutor } from "./server/exec/session.js";
|
|
27
|
-
import { createSubagentExecutor } from "./server/exec/subagent.js";
|
|
28
|
-
import { A2AServer } from "./server/a2a-server.js";
|
|
29
|
-
import { A2aRoutes } from "./server/routes.js";
|
|
26
|
+
import { probeService } from "./server/exec/agent-runtime.js";
|
|
30
27
|
import { handleApiRequest } from "./api.js";
|
|
31
|
-
import { OutboundAgentRegistry, DomainAgentStore } from "./outbound/registry.js";
|
|
32
28
|
import { A2AService } from "./service.js";
|
|
33
29
|
import { buildA2aCommand } from "./commands.js";
|
|
30
|
+
import { InboundServerManager, DomainInboundStore } from "./servers/inbound-manager.js";
|
|
31
|
+
import { OutboundServerManager, DomainOutboundStore } from "./servers/outbound-manager.js";
|
|
34
32
|
export const name = 'a2a';
|
|
35
33
|
/** Only the storage domain is required; the other halves are probed. */
|
|
36
34
|
export const inject = ['storageDomain'];
|
|
37
35
|
/** Loader-validated config schema (defaults applied by the Loader). */
|
|
38
36
|
export const Config = z.object({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
description: z.string().default('A DeepSeek Harness agent exposed over A2A v1.0'),
|
|
43
|
-
version: z.string().default('0.1.0'),
|
|
44
|
-
baseUrl: z.string(),
|
|
45
|
-
endpointPath: z.string().default('/a2a'),
|
|
46
|
-
authTokenEnv: z.string(),
|
|
47
|
-
skills: z.object({
|
|
48
|
-
ids: z.array(z.string()).default([]),
|
|
49
|
-
exclude: z.array(z.string()).default([]),
|
|
50
|
-
}),
|
|
51
|
-
executors: z.dict(z.union(['session', 'subagent'])).default({ chat: 'session' }),
|
|
52
|
-
subagentProvider: z.string().default('in-process'),
|
|
53
|
-
}),
|
|
54
|
-
client: z.object({
|
|
55
|
-
agents: z.array(z.object({
|
|
56
|
-
name: z.string(),
|
|
57
|
-
agentCardUrl: z.string(),
|
|
58
|
-
bearerTokenEnv: z.string(),
|
|
59
|
-
enabled: z.boolean().default(true),
|
|
60
|
-
timeoutMs: z.number().default(60000),
|
|
61
|
-
})).default([]),
|
|
62
|
-
toolPrefix: z.string().default('a2a'),
|
|
63
|
-
}),
|
|
37
|
+
baseUrl: z.string(),
|
|
38
|
+
subagentProvider: z.string().default('in-process'),
|
|
39
|
+
defaultTimeoutMs: z.number().default(60_000),
|
|
64
40
|
});
|
|
65
41
|
export function apply(ctx, config) {
|
|
66
42
|
const logger = ctx.logger('a2a');
|
|
67
|
-
const
|
|
68
|
-
const clientConfig = { ...config.client };
|
|
69
|
-
const authToken = serverConfig.authTokenEnv !== undefined ? process.env[serverConfig.authTokenEnv] : undefined;
|
|
70
|
-
const toolPrefix = clientConfig.toolPrefix;
|
|
71
|
-
const subagentProvider = serverConfig.subagentProvider;
|
|
43
|
+
const subagentProvider = config.subagentProvider;
|
|
72
44
|
const inboundCwd = inboundSessionCwd();
|
|
73
45
|
ctx.inject(['storageDomain'], (ctx) => {
|
|
74
46
|
ctx.effect(async () => {
|
|
75
47
|
const domain = await openDomain(ctx.storageDomain);
|
|
76
48
|
const store = new DomainTaskStore(domain);
|
|
77
|
-
|
|
78
|
-
domain,
|
|
79
|
-
store,
|
|
80
|
-
registry: undefined,
|
|
81
|
-
inbound: undefined,
|
|
82
|
-
server: undefined,
|
|
83
|
-
routes: undefined,
|
|
84
|
-
card: undefined,
|
|
85
|
-
executors: undefined,
|
|
86
|
-
enabled: serverConfig.enabled,
|
|
87
|
-
identityDefaults: {
|
|
88
|
-
name: serverConfig.name ?? 'My DSH Agent',
|
|
89
|
-
description: serverConfig.description ?? 'A DeepSeek Harness agent exposed over A2A v1.0',
|
|
90
|
-
version: serverConfig.version ?? '0.1.0',
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
// ── inbound server half ────────────────────────────────────────────
|
|
49
|
+
// Probe the optional host services each half uses.
|
|
94
50
|
const webServer = probeService(ctx, 'webServer', 'register');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
contextId: input.contextId,
|
|
133
|
-
skill: input.skill,
|
|
134
|
-
parts: input.parts,
|
|
135
|
-
remotePeerId: input.remotePeerId,
|
|
136
|
-
};
|
|
137
|
-
const decided = await ctx.waterfall('a2a/inbound-task', decision, async (d) => d);
|
|
138
|
-
// Built-in audit: every task decision is logged with its source.
|
|
139
|
-
logger.info(`[a2a] inbound skill=${decided.skill} remote=${decided.remotePeerId} rejected=${decided.rejected?.reason ?? 'no'}`);
|
|
140
|
-
if (decided.rejected !== undefined)
|
|
141
|
-
return { ok: false, reason: decided.rejected.reason };
|
|
142
|
-
return { ok: true };
|
|
143
|
-
};
|
|
144
|
-
const server = new A2AServer({
|
|
145
|
-
card,
|
|
146
|
-
store,
|
|
147
|
-
executors,
|
|
148
|
-
...(authToken ? { authToken } : {}),
|
|
149
|
-
gate,
|
|
150
|
-
onInbound: (facts) => inbound.note({
|
|
151
|
-
method: facts.method,
|
|
152
|
-
...(facts.source !== undefined ? { source: facts.source } : {}),
|
|
153
|
-
taskIds: facts.taskIds,
|
|
154
|
-
streaming: facts.streaming,
|
|
155
|
-
}),
|
|
156
|
-
onTaskSettled: (taskId) => {
|
|
157
|
-
logger.info(`[a2a] task settled ${taskId}`);
|
|
158
|
-
inbound.settle(taskId);
|
|
159
|
-
},
|
|
160
|
-
});
|
|
161
|
-
holder.server = server;
|
|
162
|
-
const routes = new A2aRoutes(webServer, server);
|
|
163
|
-
holder.routes = routes;
|
|
164
|
-
if (holder.enabled)
|
|
165
|
-
routes.enable();
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
logger.error(`a2a: server half failed to build: ${err.message}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
// ── outbound client half (after the inbound routes exist, so a
|
|
172
|
-
// loopback agent can fetch this server's own card) ──────────────────
|
|
173
|
-
const toolsService = probeService(ctx, 'tools', 'get');
|
|
174
|
-
if (toolsService === undefined) {
|
|
175
|
-
logger.warn('a2a: tools service not mounted; outbound client idle');
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
const registry = new OutboundAgentRegistry({
|
|
179
|
-
registrar: { register: (def) => toolsService.register(def) },
|
|
180
|
-
store: new DomainAgentStore(domain.agents),
|
|
181
|
-
toolPrefix,
|
|
182
|
-
tokenOf: (env) => (env ? process.env[env] : undefined),
|
|
183
|
-
onError: (message) => logger.warn(message),
|
|
184
|
-
});
|
|
185
|
-
holder.registry = registry;
|
|
186
|
-
registry.loadAll();
|
|
187
|
-
// Declared agents seed the registry: the persisted store stays the
|
|
188
|
-
// runtime state, so a disabled or removed agent is not reconnected.
|
|
189
|
-
if (clientConfig.agents.length > 0)
|
|
190
|
-
await registry.seed(clientConfig.agents);
|
|
191
|
-
}
|
|
192
|
-
// ── service facade + commands ──────────────────────────────────────
|
|
193
|
-
const impl = makeFacade(holder, logger.info.bind(logger));
|
|
51
|
+
const tools = probeService(ctx, 'tools', 'register');
|
|
52
|
+
const agents = probeService(ctx, 'agents', 'create');
|
|
53
|
+
const agentPresets = probeService(ctx, 'agentPresets', 'list');
|
|
54
|
+
const skills = probeService(ctx, 'skills', 'list');
|
|
55
|
+
const subagents = probeService(ctx, 'subagents', 'start');
|
|
56
|
+
const commandsRef = probeService(ctx, 'commands', 'register');
|
|
57
|
+
const baseUrl = config.baseUrl ?? `http://127.0.0.1:${webServerPort(webServer) ?? process.env['DSH_A2A_PORT'] ?? '3000'}`;
|
|
58
|
+
// ── inbound manager ───────────────────────────────────────────────
|
|
59
|
+
const inboundHost = {
|
|
60
|
+
ctx,
|
|
61
|
+
webServer: webServer ?? { register: noopRegister },
|
|
62
|
+
tasks: store,
|
|
63
|
+
logger,
|
|
64
|
+
...(agents !== undefined ? { agents } : {}),
|
|
65
|
+
...(agentPresets !== undefined ? { agentPresets } : {}),
|
|
66
|
+
...(skills !== undefined ? { skills } : {}),
|
|
67
|
+
...(subagents !== undefined ? { subagents } : {}),
|
|
68
|
+
...(agentPresets?.defaultId !== undefined ? { defaultPresetId: agentPresets.defaultId } : {}),
|
|
69
|
+
resolveDefaultModel: () => resolveDefaultModel(ctx),
|
|
70
|
+
sessionCwd: inboundCwd,
|
|
71
|
+
defaultBaseUrl: baseUrl,
|
|
72
|
+
subagentProvider,
|
|
73
|
+
newId: () => crypto.randomUUID().slice(0, 8),
|
|
74
|
+
};
|
|
75
|
+
const inboundManager = new InboundServerManager(inboundHost, new DomainInboundStore(domain.inbound_servers));
|
|
76
|
+
await inboundManager.boot();
|
|
77
|
+
// ── outbound manager ──────────────────────────────────────────────
|
|
78
|
+
const outboundHost = {
|
|
79
|
+
registrar: { register: (def) => tools?.register(def) },
|
|
80
|
+
tokenOf: (env) => (env ? process.env[env] : undefined),
|
|
81
|
+
onError: (message) => logger.warn(message),
|
|
82
|
+
defaultTimeoutMs: config.defaultTimeoutMs,
|
|
83
|
+
};
|
|
84
|
+
const outboundManager = new OutboundServerManager(outboundHost, domain.agents, new DomainOutboundStore(domain.outbound_servers), () => `out-${crypto.randomUUID().slice(0, 12)}`);
|
|
85
|
+
outboundManager.boot();
|
|
86
|
+
// ── service facade + commands + GUI API ───────────────────────────
|
|
87
|
+
const impl = makeFacade({ domain, store, inboundManager, outboundManager, agentPresets, logger: logger.info.bind(logger) });
|
|
194
88
|
new A2AService(ctx, impl);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
ctx.effect(() => commands.register(buildA2aCommand(impl)), 'a2a: command');
|
|
89
|
+
if (commandsRef !== undefined) {
|
|
90
|
+
ctx.effect(() => commandsRef.register(buildA2aCommand(impl)), 'a2a: command');
|
|
198
91
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (dashboardWebServer !== undefined) {
|
|
202
|
-
ctx.effect(() => dashboardWebServer.register({
|
|
92
|
+
if (webServer !== undefined) {
|
|
93
|
+
ctx.effect(() => webServer.register({
|
|
203
94
|
kind: 'prefix',
|
|
204
95
|
path: '/a2a/api',
|
|
205
96
|
handler: (req, res) => handleApiRequest(req, res, impl),
|
|
@@ -209,131 +100,134 @@ export function apply(ctx, config) {
|
|
|
209
100
|
logger.warn('a2a: webServer not mounted; GUI dashboard API idle');
|
|
210
101
|
}
|
|
211
102
|
return async () => {
|
|
212
|
-
|
|
213
|
-
await
|
|
214
|
-
await holder.registry?.disposeAll();
|
|
103
|
+
inboundManager.disposeAll();
|
|
104
|
+
await outboundManager.disposeAll();
|
|
215
105
|
await domain.close();
|
|
216
106
|
};
|
|
217
|
-
}, 'a2a:
|
|
107
|
+
}, 'a2a: multi-instance composition');
|
|
218
108
|
});
|
|
219
109
|
}
|
|
220
|
-
/** Facade closure over the
|
|
221
|
-
function makeFacade(
|
|
222
|
-
const
|
|
110
|
+
/** Facade closure over the two managers (commands and `ctx.a2a` consumers). */
|
|
111
|
+
function makeFacade(host) {
|
|
112
|
+
const inboundView = (id) => {
|
|
113
|
+
const live = host.inboundManager.get(id);
|
|
114
|
+
if (live === undefined)
|
|
115
|
+
return undefined;
|
|
116
|
+
const r = live.record;
|
|
117
|
+
return {
|
|
118
|
+
id: r.id,
|
|
119
|
+
name: r.name,
|
|
120
|
+
description: r.description,
|
|
121
|
+
version: r.version,
|
|
122
|
+
endpointPath: live.endpointPath,
|
|
123
|
+
// Effective preset: the record's, else the deployment default.
|
|
124
|
+
...(live.preset !== undefined ? { preset: live.preset } : {}),
|
|
125
|
+
...(r.authTokenEnv !== undefined ? { authTokenEnv: r.authTokenEnv } : {}),
|
|
126
|
+
cardPath: live.cardPath,
|
|
127
|
+
...(live.card.supportedInterfaces?.[0]?.url !== undefined ? { cardUrl: live.card.supportedInterfaces[0].url } : {}),
|
|
128
|
+
enabled: live.routes.active,
|
|
129
|
+
skills: live.skills.map((s) => ({ id: s.id, name: s.name, ...(s.description != null ? { description: s.description } : {}) })),
|
|
130
|
+
};
|
|
131
|
+
};
|
|
223
132
|
return {
|
|
224
133
|
status() {
|
|
225
134
|
return {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
skills: holder.card?.skills?.map((s) => s.id) ?? [],
|
|
230
|
-
executors: holder.executors ? ['session', ...(holder.executors['subagent'] !== undefined ? ['subagent'] : [])] : [],
|
|
231
|
-
name: holder.card?.name,
|
|
232
|
-
description: holder.card?.description,
|
|
233
|
-
version: holder.card?.version,
|
|
234
|
-
// True when a persisted identity override exists (the dashboard has
|
|
235
|
-
// been configured); guides the first-run onboarding.
|
|
236
|
-
configured: readIdentity(holder.domain) !== undefined,
|
|
237
|
-
},
|
|
238
|
-
tasks: holder.store.list().length,
|
|
239
|
-
agents: holder.registry?.list() ?? [],
|
|
240
|
-
inbounds: holder.inbound?.list() ?? [],
|
|
135
|
+
inbounds: host.inboundManager.list().map((live) => inboundView(live.id)).filter((v) => v !== undefined),
|
|
136
|
+
outbounds: host.outboundManager.list(),
|
|
137
|
+
tasks: host.store.list().length,
|
|
241
138
|
};
|
|
242
139
|
},
|
|
243
|
-
async
|
|
244
|
-
if (
|
|
245
|
-
return
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
140
|
+
async presets() {
|
|
141
|
+
if (host.agentPresets === undefined)
|
|
142
|
+
return [];
|
|
143
|
+
const defaultId = host.agentPresets.defaultId;
|
|
144
|
+
try {
|
|
145
|
+
return (await host.agentPresets.list()).map((p) => ({
|
|
146
|
+
id: p.id,
|
|
147
|
+
...(p.name !== undefined ? { name: p.name } : {}),
|
|
148
|
+
...(p.description !== undefined ? { description: p.description } : {}),
|
|
149
|
+
...(defaultId !== undefined && p.id === defaultId ? { isDefault: true } : {}),
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
252
155
|
},
|
|
253
|
-
|
|
254
|
-
|
|
156
|
+
// ── inbound ─────────────────────────────────────────────────────────
|
|
157
|
+
listInboundServers() {
|
|
158
|
+
return host.inboundManager.list().map((live) => inboundView(live.id)).filter((v) => v !== undefined);
|
|
255
159
|
},
|
|
256
|
-
|
|
257
|
-
|
|
160
|
+
async createInboundServer(input) {
|
|
161
|
+
const result = await host.inboundManager.add({
|
|
162
|
+
name: input.name,
|
|
163
|
+
description: input.description,
|
|
164
|
+
version: input.version,
|
|
165
|
+
...(input.endpointPath !== undefined ? { endpointPath: input.endpointPath } : {}),
|
|
166
|
+
...(input.preset !== undefined ? { preset: input.preset } : {}),
|
|
167
|
+
...(input.authTokenEnv !== undefined ? { authTokenEnv: input.authTokenEnv } : {}),
|
|
168
|
+
...(input.enabled !== undefined ? { enabled: input.enabled } : {}),
|
|
169
|
+
});
|
|
170
|
+
host.logger(`[a2a] inbound created: ${result.message}`);
|
|
171
|
+
return result;
|
|
258
172
|
},
|
|
259
|
-
async
|
|
260
|
-
|
|
261
|
-
if (record === undefined)
|
|
262
|
-
return { ok: false, message: `task ${taskId} not found` };
|
|
263
|
-
const aborted = holder.server?.abort?.(taskId) ?? false;
|
|
264
|
-
return { ok: true, message: aborted ? `task ${taskId} canceled` : `task ${taskId} already terminal` };
|
|
173
|
+
async removeInboundServer(id) {
|
|
174
|
+
return host.inboundManager.remove(id);
|
|
265
175
|
},
|
|
266
|
-
|
|
267
|
-
return
|
|
176
|
+
async setInboundServerEnabled(id, enabled) {
|
|
177
|
+
return host.inboundManager.setEnabled(id, enabled);
|
|
268
178
|
},
|
|
269
|
-
async
|
|
270
|
-
|
|
271
|
-
if (registry === undefined)
|
|
272
|
-
return { ok: false, message: 'outbound client not mounted (no tools service)' };
|
|
273
|
-
const result = await registry.add({ ...spec, enabled: true, timeoutMs: 60000 });
|
|
274
|
-
return result;
|
|
179
|
+
async updateInboundServer(id, patch) {
|
|
180
|
+
return host.inboundManager.update(id, patch);
|
|
275
181
|
},
|
|
276
|
-
|
|
277
|
-
|
|
182
|
+
// ── outbound ────────────────────────────────────────────────────────
|
|
183
|
+
listOutboundServers() {
|
|
184
|
+
return [...host.outboundManager.list()];
|
|
278
185
|
},
|
|
279
|
-
async
|
|
280
|
-
return
|
|
186
|
+
async createOutboundServer(input) {
|
|
187
|
+
return host.outboundManager.add({
|
|
188
|
+
...(input.id !== undefined ? { id: input.id } : {}),
|
|
189
|
+
name: input.name,
|
|
190
|
+
agentCardUrl: input.agentCardUrl,
|
|
191
|
+
...(input.bearerTokenEnv !== undefined ? { bearerTokenEnv: input.bearerTokenEnv } : {}),
|
|
192
|
+
...(input.preset !== undefined ? { preset: input.preset } : {}),
|
|
193
|
+
enabled: input.enabled ?? true,
|
|
194
|
+
timeoutMs: input.timeoutMs ?? host.outboundManager.defaultTimeoutMs,
|
|
195
|
+
});
|
|
281
196
|
},
|
|
282
|
-
async
|
|
283
|
-
return
|
|
197
|
+
async removeOutboundServer(id) {
|
|
198
|
+
return host.outboundManager.remove(id);
|
|
284
199
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const persisted = readIdentity(holder.domain);
|
|
288
|
-
return {
|
|
289
|
-
...(persisted ?? {}),
|
|
290
|
-
// Composition defaults shown when nothing is persisted yet.
|
|
291
|
-
defaults: holder.identityDefaults,
|
|
292
|
-
name: current?.name ?? holder.identityDefaults.name,
|
|
293
|
-
description: current?.description ?? holder.identityDefaults.description,
|
|
294
|
-
version: current?.version ?? holder.identityDefaults.version,
|
|
295
|
-
};
|
|
200
|
+
async setOutboundServerEnabled(id, enabled) {
|
|
201
|
+
return host.outboundManager.setEnabled(id, enabled);
|
|
296
202
|
},
|
|
297
|
-
async
|
|
298
|
-
|
|
299
|
-
const server = holder.server;
|
|
300
|
-
if (card === undefined || server === undefined)
|
|
301
|
-
return { ok: false, message: 'inbound server not mounted (no card)' };
|
|
302
|
-
const persisted = readIdentity(holder.domain);
|
|
303
|
-
const base = persisted ?? holder.identityDefaults;
|
|
304
|
-
const next = {
|
|
305
|
-
name: patch.name?.trim() || base.name,
|
|
306
|
-
description: patch.description?.trim() || base.description,
|
|
307
|
-
version: patch.version?.trim() || base.version,
|
|
308
|
-
};
|
|
309
|
-
writeIdentity(holder.domain, next);
|
|
310
|
-
const rebuilt = rebuildCardWithIdentity(card, next, card.skills ?? []);
|
|
311
|
-
server.setCard(rebuilt);
|
|
312
|
-
holder.card = rebuilt;
|
|
313
|
-
log(`[a2a] identity updated: ${next.name}`);
|
|
314
|
-
return { ok: true, message: `service identity updated (${next.name})` };
|
|
203
|
+
async refreshOutboundServer(id) {
|
|
204
|
+
return host.outboundManager.refresh(id);
|
|
315
205
|
},
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
await holder.server?.abort?.(taskId);
|
|
323
|
-
}
|
|
324
|
-
return inbound.closePeer(peerId);
|
|
206
|
+
// ── tasks ───────────────────────────────────────────────────────────
|
|
207
|
+
getTask(taskId) {
|
|
208
|
+
return host.store.get(taskId);
|
|
209
|
+
},
|
|
210
|
+
listTasks() {
|
|
211
|
+
return host.store.list();
|
|
325
212
|
},
|
|
213
|
+
async cancelTask(taskId) {
|
|
214
|
+
const live = host.inboundManager.list().find((l) => l.server.abort(taskId));
|
|
215
|
+
return { ok: live !== undefined, message: live !== undefined ? `task ${taskId} canceled` : `task ${taskId} not found or already terminal` };
|
|
216
|
+
},
|
|
217
|
+
// ── inbound peers (aggregated across instances) ─────────────────────
|
|
326
218
|
inbounds() {
|
|
327
|
-
return
|
|
219
|
+
return host.inboundManager.list().flatMap((live) => live.inbound.list());
|
|
328
220
|
},
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
221
|
+
async closeInbound(peerId) {
|
|
222
|
+
for (const live of host.inboundManager.list()) {
|
|
223
|
+
const peers = live.inbound.activeTasksOf(peerId);
|
|
224
|
+
for (const taskId of peers)
|
|
225
|
+
live.server.abort(taskId);
|
|
226
|
+
const result = live.inbound.closePeer(peerId);
|
|
227
|
+
if (result)
|
|
228
|
+
return { ok: true, message: `inbound peer ${peerId} closed` };
|
|
229
|
+
}
|
|
230
|
+
return { ok: false, message: `inbound peer ${peerId} not found` };
|
|
337
231
|
},
|
|
338
232
|
};
|
|
339
233
|
}
|
|
@@ -354,6 +248,17 @@ function inboundSessionCwd() {
|
|
|
354
248
|
}
|
|
355
249
|
return dir;
|
|
356
250
|
}
|
|
251
|
+
function noopRegister() {
|
|
252
|
+
return () => { };
|
|
253
|
+
}
|
|
254
|
+
/** The listening port of a mounted webServer (undefined when unavailable). */
|
|
255
|
+
function webServerPort(webServer) {
|
|
256
|
+
const candidate = webServer;
|
|
257
|
+
if (candidate === undefined)
|
|
258
|
+
return undefined;
|
|
259
|
+
const value = typeof candidate.port === 'function' ? candidate.port() : candidate.port;
|
|
260
|
+
return typeof value === 'number' && Number.isFinite(value) ? String(value) : undefined;
|
|
261
|
+
}
|
|
357
262
|
export { A2AServer } from "./server/a2a-server.js";
|
|
358
263
|
export { A2AClient, A2AError } from "./outbound/calls.js";
|
|
359
264
|
export { A2AService } from "./service.js";
|