@zhijiewang/openharness 1.0.0 → 1.3.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.
@@ -1,18 +1,24 @@
1
1
  /**
2
2
  * Remote server — HTTP + WebSocket server for remote agent dispatch,
3
- * bidirectional channels, and structured event streaming.
3
+ * bidirectional channels, A2A protocol, and structured event streaming.
4
4
  *
5
5
  * Endpoints:
6
- * - POST /dispatch — send a prompt, get a streaming response
6
+ * - POST /dispatch — send a prompt, get a streaming response (SSE)
7
+ * - POST /a2a — A2A protocol: task delegation, discovery, status
7
8
  * - GET /status — check server status
8
9
  * - WS /channel — bidirectional WebSocket channel
10
+ *
11
+ * Security: bearer token auth, per-IP rate limiting, tool allowlists.
9
12
  */
10
13
  import { createServer } from 'node:http';
11
14
  import { WebSocketServer, WebSocket } from 'ws';
15
+ import { authenticateRequest, filterRemoteTools } from './auth.js';
16
+ import { createSessionCard, publishCard, unpublishCard, discoverAgents, generateMessageId, } from '../services/a2a.js';
12
17
  export class RemoteServer {
13
18
  config;
14
19
  channels = new Map();
15
20
  server = null;
21
+ agentCardId = null;
16
22
  constructor(config) {
17
23
  this.config = config;
18
24
  }
@@ -33,12 +39,27 @@ export class RemoteServer {
33
39
  });
34
40
  this.server.listen(this.config.port, () => {
35
41
  process.stderr.write(`[remote] Server listening on http://localhost:${this.config.port}\n`);
36
- process.stderr.write(`[remote] Endpoints: POST /dispatch, GET /status, WS /channel\n`);
42
+ process.stderr.write(`[remote] Endpoints: POST /dispatch, POST /a2a, GET /status, WS /channel\n`);
43
+ // Publish A2A agent card with HTTP endpoint
44
+ const sessionId = this.config.sessionId ?? Date.now().toString(36);
45
+ const card = createSessionCard(sessionId, {
46
+ provider: this.config.provider.name,
47
+ model: this.config.model,
48
+ port: this.config.port,
49
+ });
50
+ publishCard(card);
51
+ this.agentCardId = card.id;
52
+ process.stderr.write(`[remote] A2A agent card published: ${card.id}\n`);
37
53
  resolve();
38
54
  });
39
55
  });
40
56
  }
41
57
  stop() {
58
+ // Unpublish A2A card
59
+ if (this.agentCardId) {
60
+ unpublishCard(this.agentCardId);
61
+ this.agentCardId = null;
62
+ }
42
63
  for (const ch of this.channels.values()) {
43
64
  ch.abortController.abort();
44
65
  ch.ws.close();
@@ -50,12 +71,23 @@ export class RemoteServer {
50
71
  // CORS headers
51
72
  res.setHeader('Access-Control-Allow-Origin', '*');
52
73
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
53
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
74
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
54
75
  if (req.method === 'OPTIONS') {
55
76
  res.writeHead(204);
56
77
  res.end();
57
78
  return;
58
79
  }
80
+ // Auth check (skip for /status which is a health check)
81
+ if (req.url !== '/status') {
82
+ const auth = authenticateRequest(req, res);
83
+ if (!auth.allowed) {
84
+ const status = auth.reason?.includes('Rate limit') ? 429 : 401;
85
+ res.writeHead(status, { 'Content-Type': 'application/json' });
86
+ res.end(JSON.stringify({ error: auth.reason, requestId: auth.requestId }));
87
+ return;
88
+ }
89
+ }
90
+ // ── GET /status ──
59
91
  if (req.url === '/status' && req.method === 'GET') {
60
92
  res.writeHead(200, { 'Content-Type': 'application/json' });
61
93
  res.end(JSON.stringify({
@@ -63,50 +95,146 @@ export class RemoteServer {
63
95
  provider: this.config.provider.name,
64
96
  model: this.config.model,
65
97
  channels: this.channels.size,
98
+ agentId: this.agentCardId,
66
99
  }));
67
100
  return;
68
101
  }
102
+ // ── POST /dispatch ──
69
103
  if (req.url === '/dispatch' && req.method === 'POST') {
70
- const body = await readBody(req);
71
- try {
72
- const { prompt, maxTurns } = JSON.parse(body);
73
- if (!prompt) {
74
- res.writeHead(400, { 'Content-Type': 'application/json' });
75
- res.end(JSON.stringify({ error: 'Missing "prompt" field' }));
104
+ await this.handleDispatch(req, res);
105
+ return;
106
+ }
107
+ // ── POST /a2a ──
108
+ if (req.url === '/a2a' && req.method === 'POST') {
109
+ await this.handleA2A(req, res);
110
+ return;
111
+ }
112
+ res.writeHead(404, { 'Content-Type': 'application/json' });
113
+ res.end(JSON.stringify({ error: 'Not found' }));
114
+ }
115
+ async handleDispatch(req, res) {
116
+ const body = await readBody(req);
117
+ try {
118
+ const { prompt, maxTurns } = JSON.parse(body);
119
+ if (!prompt) {
120
+ res.writeHead(400, { 'Content-Type': 'application/json' });
121
+ res.end(JSON.stringify({ error: 'Missing "prompt" field' }));
122
+ return;
123
+ }
124
+ // Apply tool filtering for remote callers
125
+ const tools = filterRemoteTools(this.config.tools);
126
+ // Stream response as Server-Sent Events
127
+ res.writeHead(200, {
128
+ 'Content-Type': 'text/event-stream',
129
+ 'Cache-Control': 'no-cache',
130
+ 'Connection': 'keep-alive',
131
+ });
132
+ const { query } = await import('../query.js');
133
+ const config = {
134
+ provider: this.config.provider,
135
+ tools,
136
+ systemPrompt: this.config.systemPrompt,
137
+ permissionMode: this.config.permissionMode,
138
+ model: this.config.model,
139
+ maxTurns: maxTurns ?? 20,
140
+ };
141
+ for await (const event of query(prompt, config)) {
142
+ const data = JSON.stringify(event);
143
+ res.write(`data: ${data}\n\n`);
144
+ }
145
+ res.write('data: [DONE]\n\n');
146
+ res.end();
147
+ }
148
+ catch (err) {
149
+ if (!res.headersSent) {
150
+ res.writeHead(500, { 'Content-Type': 'application/json' });
151
+ }
152
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
153
+ }
154
+ }
155
+ /**
156
+ * A2A protocol handler — receives inter-agent messages.
157
+ *
158
+ * Supports:
159
+ * - task: delegate a task to this agent
160
+ * - discover: return this agent's capabilities
161
+ * - status: return current state
162
+ * - cancel: abort a running task
163
+ */
164
+ async handleA2A(req, res) {
165
+ const body = await readBody(req);
166
+ try {
167
+ const message = JSON.parse(body);
168
+ switch (message.payload.kind) {
169
+ case 'discover': {
170
+ // Return our agent card
171
+ const agents = discoverAgents();
172
+ const self = agents.find(a => a.id === this.agentCardId);
173
+ const response = {
174
+ id: generateMessageId(),
175
+ from: this.agentCardId ?? 'unknown',
176
+ to: message.from,
177
+ type: 'result',
178
+ payload: { kind: 'result', taskId: message.id, output: self ?? { error: 'agent not found' } },
179
+ timestamp: Date.now(),
180
+ };
181
+ res.writeHead(200, { 'Content-Type': 'application/json' });
182
+ res.end(JSON.stringify(response));
76
183
  return;
77
184
  }
78
- // Stream response as Server-Sent Events
79
- res.writeHead(200, {
80
- 'Content-Type': 'text/event-stream',
81
- 'Cache-Control': 'no-cache',
82
- 'Connection': 'keep-alive',
83
- });
84
- const { query } = await import('../query.js');
85
- const config = {
86
- provider: this.config.provider,
87
- tools: this.config.tools,
88
- systemPrompt: this.config.systemPrompt,
89
- permissionMode: this.config.permissionMode,
90
- model: this.config.model,
91
- maxTurns: maxTurns ?? 20,
92
- };
93
- for await (const event of query(prompt, config)) {
94
- const data = JSON.stringify(event);
95
- res.write(`data: ${data}\n\n`);
185
+ case 'task': {
186
+ // Execute the task via query loop
187
+ const tools = filterRemoteTools(this.config.tools);
188
+ const { query } = await import('../query.js');
189
+ const config = {
190
+ provider: this.config.provider,
191
+ tools,
192
+ systemPrompt: `[A2A Task from agent ${message.from}]\n\n${this.config.systemPrompt}`,
193
+ permissionMode: this.config.permissionMode,
194
+ model: this.config.model,
195
+ maxTurns: 10,
196
+ };
197
+ let output = '';
198
+ for await (const event of query(String(message.payload.input), config)) {
199
+ if (event.type === 'text_delta')
200
+ output += event.content;
201
+ }
202
+ const response = {
203
+ id: generateMessageId(),
204
+ from: this.agentCardId ?? 'unknown',
205
+ to: message.from,
206
+ type: 'result',
207
+ payload: { kind: 'result', taskId: message.id, output },
208
+ timestamp: Date.now(),
209
+ };
210
+ res.writeHead(200, { 'Content-Type': 'application/json' });
211
+ res.end(JSON.stringify(response));
212
+ return;
96
213
  }
97
- res.write('data: [DONE]\n\n');
98
- res.end();
99
- }
100
- catch (err) {
101
- if (!res.headersSent) {
102
- res.writeHead(500, { 'Content-Type': 'application/json' });
214
+ case 'status': {
215
+ const response = {
216
+ id: generateMessageId(),
217
+ from: this.agentCardId ?? 'unknown',
218
+ to: message.from,
219
+ type: 'status',
220
+ payload: { kind: 'status', state: 'idle' },
221
+ timestamp: Date.now(),
222
+ };
223
+ res.writeHead(200, { 'Content-Type': 'application/json' });
224
+ res.end(JSON.stringify(response));
225
+ return;
226
+ }
227
+ default: {
228
+ res.writeHead(400, { 'Content-Type': 'application/json' });
229
+ res.end(JSON.stringify({ error: `Unknown A2A message kind: ${message.payload.kind}` }));
230
+ return;
103
231
  }
104
- res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
105
232
  }
106
- return;
107
233
  }
108
- res.writeHead(404, { 'Content-Type': 'application/json' });
109
- res.end(JSON.stringify({ error: 'Not found' }));
234
+ catch (err) {
235
+ res.writeHead(400, { 'Content-Type': 'application/json' });
236
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }));
237
+ }
110
238
  }
111
239
  handleChannel(ws) {
112
240
  const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
@@ -119,10 +247,11 @@ export class RemoteServer {
119
247
  try {
120
248
  const msg = JSON.parse(data.toString());
121
249
  if (msg.type === 'dispatch') {
250
+ const tools = filterRemoteTools(this.config.tools);
122
251
  const { query } = await import('../query.js');
123
252
  const config = {
124
253
  provider: this.config.provider,
125
- tools: this.config.tools,
254
+ tools,
126
255
  systemPrompt: this.config.systemPrompt,
127
256
  permissionMode: this.config.permissionMode,
128
257
  model: this.config.model,
package/dist/repl.js CHANGED
@@ -62,6 +62,13 @@ export async function startREPL(config) {
62
62
  const { CronExecutor } = await import('./services/CronExecutor.js');
63
63
  const cronExecutor = new CronExecutor(config.provider, config.tools, config.systemPrompt, config.permissionMode, config.model);
64
64
  cronExecutor.start();
65
+ // A2A: publish agent card for cross-process discovery
66
+ const { createSessionCard, publishCard, unpublishCard } = await import('./services/a2a.js');
67
+ const agentCard = createSessionCard(session.id, {
68
+ provider: config.provider.name,
69
+ model: config.model,
70
+ });
71
+ publishCard(agentCard);
65
72
  const cost = new CostTracker();
66
73
  let cachedConfig = readOhConfig();
67
74
  // Centralized state store — all REPL state lives here
@@ -923,6 +930,7 @@ export async function startREPL(config) {
923
930
  if (cleanedUp)
924
931
  return;
925
932
  cleanedUp = true;
933
+ unpublishCard(agentCard.id);
926
934
  cronExecutor.stop();
927
935
  renderer.stop();
928
936
  session.messages = messages;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * PipelineExecutor — declarative multi-step tool workflows.
3
+ *
4
+ * Executes a sequence of tool calls with dependency resolution and
5
+ * variable substitution. Steps can reference prior step outputs via $stepId.
6
+ *
7
+ * Unlike the LLM-mediated agent loop, pipelines are deterministic —
8
+ * faster, cheaper, and repeatable for known workflows.
9
+ *
10
+ * Reuses the dependency resolution pattern from AgentDispatcher.
11
+ */
12
+ import type { Tools, ToolContext } from '../Tool.js';
13
+ export type PipelineStep = {
14
+ id: string;
15
+ tool: string;
16
+ args: Record<string, unknown>;
17
+ dependsOn?: string[];
18
+ };
19
+ export type PipelineStepResult = {
20
+ stepId: string;
21
+ output: string;
22
+ isError: boolean;
23
+ durationMs: number;
24
+ };
25
+ export declare class PipelineExecutor {
26
+ private tools;
27
+ private context;
28
+ constructor(tools: Tools, context: ToolContext);
29
+ /**
30
+ * Execute a pipeline. Returns results for all steps.
31
+ * Steps with unmet dependencies (failed/skipped blockers) are skipped.
32
+ */
33
+ execute(steps: PipelineStep[]): Promise<PipelineStepResult[]>;
34
+ private isReady;
35
+ private hasFailedBlocker;
36
+ private executeStep;
37
+ /**
38
+ * Resolve $stepId references in args.
39
+ * If a string value starts with $, replace it with the output of that step.
40
+ * Supports nested objects and arrays.
41
+ */
42
+ private resolveArgs;
43
+ }
44
+ /**
45
+ * Format pipeline results as a readable summary.
46
+ */
47
+ export declare function formatPipelineResults(results: PipelineStepResult[]): string;
48
+ //# sourceMappingURL=PipelineExecutor.d.ts.map
@@ -0,0 +1,179 @@
1
+ /**
2
+ * PipelineExecutor — declarative multi-step tool workflows.
3
+ *
4
+ * Executes a sequence of tool calls with dependency resolution and
5
+ * variable substitution. Steps can reference prior step outputs via $stepId.
6
+ *
7
+ * Unlike the LLM-mediated agent loop, pipelines are deterministic —
8
+ * faster, cheaper, and repeatable for known workflows.
9
+ *
10
+ * Reuses the dependency resolution pattern from AgentDispatcher.
11
+ */
12
+ import { findToolByName } from '../Tool.js';
13
+ // ── Executor ──
14
+ export class PipelineExecutor {
15
+ tools;
16
+ context;
17
+ constructor(tools, context) {
18
+ this.tools = tools;
19
+ this.context = context;
20
+ }
21
+ /**
22
+ * Execute a pipeline. Returns results for all steps.
23
+ * Steps with unmet dependencies (failed/skipped blockers) are skipped.
24
+ */
25
+ async execute(steps) {
26
+ // Validate step IDs are unique
27
+ const ids = new Set(steps.map(s => s.id));
28
+ if (ids.size !== steps.length) {
29
+ return [{ stepId: 'pipeline', output: 'Error: duplicate step IDs', isError: true, durationMs: 0 }];
30
+ }
31
+ const internal = new Map();
32
+ for (const step of steps) {
33
+ internal.set(step.id, { ...step, status: 'pending' });
34
+ }
35
+ const results = [];
36
+ // Process steps in dependency order
37
+ while (true) {
38
+ const ready = [...internal.values()].filter(s => s.status === 'pending' && this.isReady(s, internal));
39
+ const running = [...internal.values()].filter(s => s.status === 'running');
40
+ if (ready.length === 0 && running.length === 0)
41
+ break;
42
+ // Execute ready steps (sequentially for safety — tools may have side effects)
43
+ for (const step of ready) {
44
+ step.status = 'running';
45
+ // Check if any blocker failed — skip this step
46
+ if (this.hasFailedBlocker(step, internal)) {
47
+ step.status = 'skipped';
48
+ const result = {
49
+ stepId: step.id,
50
+ output: 'Skipped: dependency failed',
51
+ isError: true,
52
+ durationMs: 0,
53
+ };
54
+ step.result = result;
55
+ results.push(result);
56
+ continue;
57
+ }
58
+ const result = await this.executeStep(step, internal);
59
+ step.result = result;
60
+ step.status = result.isError ? 'failed' : 'completed';
61
+ results.push(result);
62
+ }
63
+ }
64
+ return results;
65
+ }
66
+ isReady(step, all) {
67
+ if (!step.dependsOn || step.dependsOn.length === 0)
68
+ return true;
69
+ return step.dependsOn.every(id => {
70
+ const dep = all.get(id);
71
+ return dep && (dep.status === 'completed' || dep.status === 'failed' || dep.status === 'skipped');
72
+ });
73
+ }
74
+ hasFailedBlocker(step, all) {
75
+ if (!step.dependsOn)
76
+ return false;
77
+ return step.dependsOn.some(id => {
78
+ const dep = all.get(id);
79
+ return dep && (dep.status === 'failed' || dep.status === 'skipped');
80
+ });
81
+ }
82
+ async executeStep(step, all) {
83
+ const start = Date.now();
84
+ // Find the tool
85
+ const tool = findToolByName(this.tools, step.tool);
86
+ if (!tool) {
87
+ return {
88
+ stepId: step.id,
89
+ output: `Error: unknown tool '${step.tool}'`,
90
+ isError: true,
91
+ durationMs: Date.now() - start,
92
+ };
93
+ }
94
+ // Substitute $refs in args
95
+ const resolvedArgs = this.resolveArgs(step.args, all);
96
+ // Validate and execute
97
+ const parsed = tool.inputSchema.safeParse(resolvedArgs);
98
+ if (!parsed.success) {
99
+ return {
100
+ stepId: step.id,
101
+ output: `Validation error: ${parsed.error.message}`,
102
+ isError: true,
103
+ durationMs: Date.now() - start,
104
+ };
105
+ }
106
+ try {
107
+ const result = await tool.call(parsed.data, this.context);
108
+ return {
109
+ stepId: step.id,
110
+ output: result.output,
111
+ isError: result.isError,
112
+ durationMs: Date.now() - start,
113
+ };
114
+ }
115
+ catch (err) {
116
+ return {
117
+ stepId: step.id,
118
+ output: `Error: ${err instanceof Error ? err.message : String(err)}`,
119
+ isError: true,
120
+ durationMs: Date.now() - start,
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Resolve $stepId references in args.
126
+ * If a string value starts with $, replace it with the output of that step.
127
+ * Supports nested objects and arrays.
128
+ */
129
+ resolveArgs(args, all) {
130
+ const resolve = (value) => {
131
+ if (typeof value === 'string' && value.startsWith('$')) {
132
+ const refId = value.slice(1);
133
+ const refStep = all.get(refId);
134
+ if (refStep?.result && !refStep.result.isError) {
135
+ return refStep.result.output;
136
+ }
137
+ return value; // Keep as-is if ref not found
138
+ }
139
+ if (Array.isArray(value))
140
+ return value.map(resolve);
141
+ if (value && typeof value === 'object') {
142
+ const resolved = {};
143
+ for (const [k, v] of Object.entries(value)) {
144
+ resolved[k] = resolve(v);
145
+ }
146
+ return resolved;
147
+ }
148
+ return value;
149
+ };
150
+ return resolve(args);
151
+ }
152
+ }
153
+ /**
154
+ * Format pipeline results as a readable summary.
155
+ */
156
+ export function formatPipelineResults(results) {
157
+ const lines = [];
158
+ let totalMs = 0;
159
+ for (const r of results) {
160
+ const status = r.isError ? '✗' : '✓';
161
+ const duration = r.durationMs > 0 ? ` (${r.durationMs}ms)` : '';
162
+ lines.push(`${status} Step "${r.stepId}"${duration}`);
163
+ // Show truncated output
164
+ const output = r.output.length > 200
165
+ ? r.output.slice(0, 200) + '...'
166
+ : r.output;
167
+ if (output) {
168
+ for (const line of output.split('\n').slice(0, 5)) {
169
+ lines.push(` ${line}`);
170
+ }
171
+ }
172
+ lines.push('');
173
+ totalMs += r.durationMs;
174
+ }
175
+ const passed = results.filter(r => !r.isError).length;
176
+ lines.push(`Pipeline: ${passed}/${results.length} steps passed (${totalMs}ms total)`);
177
+ return lines.join('\n');
178
+ }
179
+ //# sourceMappingURL=PipelineExecutor.js.map
@@ -0,0 +1,119 @@
1
+ /**
2
+ * A2A Protocol — Agent-to-Agent discovery and routing.
3
+ *
4
+ * Enables agents running in separate processes (or machines) to:
5
+ * - Advertise their capabilities via Agent Cards
6
+ * - Discover other agents via a shared registry
7
+ * - Route messages to agents by name or capability
8
+ * - Delegate tasks with typed request/response
9
+ *
10
+ * Registry is file-based (~/.oh/agents/) for same-machine agents.
11
+ * Each running agent writes a card file on startup and removes it on exit.
12
+ *
13
+ * Based on the emerging A2A (Agent-to-Agent) protocol standard.
14
+ */
15
+ export type AgentCard = {
16
+ /** Unique agent instance ID */
17
+ id: string;
18
+ /** Human-readable name */
19
+ name: string;
20
+ /** Agent version */
21
+ version: string;
22
+ /** What this agent can do */
23
+ capabilities: AgentCapability[];
24
+ /** How to reach this agent */
25
+ endpoint: AgentEndpoint;
26
+ /** When this card was published */
27
+ registeredAt: number;
28
+ /** PID of the agent process */
29
+ pid: number;
30
+ /** Provider and model info */
31
+ provider?: string;
32
+ model?: string;
33
+ /** Working directory */
34
+ workingDir?: string;
35
+ };
36
+ export type AgentCapability = {
37
+ /** Capability identifier (e.g., 'code-review', 'test-generation') */
38
+ name: string;
39
+ /** Human description */
40
+ description: string;
41
+ /** Input schema (JSON Schema format) */
42
+ inputSchema?: Record<string, unknown>;
43
+ /** Output schema */
44
+ outputSchema?: Record<string, unknown>;
45
+ };
46
+ export type AgentEndpoint = {
47
+ /** Transport type */
48
+ type: 'http' | 'ipc' | 'stdio';
49
+ /** Address (URL for http, socket path for ipc, pid for stdio) */
50
+ address: string;
51
+ /** Port for HTTP transport */
52
+ port?: number;
53
+ };
54
+ export type A2AMessage = {
55
+ /** Message ID */
56
+ id: string;
57
+ /** Source agent ID */
58
+ from: string;
59
+ /** Target agent ID or capability name */
60
+ to: string;
61
+ /** Message type */
62
+ type: 'task' | 'result' | 'status' | 'cancel' | 'discover';
63
+ /** Payload */
64
+ payload: A2APayload;
65
+ /** Timestamp */
66
+ timestamp: number;
67
+ };
68
+ export type A2APayload = {
69
+ kind: 'task';
70
+ capability: string;
71
+ input: unknown;
72
+ timeout?: number;
73
+ } | {
74
+ kind: 'result';
75
+ taskId: string;
76
+ output: unknown;
77
+ error?: string;
78
+ } | {
79
+ kind: 'status';
80
+ state: 'idle' | 'working' | 'done' | 'error';
81
+ progress?: string;
82
+ } | {
83
+ kind: 'cancel';
84
+ taskId: string;
85
+ reason?: string;
86
+ } | {
87
+ kind: 'discover';
88
+ filter?: {
89
+ capability?: string;
90
+ name?: string;
91
+ };
92
+ };
93
+ /** Publish an agent card to the shared registry */
94
+ export declare function publishCard(card: AgentCard): void;
95
+ /** Remove an agent card from the registry */
96
+ export declare function unpublishCard(agentId: string): void;
97
+ /** Discover all registered agents */
98
+ export declare function discoverAgents(): AgentCard[];
99
+ /** Find agents by capability name */
100
+ export declare function findAgentsByCapability(capabilityName: string): AgentCard[];
101
+ /** Find an agent by name */
102
+ export declare function findAgentByName(name: string): AgentCard | null;
103
+ /**
104
+ * Route a message to an agent.
105
+ * For HTTP endpoints: sends via fetch.
106
+ * For IPC/stdio: writes to the agent's inbox file.
107
+ */
108
+ export declare function routeMessage(message: A2AMessage): Promise<A2AMessage | null>;
109
+ /** Read pending messages from an agent's inbox */
110
+ export declare function readInbox(agentId: string): A2AMessage[];
111
+ /** Generate a unique message ID */
112
+ export declare function generateMessageId(): string;
113
+ /** Create a standard agent card for the current openHarness session */
114
+ export declare function createSessionCard(sessionId: string, opts?: {
115
+ provider?: string;
116
+ model?: string;
117
+ port?: number;
118
+ }): AgentCard;
119
+ //# sourceMappingURL=a2a.d.ts.map