qlogicagent 2.11.4 → 2.11.6

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.
@@ -7,7 +7,7 @@
7
7
  import { type AgentRpcError, type AgentRpcRequest } from "../../protocol/wire/index.js";
8
8
  import { AgentProcessManager } from "../../runtime/infra/agent-process.js";
9
9
  import type { AgentConfigStore } from "../../runtime/infra/agent-config-store.js";
10
- import type { AcpDetector } from "../../runtime/infra/acp-detector.js";
10
+ import { type AcpDetector } from "../../runtime/infra/acp-detector.js";
11
11
  export interface AgentsHandlerHost {
12
12
  acpDetector: AcpDetector;
13
13
  soloProcessManager: AgentProcessManager | null;
@@ -17,9 +17,24 @@ export interface AgentsHandlerHost {
17
17
  handleMcpToolCall?(memberId: string, tool: string, args: Record<string, unknown>): Promise<unknown>;
18
18
  log?(message: string): void;
19
19
  sendResponse(id: string | number, result?: unknown, error?: AgentRpcError): void;
20
+ sendNotification?(method: string, params: Record<string, unknown>): void;
20
21
  }
22
+ /** Resolve a pending external-agent approval with the user's decision (delivered by the gateway). */
23
+ export declare function handleAgentsApprovalResponse(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
21
24
  export declare function handleAgentsScan(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
22
25
  export declare function handleAgentsList(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
26
+ /**
27
+ * Curated Discover-Agent catalog (12 agents) merged with live install status, for the
28
+ * discover grid. Status comes from the detector; install carries the official command
29
+ * summary + source for the confirm dialog (no secrets).
30
+ */
31
+ export declare function handleAgentsCatalog(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
32
+ /**
33
+ * One-click install a catalog agent by id. Runs ONLY the official command from the
34
+ * catalog manifest (never a free-form command), streaming progress via the
35
+ * agent.install.progress notification, then refreshes detection. Returns the outcome.
36
+ */
37
+ export declare function handleAgentsInstall(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
23
38
  export declare function handleAgentsConfig(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
24
39
  export declare function handleAgentsSetConfig(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
25
40
  export declare function handleAgentsGetConfig(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
@@ -31,4 +46,6 @@ export declare function handleAgentsProcesses(this: AgentsHandlerHost, msg: Agen
31
46
  export declare function handleAgentsKill(this: AgentsHandlerHost, msg: AgentRpcRequest): void;
32
47
  export declare function handleAgentsGetLog(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
33
48
  export declare function handleAgentsTestConnection(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
49
+ /** Pre-warm an external agent's process (on agent-select) so the first message skips the cold start. */
50
+ export declare function handleAgentsWarm(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
34
51
  export declare function handleAgentsPrompt(this: AgentsHandlerHost, msg: AgentRpcRequest): Promise<void>;
@@ -12,6 +12,8 @@ export interface SandboxPermissionSnapshot {
12
12
  mode: PermissionMode;
13
13
  workdir: string;
14
14
  allowedDirs: string[];
15
+ /** cut7c: current turn is an untrusted community-skill turn → tighten the sandbox. */
16
+ communityTurn: boolean;
15
17
  }
16
18
  /** Wire the OS-sandbox permission source (live rule-engine snapshot), or null to clear. */
17
19
  export declare function setSandboxPermissionSource(source: (() => SandboxPermissionSnapshot | undefined) | null): void;
@@ -30,6 +30,33 @@ export interface AcpBackendConfig {
30
30
  apiKeyEnvVar?: string;
31
31
  /** Environment variable name for the base URL. */
32
32
  baseUrlEnvVar?: string;
33
+ /** Official-logo key for AgentBrandIcon. */
34
+ brandId?: string;
35
+ /** Real-world usage scenario for grouping in the discover UI (not a capability tag). */
36
+ category?: "coding" | "writing" | "design" | "creative" | "research";
37
+ /** How the agent is driven in-app: "acp" via the ACP adapter, "custom" via a dedicated adapter (e.g. Trae). */
38
+ integration?: "acp" | "custom";
39
+ /** Where the injected key comes from: gateway-issued (llmrouter), the user's direct third-party key, or none (OAuth). */
40
+ keySource?: "llmrouter" | "direct" | "none";
41
+ /** Auth model: "key" = login-free key injection; "oauth" = no key bypass, user account required. */
42
+ authMode?: "key" | "oauth";
43
+ /** One-click install spec (official commands per platform). */
44
+ install?: AgentInstallSpec;
45
+ /** Short UI note (e.g. "需 GitHub 账号", "非 ACP,自定义适配器驱动"). */
46
+ note?: string;
47
+ }
48
+ /** Official install commands for the one-click installer (no self-hosted CDN — runs upstream). */
49
+ export interface AgentInstallSpec {
50
+ npm?: string;
51
+ brew?: string;
52
+ curl?: string;
53
+ pip?: string;
54
+ /** Executable name after install (probe target). */
55
+ cliCommand: string;
56
+ /** Probe command (default `<cliCommand> --version`). */
57
+ verifyCmd?: string;
58
+ /** Official docs / source URL (shown in the install confirmation). */
59
+ docsUrl: string;
33
60
  }
34
61
  /**
35
62
  * Extended descriptor for external agents,
@@ -387,6 +387,99 @@ export interface AgentsErrorNotification {
387
387
  error: string;
388
388
  retriesLeft: number;
389
389
  }
390
+ /** One-click install progress (state transitions + streamed log lines + final result). */
391
+ export interface AgentInstallProgressNotification {
392
+ agentId: string;
393
+ event: {
394
+ type: "state";
395
+ state: "installing" | "verifying" | "installed" | "failed";
396
+ } | {
397
+ type: "log";
398
+ stream: "stdout" | "stderr";
399
+ chunk: string;
400
+ } | {
401
+ type: "done";
402
+ ok: boolean;
403
+ error?: string;
404
+ };
405
+ }
406
+ /**
407
+ * Live streaming chunk from an EXTERNAL agent's prompt turn (agents.prompt). Echoes the gateway's
408
+ * turn identity (sessionId/turnId/requestId passed in agents.prompt params) so the gateway can
409
+ * re-emit it on the same turn.delta path the main agent uses — no separate frontend handling.
410
+ */
411
+ export interface AgentsPromptDeltaNotification {
412
+ agentId: string;
413
+ sessionId?: string;
414
+ turnId?: string;
415
+ requestId?: string;
416
+ text: string;
417
+ /** When true this chunk is the agent's reasoning/thinking (→ turn.reasoning_delta), not reply text. */
418
+ reasoning?: boolean;
419
+ }
420
+ /**
421
+ * Live tool-call lifecycle from an EXTERNAL agent's prompt turn — same echo-the-turn-identity
422
+ * mechanism as AgentsPromptDeltaNotification, so the gateway re-emits it on the main
423
+ * turn.tool_call / turn.tool_result path and the UI shows external tools running, no extra handling.
424
+ */
425
+ export interface AgentsPromptToolNotification {
426
+ agentId: string;
427
+ sessionId?: string;
428
+ turnId?: string;
429
+ requestId?: string;
430
+ event: {
431
+ kind: "call";
432
+ callId: string;
433
+ name: string;
434
+ arguments?: string;
435
+ inputSummary?: string;
436
+ } | {
437
+ kind: "result";
438
+ callId: string;
439
+ name: string;
440
+ ok: boolean;
441
+ outputPreview?: string;
442
+ error?: string;
443
+ };
444
+ }
445
+ /**
446
+ * Plan/task update from an EXTERNAL agent's prompt turn (ACP plan steps). The gateway maps the
447
+ * steps onto the main turn.task_updated path so the UI shows the agent's plan as a task list.
448
+ */
449
+ export interface AgentsPromptPlanNotification {
450
+ agentId: string;
451
+ sessionId?: string;
452
+ turnId?: string;
453
+ requestId?: string;
454
+ steps: Array<{
455
+ content?: string;
456
+ title?: string;
457
+ status?: string;
458
+ priority?: string;
459
+ }>;
460
+ }
461
+ /**
462
+ * Interactive permission request from an EXTERNAL agent's prompt turn. Echoes the gateway's turn
463
+ * identity so the gateway re-emits it on the main turn.approval_request path → the UI shows an
464
+ * approval card; the user's decision returns via the agents.approvalResponse RPC.
465
+ */
466
+ export interface AgentsPromptApprovalNotification {
467
+ agentId: string;
468
+ sessionId?: string;
469
+ turnId?: string;
470
+ requestId?: string;
471
+ approvalId: string;
472
+ callId?: string;
473
+ toolName: string;
474
+ arguments?: string;
475
+ message?: string;
476
+ category?: string;
477
+ options?: Array<{
478
+ optionId: string;
479
+ name?: string;
480
+ kind?: string;
481
+ }>;
482
+ }
390
483
  /** Solo agent progress. */
391
484
  export interface SoloProgressNotification {
392
485
  soloId: string;
@@ -761,6 +854,11 @@ export interface NotificationMethodMap {
761
854
  "pong": PongNotification;
762
855
  "agents.status": AgentsStatusNotification;
763
856
  "agents.error": AgentsErrorNotification;
857
+ "agent.install.progress": AgentInstallProgressNotification;
858
+ "agents.prompt.delta": AgentsPromptDeltaNotification;
859
+ "agents.prompt.tool": AgentsPromptToolNotification;
860
+ "agents.prompt.approval": AgentsPromptApprovalNotification;
861
+ "agents.prompt.plan": AgentsPromptPlanNotification;
764
862
  "solo.progress": SoloProgressNotification;
765
863
  "solo.evaluation": SoloEvaluationNotification;
766
864
  "solo.agentDelta": SoloAgentDeltaNotification;
@@ -8,6 +8,11 @@
8
8
  */
9
9
  import type { AcpBackendConfig, AgentDescriptor, AgentConfigStoreData } from "../../protocol/wire/acp-agent-management.js";
10
10
  export declare const ACP_BACKENDS: Record<string, AcpBackendConfig>;
11
+ export declare const AGENT_CATALOG_IDS: readonly ["claude", "codex", "gemini", "copilot", "cursor", "qwen", "kimi", "glm", "opencode", "kiro", "qoder", "auggie", "trae"];
12
+ /** Merged catalog entry (base ACP/Trae config + grid/install metadata) for one id. */
13
+ export declare function getCatalogEntry(id: string): AcpBackendConfig | undefined;
14
+ /** Ordered curated catalog: base ACP config (or Trae base) merged with grid/install metadata. */
15
+ export declare function getAgentCatalog(): AcpBackendConfig[];
11
16
  export declare function resolveWindowsCopilotLoader(): string | null;
12
17
  export declare function normalizeWindowsAcpCommand(cliPath: string, args: string[]): {
13
18
  cliPath: string;
@@ -33,15 +38,38 @@ export declare function normalizeWindowsAcpCommand(cliPath: string, args: string
33
38
  * does not read). Returns undefined when there is nothing to inject, preserving the
34
39
  * prior `env?: undefined` behavior for un-configured backends.
35
40
  */
36
- export declare function buildBackendEnv(backend: AcpBackendConfig | undefined, customConfig: import("../../protocol/wire/acp-agent-management.js").AgentConfig | undefined): Record<string, string> | undefined;
41
+ export declare function buildBackendEnv(backend: AcpBackendConfig | undefined, customConfig: import("../../protocol/wire/acp-agent-management.js").AgentConfig | undefined, autoEnv?: Record<string, string>): Record<string, string> | undefined;
42
+ /** Unified key sources for login-free catalog injection (wired by the runtime). */
43
+ export interface CatalogKeySources {
44
+ /** Gateway-issued inference key (sk-qllm) for keySource:"llmrouter". */
45
+ llmrouterKey?: string;
46
+ /** Base URL an llmrouter-routed agent should call (our gateway endpoint). */
47
+ llmrouterBaseUrl?: string;
48
+ /** Resolve a provider's direct third-party key for keySource:"direct". */
49
+ getDirectKey?: (providerId: string) => string | null | undefined;
50
+ }
51
+ /**
52
+ * Resolve the login-free key env for a catalog agent from the unified key sources.
53
+ * "set key once in our system → all agents" — the user never logs into each CLI.
54
+ * - authMode "oauth" / keySource "none" → {} (no bypass; account required)
55
+ * - keySource "llmrouter" → gateway sk-qllm + gateway baseUrl (agent calls our gateway)
56
+ * - keySource "direct" → the user's provider key (agent calls the provider directly)
57
+ * Only emits env vars the backend actually declares (apiKeyEnvVar / baseUrlEnvVar).
58
+ */
59
+ export declare function resolveCatalogKeyEnv(entry: AcpBackendConfig | undefined, sources: CatalogKeySources): Record<string, string>;
37
60
  export declare class AcpDetector {
38
61
  private cache;
39
62
  private configStore;
63
+ private keySources;
40
64
  /**
41
65
  * Provide the config store data so detector can populate `hasConfig` field
42
66
  * and include user-registered custom agents.
43
67
  */
44
68
  setConfigStore(data: AgentConfigStoreData): void;
69
+ /** Wire the unified key sources for login-free catalog injection. */
70
+ setKeySources(sources: CatalogKeySources): void;
71
+ /** Login-free key env for a catalog agent (used by non-ACP adapters, e.g. Trae). */
72
+ getCatalogKeyEnv(agentId: string): Record<string, string>;
45
73
  /**
46
74
  * Scan for installed ACP agent CLIs.
47
75
  * @param force - Clear cache and re-scan.
@@ -1,6 +1,14 @@
1
1
  import type { AcpHostRequestHandler } from "./acp-protocol-adapter.js";
2
2
  interface HostHandlerOpts {
3
3
  cwd: string;
4
+ /**
5
+ * Optional interactive permission resolver. When provided, session/request_permission is routed
6
+ * here (forwarded to the user) instead of auto-allowing. Returning a chosen optionId responds to
7
+ * the external agent. If omitted, permissions auto-allow (host-trust baseline for sub-agents).
8
+ */
9
+ requestPermission?: (params: Record<string, unknown>) => Promise<{
10
+ optionId: string;
11
+ }>;
4
12
  }
5
13
  /**
6
14
  * Host-side reverse-RPC handler for an EXTERNAL ACP agent (codex/copilot/…).
@@ -12,7 +20,7 @@ interface HostHandlerOpts {
12
20
  * disposeAll().
13
21
  *
14
22
  * Terminal response shapes align with the official ACP schema
15
- * (@zed-industries/agent-client-protocol):
23
+ * (@agentclientprotocol/sdk):
16
24
  * - createTerminal -> { terminalId }
17
25
  * - terminalOutput -> { output, truncated, exitStatus: {exitCode, signal} | null }
18
26
  * - waitForTerminalExit -> { exitCode, signal }
@@ -1,25 +1,25 @@
1
1
  /**
2
- * ACP Protocol Adapter — translates between qlogicagent's internal JSON-RPC
3
- * protocol and the ACP (Agent-Client Protocol) wire format.
2
+ * ACP Protocol Adapter — a thin wrapper over the official @agentclientprotocol/sdk
3
+ * `ClientSideConnection`. qlogicagent is the ACP *client* (host); spawned external
4
+ * agents (codex-acp, claude-agent-acp, openclaw, …) are the ACP *agents*.
4
5
  *
5
- * Aligned with @agentclientprotocol/sdk v0.18.2 (PROTOCOL_VERSION = 1).
6
- * Reference: AionUI ClientSideConnection, @agentclientprotocol/sdk.
6
+ * The SDK owns the wire protocol entirely: JSON-RPC 2.0 / NDJSON framing, request↔response
7
+ * correlation, and the reverse-RPC dispatch. This module owns only the three concerns the SDK
8
+ * leaves to the host:
9
+ * 1. bridging a Node ChildProcess's stdio to the SDK's web-stream transport (ndJsonStream)
10
+ * 2. mapping our loose `AcpHostRequestHandler` onto the typed SDK `Client` interface
11
+ * 3. translating SDK `session/update` notifications into qlogicagent's internal `turn.*` methods
7
12
  *
8
- * ACP = JSON-RPC 2.0 over NDJSON stdio:
9
- * HOST AGENT (requests):
10
- * initialize, session/new, session/prompt, session/close, session/resume
11
- * AGENT → HOST (notifications):
12
- * session/update — carries { sessionId, update: { sessionUpdate: "...", ...payload } }
13
- * AGENT → HOST (requests, host must respond):
14
- * fs/read_text_file, fs/write_text_file, session/request_permission,
15
- * session/elicitation, terminal/create, terminal/output, terminal/release,
16
- * terminal/wait_for_exit, terminal/kill
13
+ * Extension capabilities (clientCapabilities.qlogicagent) are forwarded verbatim — the SDK does
14
+ * not strip outgoing params (validation runs only on the receiving side), so openclaw interop is
15
+ * preserved.
17
16
  */
18
17
  import type { ChildProcess } from "node:child_process";
19
18
  import type { AcpInitializeResult, AcpSessionResult, AcpPromptResponse, McpServerConfig } from "../../protocol/wire/acp-agent-management.js";
20
19
  /**
21
20
  * Handler for agent→host requests (fs, terminal, permission, elicitation).
22
- * Host implementations should implement the methods they support.
21
+ * Host implementations should implement the methods they support. This is intentionally loose
22
+ * (Record-typed) — {@link AcpProtocolAdapter} bridges it onto the SDK's strongly-typed Client.
23
23
  */
24
24
  export interface AcpHostRequestHandler {
25
25
  /** Read a text file from the host filesystem. */
@@ -33,8 +33,10 @@ export interface AcpHostRequestHandler {
33
33
  path: string;
34
34
  content: string;
35
35
  }): Promise<Record<string, unknown> | undefined>;
36
- /** Request permission for a tool call (return selected option). */
37
- requestPermission?(params: Record<string, unknown>): Promise<Record<string, unknown>>;
36
+ /** Request permission for a tool call. Returns the chosen option id (host responsibility). */
37
+ requestPermission?(params: Record<string, unknown>): Promise<{
38
+ optionId: string;
39
+ }>;
38
40
  /** Elicitation request (ask user for input). */
39
41
  elicitation?(params: Record<string, unknown>): Promise<Record<string, unknown>>;
40
42
  /** Create a terminal session. */
@@ -55,73 +57,90 @@ export interface TranslatedNotification {
55
57
  method: string;
56
58
  params: Record<string, unknown>;
57
59
  }
60
+ /** An auth method an agent advertises when it needs the user to log in (subset of ACP shape). */
61
+ export interface AcpAuthMethod {
62
+ id?: string;
63
+ name?: string;
64
+ description?: string;
65
+ }
66
+ /**
67
+ * Thrown when an agent rejects session setup because the user must authenticate first — e.g. kimi
68
+ * returns -32000 "Authentication required" with a `kimi login` authMethod. Carries the agent's
69
+ * advertised authMethods and `retryable = false`, so the spawn layer surfaces it immediately (a
70
+ * missing login never heals by retrying) with an actionable message instead of a generic failure.
71
+ */
72
+ export declare class AcpAuthRequiredError extends Error {
73
+ readonly retryable: false;
74
+ readonly authMethods: AcpAuthMethod[];
75
+ constructor(message: string, authMethods: AcpAuthMethod[]);
76
+ }
77
+ /**
78
+ * Recognise the ACP "authentication required" failure (by code -32000, an authMethods payload, or
79
+ * message text) and convert it to an {@link AcpAuthRequiredError} whose message tells the user how
80
+ * to log in (from the agent's own authMethod description). Returns null for any other error.
81
+ */
82
+ export declare function asAuthRequiredError(err: unknown): AcpAuthRequiredError | null;
58
83
  export declare class AcpProtocolAdapter {
59
- private pendingRpcs;
60
- private rl;
84
+ private conn;
61
85
  private child;
62
86
  private onNotification;
63
87
  private hostHandler;
64
88
  /**
65
89
  * Attach to a child process's stdio for ACP communication.
66
- * Must be called before any RPC methods.
90
+ * Constructs the official ClientSideConnection over the child's stdin/stdout. The connection's
91
+ * read loop starts immediately, so incoming notifications + reverse-RPC are live after this call.
67
92
  *
68
93
  * @param child The spawned ACP agent process
69
- * @param onNotification Callback for agent notifications (session/update, etc.)
94
+ * @param onNotification Callback for agent notifications (raw method + params; caller translates)
70
95
  * @param hostHandler Optional handler for agent→host requests (fs, terminal, permission)
71
96
  */
72
97
  attach(child: ChildProcess, onNotification?: (method: string, params: unknown) => void, hostHandler?: AcpHostRequestHandler): void;
73
98
  /**
74
- * Handle an incoming JSON-RPC request from the agent (reverse-RPC).
75
- * Agent calls host for fs operations, terminal, permissions, etc.
99
+ * Build the SDK Client implementation (agent→host reverse-RPC) from our loose host handler.
100
+ * Re-reads this.hostHandler / this.onNotification on each call so detach() takes effect.
76
101
  */
77
- private handleAgentRequest;
78
- /** Detach from the child process. */
102
+ private buildClient;
103
+ /** Detach from the child process. The SDK read loop ends when the child's stdout closes. */
79
104
  detach(): void;
80
- /** Send a raw JSON-RPC request and await response. */
81
- sendRpc(child: ChildProcess, method: string, params?: unknown, timeoutMs?: number): Promise<unknown>;
105
+ private requireConn;
106
+ /** Race a connection call against a timeout; optionally run a side effect (e.g. cancel) on timeout. */
107
+ private withTimeout;
82
108
  /**
83
- * Perform ACP initialize handshake.
84
- * Sends standard clientInfo + clientCapabilities (aligned with SDK).
109
+ * Perform the ACP initialize handshake. Sends standard clientInfo + clientCapabilities plus the
110
+ * qlogicagent extension capability (forwarded verbatim by the SDK).
85
111
  */
86
- initialize(child: ChildProcess): Promise<AcpInitializeResult>;
112
+ initialize(_child?: ChildProcess): Promise<AcpInitializeResult>;
87
113
  /**
88
- * Create an ACP session.
89
- * Official SDK params: { cwd, mcpServers, additionalDirectories? }
90
- * Optionally injects system prompt as extension field.
114
+ * Create an ACP session (session/new). A non-standard systemPrompt is forwarded under `_meta`
115
+ * (the ACP extension mechanism) so it survives validation on the agent side.
91
116
  */
92
- createSession(child: ChildProcess, options: {
117
+ createSession(_child: ChildProcess, options: {
93
118
  cwd: string;
94
119
  mcpServers?: McpServerConfig[];
95
120
  additionalDirectories?: string[];
96
121
  systemPrompt?: string;
97
122
  }): Promise<AcpSessionResult>;
98
123
  /**
99
- * Send a prompt to a running ACP session.
100
- * Official SDK params: { sessionId, prompt: ContentBlock[], messageId? }
101
- * Official SDK response: { stopReason, usage?, userMessageId? }
102
- *
103
- * Note: Actual content is delivered via session/update notifications DURING
104
- * the prompt execution. The response only signals completion.
124
+ * Send a prompt to a running ACP session (session/prompt). Content streams back via session/update
125
+ * notifications during execution; the response carries only { stopReason, usage? }. On timeout the
126
+ * turn is cancelled (session/cancel) so the agent stops cleanly.
105
127
  */
106
- sendPrompt(child: ChildProcess, sessionId: string, content: string, timeoutMs?: number): Promise<AcpPromptResponse>;
128
+ sendPrompt(_child: ChildProcess, sessionId: string, content: string, timeoutMs?: number): Promise<AcpPromptResponse>;
107
129
  /**
108
- * Resume an existing ACP session (loadSession capability).
109
- * Official SDK method: session/load (same schema as session/new + sessionId).
130
+ * Resume an existing ACP session (session/load — replays history via notifications).
131
+ * Only valid when the agent advertised the loadSession capability.
110
132
  */
111
- resumeSession(child: ChildProcess, sessionId: string, options?: {
133
+ resumeSession(_child: ChildProcess, sessionId: string, options?: {
112
134
  cwd?: string;
113
135
  mcpServers?: McpServerConfig[];
114
136
  }): Promise<AcpSessionResult>;
115
- /**
116
- * Close an ACP session.
117
- */
118
- closeSession(child: ChildProcess, sessionId: string): Promise<void>;
119
137
  /**
120
138
  * Translate an ACP notification into qlogicagent's internal format.
121
139
  * Returns null if the notification is not translatable (handled separately).
122
140
  *
123
- * Standard ACP: agent sends "session/update" notification with
124
- * params: { sessionId, update: { sessionUpdate: "<type>", ...payload } }
141
+ * Standard ACP: agent sends "session/update" with params { sessionId, update: { sessionUpdate, … } }.
142
+ * Used by both the external-agent path (this adapter) and the internal/builtin agent path, so it
143
+ * stays a pure static function decoupled from the connection.
125
144
  */
126
145
  static translateNotification(method: string, params: unknown): TranslatedNotification | null;
127
146
  }
@@ -0,0 +1,57 @@
1
+ import type { AgentInstallSpec } from "../../protocol/wire/acp-agent-management.js";
2
+ export type InstallEvent = {
3
+ type: "state";
4
+ state: "installing" | "verifying" | "installed" | "failed";
5
+ } | {
6
+ type: "log";
7
+ stream: "stdout" | "stderr";
8
+ chunk: string;
9
+ } | {
10
+ type: "done";
11
+ ok: boolean;
12
+ error?: string;
13
+ };
14
+ /** A child process shaped like node's, so tests can inject a fake. */
15
+ export interface InstallProc {
16
+ stdout?: {
17
+ on(ev: "data", cb: (c: Buffer | string) => void): void;
18
+ } | null;
19
+ stderr?: {
20
+ on(ev: "data", cb: (c: Buffer | string) => void): void;
21
+ } | null;
22
+ on(ev: "close", cb: (code: number | null) => void): void;
23
+ on(ev: "error", cb: (err: Error) => void): void;
24
+ }
25
+ export type InstallSpawn = (command: string, shell: boolean) => InstallProc;
26
+ /**
27
+ * Choose the best official install command for the current platform.
28
+ * Preference: brew (macOS only) → npm → pip → curl (POSIX shell only). Returns the raw
29
+ * command + whether it must run through a shell (curl pipelines / shell metachars).
30
+ */
31
+ export declare function pickInstallCommand(install: AgentInstallSpec, platform: NodeJS.Platform): {
32
+ command: string;
33
+ shell: boolean;
34
+ } | null;
35
+ /**
36
+ * Inject a package-registry mirror into an install command, for users behind slow or blocked public
37
+ * registries (e.g. PyPI / npm from mainland China, where the default index frequently times out).
38
+ *
39
+ * Opt-in via `mirror`: "cn" applies curated China mirrors per package manager; off (undefined) by
40
+ * default so other users are unaffected. Only rewrites a command that hasn't already pinned a
41
+ * registry/index, and never touches brew/curl. Returns the command unchanged when not applicable.
42
+ */
43
+ export declare function applyInstallMirror(command: string, mirror: string | undefined): string;
44
+ /** Default spawner: split into argv (or run via shell for pipelines). */
45
+ export declare const defaultInstallSpawn: InstallSpawn;
46
+ /**
47
+ * Run an install spec end-to-end, streaming progress via onEvent. Emits state →
48
+ * log(s) → (verify) → done. Resolves with the final outcome.
49
+ */
50
+ export declare function runAgentInstall(install: AgentInstallSpec, onEvent: (e: InstallEvent) => void, opts?: {
51
+ platform?: NodeJS.Platform;
52
+ spawnImpl?: InstallSpawn;
53
+ mirror?: string;
54
+ }): Promise<{
55
+ ok: boolean;
56
+ error?: string;
57
+ }>;
@@ -101,6 +101,14 @@ export interface AgentProcessCallbacks {
101
101
  onExit?: (memberId: string, code: number | null, signal: string | null) => void;
102
102
  /** Called when MCP bridge server proxies a tool call from an external agent. */
103
103
  onMcpToolCall?: (memberId: string, tool: string, args: Record<string, unknown>) => Promise<unknown>;
104
+ /**
105
+ * Called when an external agent requests permission (ACP session/request_permission). When set,
106
+ * it drives interactive approval (forward to user, await decision) instead of the auto-allow
107
+ * host-trust baseline; resolve with the chosen optionId.
108
+ */
109
+ onPermissionRequest?: (memberId: string, params: Record<string, unknown>) => Promise<{
110
+ optionId: string;
111
+ }>;
104
112
  /** Logger. */
105
113
  log?: {
106
114
  info(msg: string): void;
@@ -243,6 +251,8 @@ export declare class AgentProcessManager {
243
251
  getHandle(memberId: string): AgentProcessHandle | undefined;
244
252
  /** Get all handles. */
245
253
  getAllHandles(): AgentProcessHandle[];
254
+ /** Whether a member's process is still spawned and usable (for pool reuse across turns). */
255
+ isAlive(memberId: string): boolean;
246
256
  /** Get usage tracker for an ACP agent (returns null for internal agents). */
247
257
  getUsageTracker(memberId: string): AcpUsageTracker | null;
248
258
  /**
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Pending interactive-approval registry for EXTERNAL agents.
3
+ *
4
+ * When a spawned external agent (Codex/Claude/…) asks for permission (ACP session/request_permission),
5
+ * the host handler forwards the request to the frontend and parks the ACP response here until the user
6
+ * decides. The gateway delivers the decision via the agents.approvalResponse RPC, which resolves the
7
+ * matching pending promise so the host handler can return the chosen optionId to the external agent.
8
+ *
9
+ * This is the qlogicagent-side mirror of the gateway adapter's pendingPermissionRequests map.
10
+ */
11
+ /**
12
+ * Park an approval until the user decides. Resolves with the chosen optionId, or null on timeout
13
+ * (the caller then applies a safe default). Idempotent per approvalId — a duplicate replaces the old.
14
+ */
15
+ export declare function awaitApprovalDecision(approvalId: string, timeoutMs: number): Promise<string | null>;
16
+ /** Deliver the user's decision. Returns false if no approval was pending for this id (no-op). */
17
+ export declare function resolveApprovalDecision(approvalId: string, optionId: string): boolean;
18
+ /** Cancel all pending approvals (e.g. on shutdown) — resolves them as null so awaiters unblock. */
19
+ export declare function cancelAllApprovals(): void;
20
+ export declare function pendingApprovalCount(): number;
21
+ export interface ApprovalForwardDeps {
22
+ agentId: string;
23
+ /** Current streaming turn identity (read fresh each call, since members are reused across turns). */
24
+ turnContext: () => {
25
+ sessionId?: string;
26
+ turnId?: string;
27
+ requestId?: string;
28
+ } | null;
29
+ /** Emit the forward notification to the gateway (→ turn.approval_request → UI card). */
30
+ sendNotification?: (method: string, params: Record<string, unknown>) => void;
31
+ timeoutMs?: number;
32
+ }
33
+ /**
34
+ * Drive one interactive approval for an external agent: forward the ACP session/request_permission
35
+ * to the user (agents.prompt.approval), await the decision, and return the chosen optionId. Falls
36
+ * back to a reject option if there's no streaming turn context or the user doesn't decide in time.
37
+ * Pure + injectable so the bidirectional loop is unit-testable without a live agent.
38
+ */
39
+ export declare function forwardExternalApproval(params: Record<string, unknown>, deps: ApprovalForwardDeps): Promise<{
40
+ optionId: string;
41
+ }>;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * External-agent process pool (A).
3
+ *
4
+ * Keeps spawned external ACP agents (Codex/Claude/etc.) alive and reuses them across chat turns,
5
+ * instead of the spawn → one turn → kill cycle that paid a full ACP cold-start (several seconds)
6
+ * on every message. Reuse also gives external agents real multi-turn memory (same ACP session).
7
+ *
8
+ * Design (mirrors how VSCode keeps language-server-style agent CLIs resident):
9
+ * - One AgentProcessManager + one spawned member per pool key (agentId + cwd + sessionId).
10
+ * - The first turn spawns + registers; subsequent turns reuse the live member (sendTask only).
11
+ * - A mutable `holder.current` carries the *current* turn's identity so the manager's
12
+ * construction-time onNotification can tag live deltas with the right turn (see agents-handler).
13
+ * - Idle entries are reaped after IDLE_TTL_MS; the pool is bounded to MAX_POOL by LRU eviction.
14
+ * - Busy entries (a turn in flight) are never evicted.
15
+ */
16
+ import type { AgentProcessManager } from "./agent-process.js";
17
+ /** Mutable per-turn context read by the pooled manager's onNotification callback. */
18
+ export interface PooledTurnCtx {
19
+ sessionId?: string;
20
+ turnId?: string;
21
+ requestId?: string;
22
+ deltas: string[];
23
+ streamedAny: boolean;
24
+ }
25
+ export interface PooledAgentEntry {
26
+ manager: AgentProcessManager;
27
+ memberId: string;
28
+ /** Boxed so the manager's onNotification (built before registration) can read the live turn. */
29
+ holder: {
30
+ current: PooledTurnCtx | null;
31
+ };
32
+ lastUsed: number;
33
+ }
34
+ /** Pool key: same agent + workspace + session ⇒ same resident process (and shared memory). */
35
+ export declare function poolKey(agentId: string, cwd: string, sessionId?: string): string;
36
+ /** Return a live pooled entry for the key, or undefined (evicting a dead one if found). */
37
+ export declare function getAlivePooledAgent(key: string): PooledAgentEntry | undefined;
38
+ /** Register a freshly-spawned entry, evicting any stale same-key entry and enforcing MAX_POOL. */
39
+ export declare function registerPooledAgent(key: string, entry: PooledAgentEntry): void;
40
+ /**
41
+ * Re-key a pre-warmed entry (spawned before a session existed, under WARM_SESSION) onto the real
42
+ * session key, so the first turn adopts the warmed process and skips the cold start. Does not
43
+ * dispose — the same live process is moved. Returns undefined if no live warmed entry exists.
44
+ */
45
+ export declare function adoptWarmedAgent(warmKey: string, sessionKey: string): PooledAgentEntry | undefined;
46
+ /** Reserved session token for a process pre-warmed before its real session id is known. */
47
+ export declare const WARM_SESSION = "__warm__";
48
+ /** Drop + dispose a single entry (e.g. after a turn errors and the process is suspect). */
49
+ export declare function evictPooledAgent(key: string): void;
50
+ /** Kill + dispose every pooled agent and stop the reaper. Call on agent shutdown. */
51
+ export declare function disposeAllPooledAgents(): void;
52
+ export declare function pooledAgentCount(): number;
53
+ /** Reap idle (non-busy) entries past the TTL. Exposed for deterministic testing. */
54
+ export declare function reapIdlePooledAgents(now: number): void;