qlogicagent 2.11.5 → 2.11.7

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.
@@ -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;
@@ -90,6 +90,12 @@ export declare class ModelRegistry {
90
90
  addModel(entry: Omit<ModelEntry, "id"> & {
91
91
  id?: string;
92
92
  }): string;
93
+ /**
94
+ * Backfill the reasoning control class for entries that arrived without it
95
+ * (e.g. from the llmrouter cloud catalog), using provider-core as the single
96
+ * source of truth. Entries that already carry reasoningMode are left as-is.
97
+ */
98
+ private withReasoningMode;
93
99
  replaceCatalogModels(entries: ModelEntry[]): void;
94
100
  replaceProviderModels(providerId: string, entries: ModelEntry[]): void;
95
101
  migrateModelIds(aliasToNative: Map<string, string>): void;
@@ -58,5 +58,12 @@ export declare class ProviderCatalogAdapter {
58
58
  getProvider(providerId: string): RuntimeProviderCatalogProvider | undefined;
59
59
  listProviders(): RuntimeProviderCatalogProvider[];
60
60
  listModels(providerId: string): RuntimeProviderCatalogModel[];
61
+ /**
62
+ * Classify a model's reasoning control from provider-core (the single source of
63
+ * truth), for entries sourced outside the builtin catalog (e.g. the llmrouter
64
+ * cloud catalog). Returns undefined when the model isn't a known provider-core
65
+ * model — callers should fall back to the granular default.
66
+ */
67
+ classifyReasoningMode(providerId: string, modelId: string): ReasoningMode | undefined;
61
68
  resolveBest(input: RuntimeProviderVariantResolutionInput): RuntimeProviderVariantResolution | undefined;
62
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qlogicagent",
3
- "version": "2.11.5",
3
+ "version": "2.11.7",
4
4
  "description": "XiaozhiClaw Agent CLI — subprocess architecture (JSON-RPC over stdio)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -86,6 +86,7 @@
86
86
  "node": ">=22.0.0"
87
87
  },
88
88
  "dependencies": {
89
+ "@agentclientprotocol/sdk": "^0.25.0",
89
90
  "@napi-rs/canvas": "0.1.100",
90
91
  "@xiaozhiclaw/pet-core": "0.1.0",
91
92
  "@xiaozhiclaw/provider-core": "0.1.4",
@@ -98,7 +99,6 @@
98
99
  "devDependencies": {
99
100
  "@types/better-sqlite3": "^7.6.13",
100
101
  "@types/node": "^22.15.0",
101
- "@zed-industries/agent-client-protocol": "0.4.5",
102
102
  "esbuild": "^0.28.0",
103
103
  "oxlint": "1.67.0",
104
104
  "tsx": "^4.19.0",
@@ -1,53 +0,0 @@
1
- import type { PortableTool } from "../portable-tool.js";
2
- export declare const NOTIFY_TOOL_NAME: "notify";
3
- export interface NotifyToolParams {
4
- /** Notification message */
5
- message: string;
6
- /** Optional title/subject */
7
- title?: string;
8
- /** Priority level */
9
- priority?: "low" | "normal" | "high";
10
- /** Target channel override (default: user's primary channel) */
11
- channel?: string;
12
- }
13
- export declare const NOTIFY_TOOL_SCHEMA: {
14
- readonly type: "object";
15
- readonly properties: {
16
- readonly message: {
17
- readonly type: "string";
18
- readonly description: "Notification content to send to the user.";
19
- };
20
- readonly title: {
21
- readonly type: "string";
22
- readonly description: "Optional notification title/subject line.";
23
- };
24
- readonly priority: {
25
- readonly type: "string";
26
- readonly enum: readonly ["low", "normal", "high"];
27
- readonly description: "Notification priority (default: normal). High may trigger sound/vibration.";
28
- };
29
- readonly channel: {
30
- readonly type: "string";
31
- readonly description: "Target delivery channel (e.g. 'wechat', 'feishu', 'discord'). Default: user's primary.";
32
- };
33
- };
34
- readonly required: readonly ["message"];
35
- };
36
- export interface NotifyResult {
37
- delivered: boolean;
38
- channel: string;
39
- error?: string;
40
- }
41
- /**
42
- * Host-provided notification delivery backend.
43
- */
44
- export interface NotifyToolDeps {
45
- /** Send notification to user. Host routes to correct platform. */
46
- sendNotification(params: {
47
- message: string;
48
- title?: string;
49
- priority?: string;
50
- channel?: string;
51
- }): Promise<NotifyResult>;
52
- }
53
- export declare function createNotifyTool(deps: NotifyToolDeps): PortableTool<NotifyToolParams>;