@aman_asmuei/aman-agent 0.31.0-next.0 → 0.31.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.
- package/README.md +16 -9
- package/dist/delegate-remote.js +27 -7
- package/dist/delegate-remote.js.map +1 -1
- package/dist/delegate.js +27 -7
- package/dist/delegate.js.map +1 -1
- package/dist/index.js +50 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,40 +94,47 @@
|
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
|
97
|
-
## What's New in v0.
|
|
97
|
+
## What's New in v0.31.0
|
|
98
98
|
|
|
99
|
-
> **
|
|
100
|
-
>
|
|
99
|
+
> **Multi-agent (A2A) via MCP server mode.**<br/>
|
|
100
|
+
> Multiple `aman-agent` instances on the same machine can now discover each other via a local registry and delegate tasks to each other over the MCP protocol. No new wire format, no broker, no new runtime daemon.
|
|
101
101
|
|
|
102
102
|
<table>
|
|
103
103
|
<tr>
|
|
104
104
|
<td width="33%" valign="top">
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
**`aman-agent serve`**
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
Run any profile as a local MCP server. Registers in `~/.aman-agent/registry.json` (mode `0600`), exposes `agent.info`, `agent.delegate`, and `agent.send` tools over localhost HTTP with bearer auth.
|
|
109
109
|
|
|
110
110
|
</td>
|
|
111
111
|
<td width="33%" valign="top">
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
**`/delegate @coder <task>`**
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
From any other `aman-agent`, delegate to a running serve instance by handle. The `@`-prefix routes through `delegateRemote` which dials via `StreamableHTTPClientTransport` using the bearer from the registry.
|
|
116
116
|
|
|
117
117
|
</td>
|
|
118
118
|
<td width="33%" valign="top">
|
|
119
119
|
|
|
120
|
-
|
|
120
|
+
**`/agents list|info|ping`**
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
Discover, inspect, and latency-check every agent currently running on this machine. `/agents list` merges local registry entries with remotes (local wins on name collision).
|
|
123
123
|
|
|
124
124
|
</td>
|
|
125
125
|
</tr>
|
|
126
126
|
</table>
|
|
127
127
|
|
|
128
|
+
See the [Multi-agent (A2A)](#multi-agent-a2a) section below for the full walkthrough.
|
|
129
|
+
|
|
128
130
|
<details>
|
|
129
131
|
<summary><strong>Highlights from earlier releases</strong></summary>
|
|
130
132
|
|
|
133
|
+
**v0.30 — Agent hardening**
|
|
134
|
+
- Delegation confirmation prompts (no more silent sub-agents)
|
|
135
|
+
- Persistent background task state surviving crashes
|
|
136
|
+
- Rich `/eval report` with trust, sentiment, energy, burnout risk
|
|
137
|
+
|
|
131
138
|
**v0.29 — Ecosystem parity**
|
|
132
139
|
- Auto-relate memories after extraction (knowledge graph edges)
|
|
133
140
|
- Stale reference cleanup
|
package/dist/delegate-remote.js
CHANGED
|
@@ -128,6 +128,20 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
128
128
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
129
129
|
requestInit: {
|
|
130
130
|
headers: { Authorization: `Bearer ${entry.token}` }
|
|
131
|
+
},
|
|
132
|
+
// Disable SSE reconnection scheduling. On close(), the SDK aborts
|
|
133
|
+
// the controller; without this override, the SSE stream's error
|
|
134
|
+
// handler races to schedule a new _reconnectionTimeout AFTER close()
|
|
135
|
+
// cleared the old one, and the timer (plus its referenced socket)
|
|
136
|
+
// pins Node's event loop until the undici keepalive times out. A
|
|
137
|
+
// delegateRemote caller then can't exit cleanly. maxRetries: 0
|
|
138
|
+
// drops the schedule-on-error path entirely; we're doing a single
|
|
139
|
+
// RPC, not a persistent stream, so reconnection has no value here.
|
|
140
|
+
reconnectionOptions: {
|
|
141
|
+
maxRetries: 0,
|
|
142
|
+
initialReconnectionDelay: 1,
|
|
143
|
+
maxReconnectionDelay: 1,
|
|
144
|
+
reconnectionDelayGrowFactor: 1
|
|
131
145
|
}
|
|
132
146
|
});
|
|
133
147
|
const client = new Client({ name: "aman-agent-a2a-caller", version: "0.1.0" });
|
|
@@ -141,13 +155,19 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
141
155
|
...options.context ? { context: options.context } : {}
|
|
142
156
|
}
|
|
143
157
|
});
|
|
144
|
-
|
|
145
|
-
|
|
158
|
+
let timeoutId;
|
|
159
|
+
const timeout = new Promise((_, rej) => {
|
|
160
|
+
timeoutId = setTimeout(
|
|
146
161
|
() => rej(new Error(`remote delegate timed out after ${timeoutMs}ms`)),
|
|
147
162
|
timeoutMs
|
|
148
|
-
)
|
|
149
|
-
);
|
|
150
|
-
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
let result;
|
|
166
|
+
try {
|
|
167
|
+
result = await Promise.race([call, timeout]);
|
|
168
|
+
} finally {
|
|
169
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
170
|
+
}
|
|
151
171
|
const text = Array.isArray(result.content) ? result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("") : "";
|
|
152
172
|
if (result.isError) {
|
|
153
173
|
return {
|
|
@@ -196,11 +216,11 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
196
216
|
};
|
|
197
217
|
} finally {
|
|
198
218
|
try {
|
|
199
|
-
await
|
|
219
|
+
await transport.terminateSession();
|
|
200
220
|
} catch {
|
|
201
221
|
}
|
|
202
222
|
try {
|
|
203
|
-
await
|
|
223
|
+
await client.close();
|
|
204
224
|
} catch {
|
|
205
225
|
}
|
|
206
226
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/delegate-remote.ts","../src/server/registry.ts","../src/logger.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { findAgent } from \"./server/registry.js\";\nimport type { DelegationResult } from \"./delegate.js\";\nimport { log } from \"./logger.js\";\n\nexport interface RemoteDelegateOptions {\n context?: string;\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n/**\n * Dial another aman-agent running as an A2A server on the same machine\n * and run a task through its `agent.delegate` MCP tool. Returns a\n * DelegationResult matching the shape of the local `delegateTask` so\n * callers can treat local and remote delegation uniformly.\n *\n * Trust model: same user, same machine — bearer comes from the local\n * registry file (mode 0600). See plan docs for the broader discussion.\n */\nexport async function delegateRemote(\n task: string,\n agentName: string,\n options: RemoteDelegateOptions = {},\n): Promise<DelegationResult> {\n const entry = await findAgent(agentName);\n if (!entry) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: `agent not found: ${agentName}`,\n };\n }\n\n const url = new URL(`http://127.0.0.1:${entry.port}/mcp`);\n const transport = new StreamableHTTPClientTransport(url, {\n requestInit: {\n headers: { Authorization: `Bearer ${entry.token}` },\n },\n });\n const client = new Client({ name: \"aman-agent-a2a-caller\", version: \"0.1.0\" });\n\n try {\n await client.connect(transport);\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const call = client.callTool({\n name: \"agent.delegate\",\n arguments: {\n task,\n ...(options.context ? { context: options.context } : {}),\n },\n });\n const timeout = new Promise<never>((_, rej) =>\n setTimeout(\n () => rej(new Error(`remote delegate timed out after ${timeoutMs}ms`)),\n timeoutMs,\n ),\n );\n const result = await Promise.race([call, timeout]);\n\n const text = Array.isArray(result.content)\n ? (result.content as Array<{ type: string; text?: string }>)\n .filter((c) => c.type === \"text\")\n .map((c) => c.text ?? \"\")\n .join(\"\")\n : \"\";\n\n // MCP tool-level errors arrive as { isError: true, content: [{text: \"...\"}] }.\n // Surface them distinctly from JSON.parse failures and from empty responses.\n if ((result as { isError?: boolean }).isError) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: `remote tool error: ${text || \"(no details)\"}`,\n };\n }\n\n const parsed = text ? JSON.parse(text) : { ok: false, error: \"empty response\" };\n\n log.debug(\"delegate-remote\", `@${agentName} ok=${parsed.ok}`);\n\n if (!parsed.ok) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: parsed.error ?? \"unknown remote error\",\n };\n }\n\n return {\n profile: `@${agentName}`,\n task,\n response: parsed.text ?? \"\",\n toolsUsed: parsed.tools_used ?? [],\n turns: parsed.turns ?? 0,\n success: true,\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n const normalized =\n lower.includes(\"401\") || lower.includes(\"unauthor\")\n ? `unauthorized: ${msg}`\n : msg;\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: normalized,\n };\n } finally {\n // Tear down in order: client (releases SDK state) → transport session\n // (tells the server to drop the session) → transport (aborts any pending\n // fetch/SSE keepalive that would otherwise pin the event loop open).\n // All three are best-effort: any throw here is logged and swallowed so\n // a teardown failure never masks a real result from the caller.\n try { await client.close(); } catch { /* best effort */ }\n try { await transport.terminateSession(); } catch { /* best effort */ }\n try { await transport.close(); } catch { /* best effort */ }\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { log } from \"../logger.js\";\n\nexport interface AgentEntry {\n name: string; // unique handle, used as @name\n profile: string; // aman-agent profile this server loaded\n pid: number; // process id for liveness check\n port: number; // 127.0.0.1 port\n token: string; // 32-byte hex bearer\n started_at: number; // epoch ms\n version: string; // package version\n}\n\nexport interface ListOptions {\n prune?: boolean; // write the pruned registry back\n isAlive?: (pid: number) => boolean; // injectable for tests\n}\n\nfunction amanAgentHome(): string {\n return process.env.AMAN_AGENT_HOME || path.join(os.homedir(), \".aman-agent\");\n}\n\nfunction registryPath(): string {\n return path.join(amanAgentHome(), \"registry.json\");\n}\n\nasync function ensureHome(): Promise<void> {\n await fs.mkdir(amanAgentHome(), { recursive: true });\n}\n\nasync function readRaw(): Promise<AgentEntry[]> {\n try {\n const buf = await fs.readFile(registryPath(), \"utf-8\");\n const parsed = JSON.parse(buf);\n return Array.isArray(parsed) ? parsed : [];\n } catch (err: unknown) {\n const code = (err as { code?: string }).code;\n if (code === \"ENOENT\") return [];\n const message = err instanceof Error ? err.message : String(err);\n log.warn(\"registry\", `failed to read registry: ${message}`);\n return [];\n }\n}\n\nasync function writeAtomic(entries: AgentEntry[]): Promise<void> {\n await ensureHome();\n const tmp = registryPath() + \".tmp\";\n await fs.writeFile(tmp, JSON.stringify(entries, null, 2), { mode: 0o600 });\n await fs.rename(tmp, registryPath());\n // Ensure mode even if file already existed (chmod is idempotent).\n try {\n await fs.chmod(registryPath(), 0o600);\n } catch {\n // best effort\n }\n}\n\nfunction defaultIsAlive(pid: number): boolean {\n try {\n // Signal 0 probes existence without sending anything.\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function registerAgent(entry: AgentEntry): Promise<void> {\n const current = await readRaw();\n const filtered = current.filter((e) => e.name !== entry.name);\n if (filtered.length !== current.length) {\n log.warn(\"registry\", `replacing existing entry for name=\"${entry.name}\"`);\n }\n filtered.push(entry);\n await writeAtomic(filtered);\n}\n\nexport async function unregisterAgent(name: string): Promise<void> {\n const current = await readRaw();\n const next = current.filter((e) => e.name !== name);\n if (next.length !== current.length) {\n await writeAtomic(next);\n }\n}\n\nexport async function listAgents(opts: ListOptions = {}): Promise<AgentEntry[]> {\n const isAlive = opts.isAlive ?? defaultIsAlive;\n const raw = await readRaw();\n const alive = raw.filter((e) => isAlive(e.pid));\n if (opts.prune && alive.length !== raw.length) {\n await writeAtomic(alive);\n }\n return alive;\n}\n\nexport async function findAgent(name: string): Promise<AgentEntry | null> {\n const all = await listAgents();\n return all.find((e) => e.name === name) ?? null;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nconst LOG_DIR = path.join(os.homedir(), \".aman-agent\");\nexport const LOG_PATH = path.join(LOG_DIR, \"debug.log\");\nconst MAX_LOG_SIZE = 1_048_576; // 1MB\n\ninterface LogEntry {\n timestamp: string;\n level: \"debug\" | \"warn\" | \"error\";\n module: string;\n message: string;\n data?: string;\n}\n\nfunction ensureDir(): void {\n if (!fs.existsSync(LOG_DIR)) {\n fs.mkdirSync(LOG_DIR, { recursive: true });\n }\n}\n\nfunction maybeRotate(): void {\n try {\n if (!fs.existsSync(LOG_PATH)) return;\n const stat = fs.statSync(LOG_PATH);\n if (stat.size >= MAX_LOG_SIZE) {\n const backupPath = LOG_PATH + \".1\";\n if (fs.existsSync(backupPath)) fs.unlinkSync(backupPath);\n fs.renameSync(LOG_PATH, backupPath);\n }\n } catch {\n // Rotation failure is non-critical\n }\n}\n\nfunction write(level: LogEntry[\"level\"], module: string, message: string, data?: unknown): void {\n try {\n ensureDir();\n maybeRotate();\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n module,\n message,\n };\n if (data !== undefined) {\n entry.data = data instanceof Error ? data.message : String(data);\n }\n fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Logger must never throw\n }\n}\n\nexport const log = {\n debug: (module: string, message: string, data?: unknown) => write(\"debug\", module, message, data),\n warn: (module: string, message: string, data?: unknown) => write(\"warn\", module, message, data),\n error: (module: string, message: string, data?: unknown) => write(\"error\", module, message, data),\n};\n"],"mappings":"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","names":["fs","path","os","path","os","fs"]}
|
|
1
|
+
{"version":3,"sources":["../src/delegate-remote.ts","../src/server/registry.ts","../src/logger.ts"],"sourcesContent":["import { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { StreamableHTTPClientTransport } from \"@modelcontextprotocol/sdk/client/streamableHttp.js\";\nimport { findAgent } from \"./server/registry.js\";\nimport type { DelegationResult } from \"./delegate.js\";\nimport { log } from \"./logger.js\";\n\nexport interface RemoteDelegateOptions {\n context?: string;\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 120_000;\n\n/**\n * Dial another aman-agent running as an A2A server on the same machine\n * and run a task through its `agent.delegate` MCP tool. Returns a\n * DelegationResult matching the shape of the local `delegateTask` so\n * callers can treat local and remote delegation uniformly.\n *\n * Trust model: same user, same machine — bearer comes from the local\n * registry file (mode 0600). See plan docs for the broader discussion.\n */\nexport async function delegateRemote(\n task: string,\n agentName: string,\n options: RemoteDelegateOptions = {},\n): Promise<DelegationResult> {\n const entry = await findAgent(agentName);\n if (!entry) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: `agent not found: ${agentName}`,\n };\n }\n\n const url = new URL(`http://127.0.0.1:${entry.port}/mcp`);\n const transport = new StreamableHTTPClientTransport(url, {\n requestInit: {\n headers: { Authorization: `Bearer ${entry.token}` },\n },\n // Disable SSE reconnection scheduling. On close(), the SDK aborts\n // the controller; without this override, the SSE stream's error\n // handler races to schedule a new _reconnectionTimeout AFTER close()\n // cleared the old one, and the timer (plus its referenced socket)\n // pins Node's event loop until the undici keepalive times out. A\n // delegateRemote caller then can't exit cleanly. maxRetries: 0\n // drops the schedule-on-error path entirely; we're doing a single\n // RPC, not a persistent stream, so reconnection has no value here.\n reconnectionOptions: {\n maxRetries: 0,\n initialReconnectionDelay: 1,\n maxReconnectionDelay: 1,\n reconnectionDelayGrowFactor: 1,\n },\n });\n const client = new Client({ name: \"aman-agent-a2a-caller\", version: \"0.1.0\" });\n\n try {\n await client.connect(transport);\n\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const call = client.callTool({\n name: \"agent.delegate\",\n arguments: {\n task,\n ...(options.context ? { context: options.context } : {}),\n },\n });\n\n // Promise.race picks a winner but does NOT cancel the losing promise's\n // resources. Capturing the timer id lets us clear it after the call\n // resolves — otherwise the setTimeout keeps a Timeout handle alive for\n // the full timeoutMs (120 s default) and pins Node's event loop long\n // after the caller thinks the RPC is done. Equivalent effect to using\n // AbortSignal.timeout() but keeps the existing error message.\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, rej) => {\n timeoutId = setTimeout(\n () => rej(new Error(`remote delegate timed out after ${timeoutMs}ms`)),\n timeoutMs,\n );\n });\n let result;\n try {\n result = await Promise.race([call, timeout]);\n } finally {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n }\n\n const text = Array.isArray(result.content)\n ? (result.content as Array<{ type: string; text?: string }>)\n .filter((c) => c.type === \"text\")\n .map((c) => c.text ?? \"\")\n .join(\"\")\n : \"\";\n\n // MCP tool-level errors arrive as { isError: true, content: [{text: \"...\"}] }.\n // Surface them distinctly from JSON.parse failures and from empty responses.\n if ((result as { isError?: boolean }).isError) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: `remote tool error: ${text || \"(no details)\"}`,\n };\n }\n\n const parsed = text ? JSON.parse(text) : { ok: false, error: \"empty response\" };\n\n log.debug(\"delegate-remote\", `@${agentName} ok=${parsed.ok}`);\n\n if (!parsed.ok) {\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: parsed.error ?? \"unknown remote error\",\n };\n }\n\n return {\n profile: `@${agentName}`,\n task,\n response: parsed.text ?? \"\",\n toolsUsed: parsed.tools_used ?? [],\n turns: parsed.turns ?? 0,\n success: true,\n };\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n const normalized =\n lower.includes(\"401\") || lower.includes(\"unauthor\")\n ? `unauthorized: ${msg}`\n : msg;\n return {\n profile: `@${agentName}`,\n task,\n response: \"\",\n toolsUsed: [],\n turns: 0,\n success: false,\n error: normalized,\n };\n } finally {\n // Teardown order matters:\n // 1. terminateSession() sends an MCP DELETE to drop the server-side\n // session. This needs the transport's abort controller to still\n // be alive, so it MUST run BEFORE client.close() (which aborts\n // the controller). Earlier order threw DOMException[AbortError].\n // 2. client.close() then releases SDK-side state and aborts the\n // transport. Combined with reconnectionOptions: { maxRetries: 0 }\n // on construction, this leaves zero handles pinning the event\n // loop — verified via process.getActiveResourcesInfo() === [].\n // 3. transport.close() is a no-op after client.close() (which\n // transitively closes the transport) but kept for symmetry.\n // All three are best-effort: any throw here is swallowed so a\n // teardown failure never masks a real result from the caller.\n try { await transport.terminateSession(); } catch { /* best effort */ }\n try { await client.close(); } catch { /* best effort */ }\n try { await transport.close(); } catch { /* best effort */ }\n }\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { log } from \"../logger.js\";\n\nexport interface AgentEntry {\n name: string; // unique handle, used as @name\n profile: string; // aman-agent profile this server loaded\n pid: number; // process id for liveness check\n port: number; // 127.0.0.1 port\n token: string; // 32-byte hex bearer\n started_at: number; // epoch ms\n version: string; // package version\n}\n\nexport interface ListOptions {\n prune?: boolean; // write the pruned registry back\n isAlive?: (pid: number) => boolean; // injectable for tests\n}\n\nfunction amanAgentHome(): string {\n return process.env.AMAN_AGENT_HOME || path.join(os.homedir(), \".aman-agent\");\n}\n\nfunction registryPath(): string {\n return path.join(amanAgentHome(), \"registry.json\");\n}\n\nasync function ensureHome(): Promise<void> {\n await fs.mkdir(amanAgentHome(), { recursive: true });\n}\n\nasync function readRaw(): Promise<AgentEntry[]> {\n try {\n const buf = await fs.readFile(registryPath(), \"utf-8\");\n const parsed = JSON.parse(buf);\n return Array.isArray(parsed) ? parsed : [];\n } catch (err: unknown) {\n const code = (err as { code?: string }).code;\n if (code === \"ENOENT\") return [];\n const message = err instanceof Error ? err.message : String(err);\n log.warn(\"registry\", `failed to read registry: ${message}`);\n return [];\n }\n}\n\nasync function writeAtomic(entries: AgentEntry[]): Promise<void> {\n await ensureHome();\n const tmp = registryPath() + \".tmp\";\n await fs.writeFile(tmp, JSON.stringify(entries, null, 2), { mode: 0o600 });\n await fs.rename(tmp, registryPath());\n // Ensure mode even if file already existed (chmod is idempotent).\n try {\n await fs.chmod(registryPath(), 0o600);\n } catch {\n // best effort\n }\n}\n\nfunction defaultIsAlive(pid: number): boolean {\n try {\n // Signal 0 probes existence without sending anything.\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function registerAgent(entry: AgentEntry): Promise<void> {\n const current = await readRaw();\n const filtered = current.filter((e) => e.name !== entry.name);\n if (filtered.length !== current.length) {\n log.warn(\"registry\", `replacing existing entry for name=\"${entry.name}\"`);\n }\n filtered.push(entry);\n await writeAtomic(filtered);\n}\n\nexport async function unregisterAgent(name: string): Promise<void> {\n const current = await readRaw();\n const next = current.filter((e) => e.name !== name);\n if (next.length !== current.length) {\n await writeAtomic(next);\n }\n}\n\nexport async function listAgents(opts: ListOptions = {}): Promise<AgentEntry[]> {\n const isAlive = opts.isAlive ?? defaultIsAlive;\n const raw = await readRaw();\n const alive = raw.filter((e) => isAlive(e.pid));\n if (opts.prune && alive.length !== raw.length) {\n await writeAtomic(alive);\n }\n return alive;\n}\n\nexport async function findAgent(name: string): Promise<AgentEntry | null> {\n const all = await listAgents();\n return all.find((e) => e.name === name) ?? null;\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\n\nconst LOG_DIR = path.join(os.homedir(), \".aman-agent\");\nexport const LOG_PATH = path.join(LOG_DIR, \"debug.log\");\nconst MAX_LOG_SIZE = 1_048_576; // 1MB\n\ninterface LogEntry {\n timestamp: string;\n level: \"debug\" | \"warn\" | \"error\";\n module: string;\n message: string;\n data?: string;\n}\n\nfunction ensureDir(): void {\n if (!fs.existsSync(LOG_DIR)) {\n fs.mkdirSync(LOG_DIR, { recursive: true });\n }\n}\n\nfunction maybeRotate(): void {\n try {\n if (!fs.existsSync(LOG_PATH)) return;\n const stat = fs.statSync(LOG_PATH);\n if (stat.size >= MAX_LOG_SIZE) {\n const backupPath = LOG_PATH + \".1\";\n if (fs.existsSync(backupPath)) fs.unlinkSync(backupPath);\n fs.renameSync(LOG_PATH, backupPath);\n }\n } catch {\n // Rotation failure is non-critical\n }\n}\n\nfunction write(level: LogEntry[\"level\"], module: string, message: string, data?: unknown): void {\n try {\n ensureDir();\n maybeRotate();\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n module,\n message,\n };\n if (data !== undefined) {\n entry.data = data instanceof Error ? data.message : String(data);\n }\n fs.appendFileSync(LOG_PATH, JSON.stringify(entry) + \"\\n\");\n } catch {\n // Logger must never throw\n }\n}\n\nexport const log = {\n debug: (module: string, message: string, data?: unknown) => write(\"debug\", module, message, data),\n warn: (module: string, message: string, data?: unknown) => write(\"warn\", module, message, data),\n error: (module: string, message: string, data?: unknown) => write(\"error\", module, message, data),\n};\n"],"mappings":"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","names":["fs","path","os","path","os","fs"]}
|
package/dist/delegate.js
CHANGED
|
@@ -149,6 +149,20 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
149
149
|
const transport = new StreamableHTTPClientTransport(url, {
|
|
150
150
|
requestInit: {
|
|
151
151
|
headers: { Authorization: `Bearer ${entry.token}` }
|
|
152
|
+
},
|
|
153
|
+
// Disable SSE reconnection scheduling. On close(), the SDK aborts
|
|
154
|
+
// the controller; without this override, the SSE stream's error
|
|
155
|
+
// handler races to schedule a new _reconnectionTimeout AFTER close()
|
|
156
|
+
// cleared the old one, and the timer (plus its referenced socket)
|
|
157
|
+
// pins Node's event loop until the undici keepalive times out. A
|
|
158
|
+
// delegateRemote caller then can't exit cleanly. maxRetries: 0
|
|
159
|
+
// drops the schedule-on-error path entirely; we're doing a single
|
|
160
|
+
// RPC, not a persistent stream, so reconnection has no value here.
|
|
161
|
+
reconnectionOptions: {
|
|
162
|
+
maxRetries: 0,
|
|
163
|
+
initialReconnectionDelay: 1,
|
|
164
|
+
maxReconnectionDelay: 1,
|
|
165
|
+
reconnectionDelayGrowFactor: 1
|
|
152
166
|
}
|
|
153
167
|
});
|
|
154
168
|
const client = new Client({ name: "aman-agent-a2a-caller", version: "0.1.0" });
|
|
@@ -162,13 +176,19 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
162
176
|
...options.context ? { context: options.context } : {}
|
|
163
177
|
}
|
|
164
178
|
});
|
|
165
|
-
|
|
166
|
-
|
|
179
|
+
let timeoutId;
|
|
180
|
+
const timeout = new Promise((_, rej) => {
|
|
181
|
+
timeoutId = setTimeout(
|
|
167
182
|
() => rej(new Error(`remote delegate timed out after ${timeoutMs}ms`)),
|
|
168
183
|
timeoutMs
|
|
169
|
-
)
|
|
170
|
-
);
|
|
171
|
-
|
|
184
|
+
);
|
|
185
|
+
});
|
|
186
|
+
let result;
|
|
187
|
+
try {
|
|
188
|
+
result = await Promise.race([call, timeout]);
|
|
189
|
+
} finally {
|
|
190
|
+
if (timeoutId !== void 0) clearTimeout(timeoutId);
|
|
191
|
+
}
|
|
172
192
|
const text = Array.isArray(result.content) ? result.content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("") : "";
|
|
173
193
|
if (result.isError) {
|
|
174
194
|
return {
|
|
@@ -217,11 +237,11 @@ async function delegateRemote(task, agentName, options = {}) {
|
|
|
217
237
|
};
|
|
218
238
|
} finally {
|
|
219
239
|
try {
|
|
220
|
-
await
|
|
240
|
+
await transport.terminateSession();
|
|
221
241
|
} catch {
|
|
222
242
|
}
|
|
223
243
|
try {
|
|
224
|
-
await
|
|
244
|
+
await client.close();
|
|
225
245
|
} catch {
|
|
226
246
|
}
|
|
227
247
|
try {
|