@pwrdrvr/agent-acp 0.5.0 → 0.6.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/dist/index.d.ts +46 -1
- package/dist/index.js +109 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -315,6 +315,11 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
315
315
|
/** Built ONLY when a re-establish actually happens (skipped for a live
|
|
316
316
|
* session), so the host doesn't rebuild the system prompt every turn. */
|
|
317
317
|
buildInstructions?: () => string;
|
|
318
|
+
/** Per-thread MCP servers for THIS session. Lets ONE shared client serve
|
|
319
|
+
* surfaces with different tool sets (library vs sizzle): each surface
|
|
320
|
+
* passes its own servers, so its threads spawn its tools — overriding the
|
|
321
|
+
* client-level default. */
|
|
322
|
+
mcpServers?: AcpMcpServerConfig[];
|
|
318
323
|
}): Promise<void>;
|
|
319
324
|
/**
|
|
320
325
|
* Mint a thread id WITHOUT spawning the agent or opening a session. The
|
|
@@ -326,6 +331,11 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
326
331
|
* turn). The host id is a random UUID since there's no session GUID yet.
|
|
327
332
|
*/
|
|
328
333
|
createDeferredThread(_options?: AgentStartThreadOptions): Promise<AgentBackendStartThreadResult>;
|
|
334
|
+
/** Warm the agent: spawn the process + run the `initialize` handshake WITHOUT
|
|
335
|
+
* opening a session, so the first thread/turn doesn't pay the multi-second
|
|
336
|
+
* spawn. Idempotent. Used by `AcpAgentClientPool` to hold a configured agent
|
|
337
|
+
* ready from app startup. */
|
|
338
|
+
connect(): Promise<void>;
|
|
329
339
|
/** Shared `session/new` + session registration. Mints a new thread id unless
|
|
330
340
|
* `bindThreadId` is given (resume), in which case the new ACP session is
|
|
331
341
|
* bound to that existing host thread id. */
|
|
@@ -356,6 +366,10 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
356
366
|
private handleNotification;
|
|
357
367
|
private applySessionUpdate;
|
|
358
368
|
private handleAcpRequest;
|
|
369
|
+
/** Every MCP server name this client knows about — the client-level default
|
|
370
|
+
* plus the per-thread servers of every live session. Used to auto-approve a
|
|
371
|
+
* configured MCP tool regardless of which session's permission request it is. */
|
|
372
|
+
private knownMcpServerNames;
|
|
359
373
|
private handlePermissionRequest;
|
|
360
374
|
private setRuntimeOption;
|
|
361
375
|
private setRuntimeOptionOnTransport;
|
|
@@ -375,6 +389,37 @@ declare class AcpAgentClient implements AgentBackend {
|
|
|
375
389
|
supportsSessionLoad(): boolean;
|
|
376
390
|
}
|
|
377
391
|
|
|
392
|
+
type AcpAgentClientFactory = () => AcpAgentClient;
|
|
393
|
+
type AcpAgentClientPoolOptions = {
|
|
394
|
+
/** Max time to wait for a client to warm (spawn + `initialize`) before an
|
|
395
|
+
* `acquire` rejects. Default 30s. */
|
|
396
|
+
warmTimeoutMs?: number;
|
|
397
|
+
logger?: Logger;
|
|
398
|
+
};
|
|
399
|
+
declare class AcpAgentClientPool {
|
|
400
|
+
private readonly entries;
|
|
401
|
+
private readonly warmTimeoutMs;
|
|
402
|
+
private readonly logger;
|
|
403
|
+
constructor(options?: AcpAgentClientPoolOptions);
|
|
404
|
+
/**
|
|
405
|
+
* Return the shared, warmed client for `key`, creating it via `factory` if it
|
|
406
|
+
* doesn't exist. Concurrent acquires for the same key share ONE spawn — they
|
|
407
|
+
* receive the same in-flight promise, which resolves when the agent is ready
|
|
408
|
+
* or rejects if warm-up fails / times out. A failed warm-up evicts the entry
|
|
409
|
+
* so a later acquire retries with a fresh client.
|
|
410
|
+
*/
|
|
411
|
+
acquire(key: string, factory: AcpAgentClientFactory): Promise<AcpAgentClient>;
|
|
412
|
+
/** Fire-and-forget warm-up for app startup. Errors are logged, not thrown. */
|
|
413
|
+
warm(key: string, factory: AcpAgentClientFactory): void;
|
|
414
|
+
/** Whether a (warming or warm) entry exists for `key`. */
|
|
415
|
+
has(key: string): boolean;
|
|
416
|
+
/** Close + evict ONE key (e.g. its config changed). Safe if absent. */
|
|
417
|
+
release(key: string): Promise<void>;
|
|
418
|
+
/** Close + evict EVERY entry (app quit). */
|
|
419
|
+
closeAll(): Promise<void>;
|
|
420
|
+
private warmWithTimeout;
|
|
421
|
+
}
|
|
422
|
+
|
|
378
423
|
type AcpOneShotClientOptions = {
|
|
379
424
|
/** Connected (or lazily-connecting) ACP stdio transport for the agent. */
|
|
380
425
|
transport: AcpJsonRpcTransport;
|
|
@@ -706,4 +751,4 @@ declare class AcpRegistryService {
|
|
|
706
751
|
declare function evaluateAcpDistributionPolicy(distribution: AcpRegistryDistribution, allowlist: AcpAllowlistDecision): AcpDistributionPolicy;
|
|
707
752
|
declare function normalizeRegistry(raw: unknown): AcpRegistryAgent[];
|
|
708
753
|
|
|
709
|
-
export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientOptions, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpJsonRpcTransport, type AcpMcpServerConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpStartThreadOptions, type AcpStartTurnOptions, AcpStdioJsonRpcTransport, type AcpStdioJsonRpcTransportOptions, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsSessionLoad, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
|
754
|
+
export { ACP_REGISTRY_URL, AcpAgentAllowlist, type AcpAgentAllowlistRule, AcpAgentClient, type AcpAgentClientFactory, type AcpAgentClientOptions, AcpAgentClientPool, type AcpAgentClientPoolOptions, type AcpAgentInstanceSource, type AcpAgentQuirks, type AcpAgentStrategy, type AcpAllowlistDecision, type AcpApplyContext, type AcpBinaryPlatformDistribution, type AcpDiscoveryProbe, type AcpDistributionKind, type AcpDistributionPolicy, type AcpJsonRpcTransport, type AcpMcpServerConfig, type AcpNormalizeResult, type AcpNormalizerOptions, AcpOneShotClient, type AcpOneShotClientOptions, type AcpOneShotRequest, type AcpOneShotResponse, type AcpPackageDistribution, type AcpPathExecutableLister, type AcpPromptContentBlock, type AcpRegistryAgent, type AcpRegistryAgentWithPolicy, type AcpRegistryAuthDescriptor, type AcpRegistryAuthMethod, type AcpRegistryDistribution, type AcpRegistryFetch, AcpRegistryService, type AcpRegistryServiceOptions, type AcpRegistrySnapshot, type AcpRuntimeAgentCapabilities, type AcpRuntimeAgentInfo, type AcpRuntimeCapabilities, type AcpRuntimeCapabilitiesHandler, type AcpRuntimeCapabilitiesSource, type AcpRuntimeConfigOption, type AcpRuntimeConfigOptionValue, type AcpRuntimeMode, type AcpRuntimeModel, type AcpRuntimeModels, type AcpRuntimeModes, type AcpRuntimeOptionSource, AcpSessionNormalizer, type AcpSessionRuntimeState, type AcpSpawnSpec, type AcpStartThreadOptions, type AcpStartTurnOptions, AcpStdioJsonRpcTransport, type AcpStdioJsonRpcTransportOptions, type AcpTitleHandler, type AcpVerificationStatus, BANNED_ACP_REGISTRY_IDS, BUILT_IN_ACP_STRATEGIES, DEFAULT_ACP_AGENT_ALLOWLIST, type DiscoveredAcpAgent, type DiscoveredAcpAgentGroup, type DiscoveredAcpAgentInstance, type LocalAcpAgentProbe, type LocalAcpDiscoveryOptions, type LocalAcpProbeResult, acpRuntimeSupportsSessionLoad, acpSessionRuntimeStateFromCapabilities, acpSessionRuntimeStateFromUpdate, asRecord, buildAcpBackendId, buildStrategyTable, defaultAcpAgentAllowlist, defaultQuirks, discoverLocalAcpAgentInstances, discoverLocalAcpAgents, evaluateAcpDistributionPolicy, geminiStrategy, grokStrategy, isBannedAcpRegistryId, kimiStrategy, mergeAcpRuntimeState, modeLabelFor, normalizeAcpRuntimeCapabilities, normalizeRegistry, qwenStrategy, readAcpContentText, readBoolean, readContentText, readFirstLocationPath, readFirstString, readKind, readNonEmptyString, readNumber, readPromptText, readString, readToolCallId, readToolContentText, readToolOutput, readUpdateText, strategyByBackendId, strategyById, toolCallFromUpdate, wellKnownAgentBinDirs };
|
package/dist/index.js
CHANGED
|
@@ -912,7 +912,8 @@ var AcpAgentClient = class {
|
|
|
912
912
|
const instructions = options.buildInstructions?.();
|
|
913
913
|
await this.establishSession({
|
|
914
914
|
bindThreadId: options.threadId,
|
|
915
|
-
...instructions !== void 0 ? { instructions } : {}
|
|
915
|
+
...instructions !== void 0 ? { instructions } : {},
|
|
916
|
+
...options.mcpServers !== void 0 ? { mcpServers: options.mcpServers } : {}
|
|
916
917
|
});
|
|
917
918
|
}
|
|
918
919
|
/**
|
|
@@ -930,6 +931,13 @@ var AcpAgentClient = class {
|
|
|
930
931
|
out.modelProvider = this.strategy.id;
|
|
931
932
|
return out;
|
|
932
933
|
}
|
|
934
|
+
/** Warm the agent: spawn the process + run the `initialize` handshake WITHOUT
|
|
935
|
+
* opening a session, so the first thread/turn doesn't pay the multi-second
|
|
936
|
+
* spawn. Idempotent. Used by `AcpAgentClientPool` to hold a configured agent
|
|
937
|
+
* ready from app startup. */
|
|
938
|
+
async connect() {
|
|
939
|
+
await this.initialize();
|
|
940
|
+
}
|
|
933
941
|
/** Shared `session/new` + session registration. Mints a new thread id unless
|
|
934
942
|
* `bindThreadId` is given (resume), in which case the new ACP session is
|
|
935
943
|
* bound to that existing host thread id. */
|
|
@@ -967,7 +975,11 @@ var AcpAgentClient = class {
|
|
|
967
975
|
turnId: void 0,
|
|
968
976
|
runtimeState: void 0,
|
|
969
977
|
pendingTurn: void 0,
|
|
970
|
-
pendingInstructions: options.instructions
|
|
978
|
+
pendingInstructions: options.instructions,
|
|
979
|
+
// Remember the MCP servers attached to THIS session so its tools can be
|
|
980
|
+
// auto-approved even when the (pooled, shared) client has no client-level
|
|
981
|
+
// `mcpServers` default.
|
|
982
|
+
mcpServerNames: (options.mcpServers ?? this.defaultMcpServers).map((server) => server.name)
|
|
971
983
|
};
|
|
972
984
|
this.sessions.set(threadId, session);
|
|
973
985
|
this.threadIdByProtocolId.set(protocolSessionId, threadId);
|
|
@@ -1209,14 +1221,21 @@ var AcpAgentClient = class {
|
|
|
1209
1221
|
}
|
|
1210
1222
|
throw new Error(`Unsupported ACP request: ${method}`);
|
|
1211
1223
|
}
|
|
1224
|
+
/** Every MCP server name this client knows about — the client-level default
|
|
1225
|
+
* plus the per-thread servers of every live session. Used to auto-approve a
|
|
1226
|
+
* configured MCP tool regardless of which session's permission request it is. */
|
|
1227
|
+
knownMcpServerNames() {
|
|
1228
|
+
const names = new Set(this.defaultMcpServers.map((server) => server.name));
|
|
1229
|
+
for (const session of this.sessions.values()) {
|
|
1230
|
+
for (const name of session.mcpServerNames) names.add(name);
|
|
1231
|
+
}
|
|
1232
|
+
return [...names];
|
|
1233
|
+
}
|
|
1212
1234
|
async handlePermissionRequest(params, id) {
|
|
1213
1235
|
const protocolSessionId = readString2(params, "sessionId") ?? readString2(params, "session_id");
|
|
1214
1236
|
const threadId = protocolSessionId ? this.threadIdByProtocolId.get(protocolSessionId) : void 0;
|
|
1215
1237
|
const options = readPermissionOptions(params.options);
|
|
1216
|
-
if (this.autoApproveConfiguredMcpTools && permissionTargetsConfiguredMcpServer(
|
|
1217
|
-
params,
|
|
1218
|
-
this.defaultMcpServers.map((server) => server.name)
|
|
1219
|
-
)) {
|
|
1238
|
+
if (this.autoApproveConfiguredMcpTools && permissionTargetsConfiguredMcpServer(params, this.knownMcpServerNames())) {
|
|
1220
1239
|
return autoApprovePermissionOutcome(options);
|
|
1221
1240
|
}
|
|
1222
1241
|
const handler = this.approvalHandler;
|
|
@@ -1497,9 +1516,90 @@ function readAcpPromptUsage(result) {
|
|
|
1497
1516
|
return usage;
|
|
1498
1517
|
}
|
|
1499
1518
|
|
|
1519
|
+
// src/acp-agent-client-pool.ts
|
|
1520
|
+
import { noopLogger as noopLogger3 } from "@pwrdrvr/agent-core";
|
|
1521
|
+
function errorMessage2(cause) {
|
|
1522
|
+
return cause instanceof Error ? cause.message : String(cause);
|
|
1523
|
+
}
|
|
1524
|
+
var AcpAgentClientPool = class {
|
|
1525
|
+
entries = /* @__PURE__ */ new Map();
|
|
1526
|
+
warmTimeoutMs;
|
|
1527
|
+
logger;
|
|
1528
|
+
constructor(options = {}) {
|
|
1529
|
+
this.warmTimeoutMs = options.warmTimeoutMs ?? 3e4;
|
|
1530
|
+
this.logger = options.logger ?? noopLogger3;
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* Return the shared, warmed client for `key`, creating it via `factory` if it
|
|
1534
|
+
* doesn't exist. Concurrent acquires for the same key share ONE spawn — they
|
|
1535
|
+
* receive the same in-flight promise, which resolves when the agent is ready
|
|
1536
|
+
* or rejects if warm-up fails / times out. A failed warm-up evicts the entry
|
|
1537
|
+
* so a later acquire retries with a fresh client.
|
|
1538
|
+
*/
|
|
1539
|
+
async acquire(key, factory) {
|
|
1540
|
+
const existing = this.entries.get(key);
|
|
1541
|
+
if (existing !== void 0) return existing.ready;
|
|
1542
|
+
const client = factory();
|
|
1543
|
+
const ready = this.warmWithTimeout(key, client);
|
|
1544
|
+
this.entries.set(key, { client, ready });
|
|
1545
|
+
return ready;
|
|
1546
|
+
}
|
|
1547
|
+
/** Fire-and-forget warm-up for app startup. Errors are logged, not thrown. */
|
|
1548
|
+
warm(key, factory) {
|
|
1549
|
+
void this.acquire(key, factory).catch((cause) => {
|
|
1550
|
+
this.logger.warn?.("acp pool: background warm-up failed", {
|
|
1551
|
+
key,
|
|
1552
|
+
message: errorMessage2(cause)
|
|
1553
|
+
});
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
/** Whether a (warming or warm) entry exists for `key`. */
|
|
1557
|
+
has(key) {
|
|
1558
|
+
return this.entries.has(key);
|
|
1559
|
+
}
|
|
1560
|
+
/** Close + evict ONE key (e.g. its config changed). Safe if absent. */
|
|
1561
|
+
async release(key) {
|
|
1562
|
+
const entry = this.entries.get(key);
|
|
1563
|
+
if (entry === void 0) return;
|
|
1564
|
+
this.entries.delete(key);
|
|
1565
|
+
entry.ready.catch(() => void 0);
|
|
1566
|
+
await entry.client.close().catch(() => void 0);
|
|
1567
|
+
}
|
|
1568
|
+
/** Close + evict EVERY entry (app quit). */
|
|
1569
|
+
async closeAll() {
|
|
1570
|
+
const entries = [...this.entries.values()];
|
|
1571
|
+
this.entries.clear();
|
|
1572
|
+
for (const entry of entries) entry.ready.catch(() => void 0);
|
|
1573
|
+
await Promise.allSettled(entries.map((entry) => entry.client.close()));
|
|
1574
|
+
}
|
|
1575
|
+
async warmWithTimeout(key, client) {
|
|
1576
|
+
let timer;
|
|
1577
|
+
try {
|
|
1578
|
+
await Promise.race([
|
|
1579
|
+
client.connect(),
|
|
1580
|
+
new Promise((_, reject) => {
|
|
1581
|
+
timer = setTimeout(
|
|
1582
|
+
() => reject(
|
|
1583
|
+
new Error(`ACP agent "${key}" warm-up timed out after ${this.warmTimeoutMs}ms`)
|
|
1584
|
+
),
|
|
1585
|
+
this.warmTimeoutMs
|
|
1586
|
+
);
|
|
1587
|
+
})
|
|
1588
|
+
]);
|
|
1589
|
+
return client;
|
|
1590
|
+
} catch (cause) {
|
|
1591
|
+
if (this.entries.get(key)?.client === client) this.entries.delete(key);
|
|
1592
|
+
await client.close().catch(() => void 0);
|
|
1593
|
+
throw cause;
|
|
1594
|
+
} finally {
|
|
1595
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
};
|
|
1599
|
+
|
|
1500
1600
|
// src/acp-oneshot-client.ts
|
|
1501
1601
|
import {
|
|
1502
|
-
noopLogger as
|
|
1602
|
+
noopLogger as noopLogger4
|
|
1503
1603
|
} from "@pwrdrvr/agent-core";
|
|
1504
1604
|
var AcpOneShotClient = class {
|
|
1505
1605
|
client;
|
|
@@ -1510,7 +1610,7 @@ var AcpOneShotClient = class {
|
|
|
1510
1610
|
queue = Promise.resolve();
|
|
1511
1611
|
constructor(options) {
|
|
1512
1612
|
this.strategy = options.strategy;
|
|
1513
|
-
const logger = options.logger ??
|
|
1613
|
+
const logger = options.logger ?? noopLogger4;
|
|
1514
1614
|
this.client = new AcpAgentClient({
|
|
1515
1615
|
transport: options.transport,
|
|
1516
1616
|
strategy: options.strategy,
|
|
@@ -2299,6 +2399,7 @@ export {
|
|
|
2299
2399
|
ACP_REGISTRY_URL,
|
|
2300
2400
|
AcpAgentAllowlist,
|
|
2301
2401
|
AcpAgentClient,
|
|
2402
|
+
AcpAgentClientPool,
|
|
2302
2403
|
AcpOneShotClient,
|
|
2303
2404
|
AcpRegistryService,
|
|
2304
2405
|
AcpSessionNormalizer,
|