@qoder-ai/qoder-agent-sdk 1.0.54 → 1.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,25 @@
1
1
  import type { Readable, Writable } from "node:stream";
2
2
  import { type DaemonCommandResultMap, type DaemonCommandParamsMap, type DaemonControlRequestFrame, type DaemonQueryMethod, type DaemonWireSessionMessage, type JsonObject, type JsonValue, type OpenSessionParams, type RenameSessionResult } from "./protocol.js";
3
3
  import type { SdkAuthPayload } from "../types/auth.js";
4
+ import type { PluginValidationReport } from "../types/plugins.js";
4
5
  import { DaemonSessionTransport, type DaemonSessionTransportOptions } from "./daemon-session-transport.js";
5
6
  import type { DiagnosticsSink } from "../core/sdk-diagnostics.js";
6
7
  export declare const SESSION_STORE_FILE_CHECKPOINTING_CAPABILITY = "session_store_file_checkpointing_v1";
7
8
  export declare const SESSION_STORE_FORK_BARRIER_CAPABILITY = "session_store_fork_barrier_v1";
9
+ /**
10
+ * 心跳判死阈值:30s 无任何入站帧即判 daemon 失活。与 CLI 仓
11
+ * DAEMON_KEEP_ALIVE_INTERVAL_MS(5s 发送周期,本值 = 6 周期)镜像;能力常量
12
+ * DAEMON_KEEP_ALIVE_CAPABILITY 同为两仓镜像,靠握手协商门控——组合不匹配时
13
+ * 最多不武装看门狗,不会误判。
14
+ */
15
+ export declare const DAEMON_HEARTBEAT_TIMEOUT_MS = 30000;
16
+ export declare const DAEMON_KEEP_ALIVE_CAPABILITY = "daemon_keep_alive_v1";
17
+ /**
18
+ * daemon 会话 close 改成「关信道即回包、后台排干 teardown」的能力位(CLI 仓镜像)。
19
+ * 会话 initialize 回包声明它时,QueryRunner.close 不再隐式等 memory / evolution
20
+ * flush;旧 CLI 不声明则保持原有等待语义(版本错配保护)。
21
+ */
22
+ export declare const DAEMON_ASYNC_SESSION_TEARDOWN_CAPABILITY = "daemon_async_session_teardown_v1";
8
23
  export interface DaemonConnectionOptions {
9
24
  input: Writable;
10
25
  output: Readable;
@@ -27,6 +42,11 @@ export interface DaemonConnectionOptions {
27
42
  * 只打协议元数据(方法名、id、耗时、字节数),绝不打帧原文、凭据与 prompt 正文。
28
43
  */
29
44
  diagnostics?: DiagnosticsSink;
45
+ /**
46
+ * 心跳看门狗阈值(ms),缺省 DAEMON_HEARTBEAT_TIMEOUT_MS;0 = 关闭(逃生口)。
47
+ * 仅握手协商出 daemon_keep_alive_v1 时武装;单调钟判定,机器睡眠/唤醒不误杀。
48
+ */
49
+ heartbeatTimeoutMs?: number;
30
50
  }
31
51
  export interface DaemonInitializeOptions {
32
52
  /** Advertised in addition to the capability this SDK cannot work without. */
@@ -38,6 +58,11 @@ export interface DaemonInitializeOptions {
38
58
  * without it. Carries a plaintext credential — never log it.
39
59
  */
40
60
  auth?: SdkAuthPayload;
61
+ /**
62
+ * Process-wide proxy applied during the handshake (`daemon_proxy_v1`).
63
+ * An empty string clears a previously configured proxy.
64
+ */
65
+ proxy?: string;
41
66
  }
42
67
  export type DaemonRequestHandler = (request: DaemonControlRequestFrame) => Promise<JsonValue>;
43
68
  export declare class DaemonRpcError extends Error {
@@ -62,13 +87,50 @@ export declare class DaemonRequestError extends Error {
62
87
  details?: JsonObject;
63
88
  });
64
89
  }
90
+ /**
91
+ * A second concurrent Query declared a session id that is already opening or
92
+ * open on this daemon connection. Thrown synchronously from `query()` so the
93
+ * caller sees the conflict before any frame is sent.
94
+ */
95
+ export declare class DaemonSessionAlreadyOpenError extends Error {
96
+ readonly sessionId: string;
97
+ readonly state: "opening" | "open";
98
+ readonly code = "SESSION_ALREADY_OPEN";
99
+ constructor(sessionId: string, state: "opening" | "open");
100
+ }
101
+ /** `close` 进行中的 id 不会拒绝新 Query;凭据里的 ready 在 close 出清后兑现。 */
102
+ export interface DaemonSessionIdClaim {
103
+ readonly sessionId: string;
104
+ readonly ready: Promise<void>;
105
+ }
106
+ /**
107
+ * 心跳看门狗判死:daemon「活着但静默」(事件循环卡死、管道半开、OOM 挂持)时
108
+ * 唯一的判定手段。message 以错误码开头——app 侧按约定取 message 前 120 字符做
109
+ * failureCode,码在开头才可检索。
110
+ */
111
+ export declare class DaemonHeartbeatTimeoutError extends Error {
112
+ readonly code = "DAEMON_HEARTBEAT_TIMEOUT";
113
+ readonly elapsedMs: number;
114
+ constructor(elapsedMs: number);
115
+ }
65
116
  export declare class DaemonConnection {
66
117
  private readonly input;
67
- private readonly requestHandler;
118
+ /**
119
+ * Daemon-level reverse-request handler (the `daemon.auth.*` host-callback bridge).
120
+ * Mutable: `Daemon.auth(payload)` can rebind it when a later payload switches the
121
+ * host-callback arm (jobToken / serviceAccount-token-provider), so the daemon's
122
+ * token fetch for the *new* credentials reaches the right host callback.
123
+ */
124
+ private requestHandler;
68
125
  private readonly pendingRequests;
69
126
  private readonly timedOutRequestIds;
70
127
  private readonly activeInboundRequestIds;
71
128
  private readonly sessions;
129
+ /**
130
+ * 声明 id 的占用表:query() 创建 transport 时同步占位,回包前就能拦住第二个
131
+ * 同 id Query。close 期间到达的 Query 排队等 close 出清,不误杀顺序重开。
132
+ */
133
+ private readonly sessionSlots;
72
134
  private readonly closedSessionIds;
73
135
  /**
74
136
  * 每个 daemon-bound 会话 open 时携带的工作目录(sessionId → cwd)。daemon 已无 cwd
@@ -93,10 +155,46 @@ export declare class DaemonConnection {
93
155
  private readonly failureListeners;
94
156
  private readonly describeUnexpectedClose;
95
157
  private readonly diagnostics;
158
+ /**
159
+ * 心跳看门狗:最近一次入站帧的单调钟时刻(任意入站帧都刷新——有正常会话流量
160
+ * 时天然免检,keep_alive 只是空闲期保底节拍)。单调钟使机器睡眠/唤醒后差值仍小,
161
+ * 不误杀。看门狗定时器与阈值见 armHeartbeatWatchdog。
162
+ */
163
+ private lastInboundAt;
164
+ private heartbeatWatchdog;
165
+ private readonly heartbeatTimeoutMs;
96
166
  constructor(options: DaemonConnectionOptions);
97
167
  initialize(sdkVersion: string, options?: DaemonInitializeOptions): Promise<void>;
98
168
  createSessionTransport(options: DaemonSessionTransportOptions): DaemonSessionTransport;
99
169
  supportsCapability(capability: string): boolean;
170
+ /**
171
+ * Switches the daemon-level (process-wide) proxy, or clears it with
172
+ * `undefined` / `null` / an empty string. Returns the normalized value.
173
+ */
174
+ setProxy(proxy?: string | null): Promise<string | null>;
175
+ /**
176
+ * 同步占用一个声明 id。opening/open 直接抛冲突;closing 则排队,ready 在
177
+ * 前一次 close 出清后兑现。未声明 id(daemon 现 mint)不会撞,调用方不占用。
178
+ */
179
+ claimSessionId(sessionId: string): DaemonSessionIdClaim;
180
+ markSessionOpen(sessionId: string): void;
181
+ markSessionClosing(sessionId: string): void;
182
+ /** open 失败/放弃时出清占位;不向等待者交接(close 才有资格交接)。 */
183
+ releaseSessionClaim(sessionId: string): void;
184
+ private completeSessionClose;
185
+ private advanceSessionSlot;
186
+ /**
187
+ * Statically validates local plugins inside the daemon. Gated on
188
+ * `daemon_validate_plugins_v1` so an older CLI rejects here instead of
189
+ * answering `METHOD_NOT_FOUND`; the caller falls back to the Worker path.
190
+ */
191
+ validatePlugins(paths: readonly string[], configDir?: string): Promise<PluginValidationReport[]>;
192
+ /**
193
+ * Rebinds the daemon-level reverse-request handler. Used by `Daemon.auth(payload)`
194
+ * when the new credentials carry a host-callback arm, so the daemon's token fetch
195
+ * for the overridden credentials is answered by the matching host callback.
196
+ */
197
+ setRequestHandler(handler: DaemonRequestHandler | undefined): void;
100
198
  onFailure(listener: (error: Error) => void): () => void;
101
199
  openSession(params: OpenSessionParams, transport: DaemonSessionTransport): Promise<string>;
102
200
  listSessions(cwd?: string): Promise<import("./protocol.js").DaemonSessionInfo[]>;
@@ -113,6 +211,20 @@ export declare class DaemonConnection {
113
211
  * fails that one query, not the connection.
114
212
  */
115
213
  query<Method extends DaemonQueryMethod>(method: Method, params: DaemonCommandParamsMap[Method]): Promise<DaemonCommandResultMap[Method]>;
214
+ /**
215
+ * Explicitly triggers the daemon's on-demand credential landing
216
+ * (`daemon.authenticate`). The handshake only validated the payload (and auth is
217
+ * optional there), so this is the host-facing way to land credentials up front.
218
+ *
219
+ * `payload` omitted → authenticate with the credentials the daemon already holds.
220
+ * `payload` present → override the held credentials, then authenticate. Idempotent
221
+ * on the CLI side for the no-payload form.
222
+ *
223
+ * Gated on `daemon_queries_v1` (always advertised by a CLI that speaks this
224
+ * protocol); an old CLI that lacks the method rejects here with a clear protocol
225
+ * error instead of a bare METHOD_NOT_FOUND frame.
226
+ */
227
+ authenticate(payload?: SdkAuthPayload): Promise<DaemonCommandResultMap["authenticate"]>;
116
228
  shutdown(timeoutMs?: number): Promise<void>;
117
229
  /**
118
230
  * EOF 落定前问一次进程侧:对端是不是已经退了、为什么退。
@@ -120,6 +232,14 @@ export declare class DaemonConnection {
120
232
  */
121
233
  private resolveUnexpectedClose;
122
234
  fail(error: unknown, notifyListeners?: boolean): void;
235
+ /**
236
+ * 心跳看门狗:每 5s 检查一次距最近入站帧的单调钟差值,超阈值即判 daemon 失活。
237
+ * 判定后走既有 fail() 全量传播(在途请求 reject、在途会话流 error 终止、后续调用
238
+ * 立即 reject、failureListeners 通知)——「对外抛错」不需要新机制。
239
+ * 定时器 unref:看门狗是宿主的判定器,不该替宿主保活进程。
240
+ */
241
+ private armHeartbeatWatchdog;
242
+ private disarmHeartbeatWatchdog;
123
243
  private request;
124
244
  private readOutput;
125
245
  private acceptFrame;
@@ -1,12 +1,14 @@
1
1
  import type { Transport } from '../core/transport.js';
2
2
  import type { StdoutMessage } from '../types/control.js';
3
3
  import type { DaemonWireSessionMessage } from './protocol.js';
4
- import type { DaemonConnection } from './daemon-connection.js';
4
+ import type { DaemonConnection, DaemonSessionIdClaim } from './daemon-connection.js';
5
5
  import type { DiagnosticsSink } from '../core/sdk-diagnostics.js';
6
6
  export type DaemonSessionTransportOptions = {
7
7
  workerArgs: string[];
8
8
  cwd?: string;
9
9
  configDir?: string;
10
+ /** query() 创建时按声明 id 占到的位置;close 进行中时先等 ready。 */
11
+ claim?: DaemonSessionIdClaim;
10
12
  cleanup?: () => void;
11
13
  /**
12
14
  * 由 DaemonConnection.createSessionTransport 注入,调用方不需要传。
@@ -2,11 +2,14 @@ import type { InternalAuthOptions } from '../types/auth.js';
2
2
  import { type Daemon } from './start-daemon.js';
3
3
  export interface InitDaemonOptions {
4
4
  auth: InternalAuthOptions;
5
- /** Idle Worker timeout in milliseconds. Set to 0 to disable reclamation. */
6
- workerIdleTimeoutMs?: number;
7
- cwd?: string;
8
5
  pathToQoderCLIExecutable?: string;
6
+ /** 语义同 `StartDaemonOptions.executable`。 */
7
+ executable?: string;
8
+ /** 语义同 `StartDaemonOptions.executableArgs`。 */
9
+ executableArgs?: string[];
9
10
  proxy?: string;
11
+ /** 语义同 `StartDaemonOptions.vpcEndpoint`。 */
12
+ vpcEndpoint?: string;
10
13
  env?: Record<string, string | undefined>;
11
14
  /** 语义同 `StartDaemonOptions.stderr`:daemon 子进程 stderr 的实时回调。 */
12
15
  stderr?: (data: string) => void;
@@ -5,15 +5,29 @@
5
5
  */
6
6
  import type { BYOKModelValidationInput, BYOKProviderInfo, ByokConfigCheckResult, ByokConfigInfo, ByokConfigReference, CheckByokConfigInput, CreateByokConfigInput, DeleteByokConfigInput, UpdateByokConfigInput } from "../protocol/control.js";
7
7
  import type { AccountInfo, ModelInfo, SessionCreditsUsage, UsageInfo } from "../protocol/common.js";
8
- import type { PluginDetails } from "../types/plugins.js";
8
+ import type { PluginDetails, PluginValidationReport } from "../types/plugins.js";
9
9
  import type { SdkAuthPayload } from "../types/auth.js";
10
10
  export declare const DAEMON_PROTOCOL_VERSION: 2;
11
11
  /**
12
12
  * Reported by the daemon in its initialize result once it accepted an `auth`
13
- * payload, i.e. when the account-level query plane is usable on this
14
- * connection. Absent for a daemon that only manages Sessions.
13
+ * payload, i.e. when the account-level query plane (and the explicit
14
+ * `daemon.authenticate` trigger) is available on this connection. Acceptance is
15
+ * shape validation only — the credentials land on demand, so queries before the
16
+ * first landing may answer `AUTH_REQUIRED`. Absent for a daemon that only
17
+ * manages Sessions.
15
18
  */
16
19
  export declare const DAEMON_QUERIES_CAPABILITY = "daemon_queries_v1";
20
+ /**
21
+ * The daemon can statically validate local plugins in-process
22
+ * (`daemon.validate_plugins`), so the SDK can skip spawning a Worker for
23
+ * plugin validation. Absent on older CLIs; callers must fall back.
24
+ */
25
+ export declare const DAEMON_VALIDATE_PLUGINS_CAPABILITY = "daemon_validate_plugins_v1";
26
+ /**
27
+ * The daemon can apply a process-wide proxy during the handshake (`initialize`)
28
+ * and switch it later via `daemon.setproxy`.
29
+ */
30
+ export declare const DAEMON_PROXY_CAPABILITY = "daemon_proxy_v1";
17
31
  export type JsonPrimitive = string | number | boolean | null;
18
32
  export type JsonValue = JsonPrimitive | JsonValue[] | {
19
33
  [key: string]: JsonValue;
@@ -83,6 +97,11 @@ export type InitializeDaemonParams = {
83
97
  * message.
84
98
  */
85
99
  auth?: SdkAuthPayload;
100
+ /**
101
+ * Process-wide proxy applied during the handshake (`daemon_proxy_v1`). An
102
+ * empty string clears a previously configured proxy.
103
+ */
104
+ proxy?: string;
86
105
  };
87
106
  export type InitializeDaemonResult = {
88
107
  daemon_protocol_version: typeof DAEMON_PROTOCOL_VERSION;
@@ -138,6 +157,31 @@ export type DeleteSessionParams = {
138
157
  cwd?: string;
139
158
  };
140
159
  export type ShutdownParams = Record<string, never>;
160
+ /**
161
+ * Explicit auth trigger (`daemon.authenticate`, surfaced as `Daemon.auth(payload?)`).
162
+ * The initialize handshake only validates the credential payload (and auth is now
163
+ * optional there); the CLI lands it on demand — the first auth-needing consumer
164
+ * (this method, an account-level query, or a session's inference readiness) starts
165
+ * the login.
166
+ *
167
+ * `auth` is optional:
168
+ * - omitted → authenticate with the credentials the daemon already holds (from
169
+ * `startDaemon`); rejected with `AUTH_PAYLOAD_REQUIRED` if it holds none.
170
+ * - present → **override** the held credentials with this payload, then authenticate
171
+ * (supports supplying credentials to a daemon started without auth, and switching
172
+ * accounts on an already-authenticated daemon).
173
+ */
174
+ export type DaemonAuthenticateParams = {
175
+ auth?: SdkAuthPayload;
176
+ };
177
+ export type DaemonAuthenticateResult = {
178
+ /**
179
+ * False is a non-fatal "no credential" outcome (e.g. the read-only borrow of
180
+ * the local qodercli login found nothing). Rejected credentials arrive as an
181
+ * error frame instead, never as `authenticated: false`.
182
+ */
183
+ authenticated: boolean;
184
+ };
141
185
  /**
142
186
  * Account-level query plane (`daemon_queries_v1`).
143
187
  *
@@ -202,6 +246,24 @@ export type DaemonListPluginsParams = Record<string, never>;
202
246
  export type DaemonListPluginsResult = {
203
247
  plugins: PluginDetails[];
204
248
  };
249
+ /**
250
+ * Static, session-less plugin validation. `config_dir` is the base directory
251
+ * for relative paths, mirroring the Worker path's `QODER_CONFIG_DIR` context.
252
+ */
253
+ export type DaemonValidatePluginsParams = {
254
+ paths: string[];
255
+ config_dir?: string;
256
+ };
257
+ export type DaemonValidatePluginsResult = {
258
+ reports: PluginValidationReport[];
259
+ };
260
+ export type DaemonSetProxyParams = {
261
+ /** `null` clears the daemon-level proxy. */
262
+ proxy: string | null;
263
+ };
264
+ export type DaemonSetProxyResult = {
265
+ proxy: string | null;
266
+ };
205
267
  export type DaemonGetUsageInfoParams = Record<string, never>;
206
268
  export type DaemonGetUsageInfoResult = {
207
269
  usage: UsageInfo | null;
@@ -213,7 +275,7 @@ export type DaemonAccountInfoParams = Record<string, never>;
213
275
  export type DaemonAccountInfoResult = {
214
276
  account: AccountInfo;
215
277
  };
216
- export type DaemonCommandMethod = "initialize" | "open_session" | "list_sessions" | "rename_session" | "close_session" | "delete_session" | "shutdown" | DaemonQueryMethod;
278
+ export type DaemonCommandMethod = "initialize" | "open_session" | "list_sessions" | "rename_session" | "close_session" | "delete_session" | "shutdown" | "authenticate" | "validate_plugins" | "setproxy" | DaemonQueryMethod;
217
279
  export interface DaemonCommandParamsMap {
218
280
  initialize: InitializeDaemonParams;
219
281
  open_session: OpenSessionParams;
@@ -222,6 +284,7 @@ export interface DaemonCommandParamsMap {
222
284
  close_session: CloseSessionParams;
223
285
  delete_session: DeleteSessionParams;
224
286
  shutdown: ShutdownParams;
287
+ authenticate: DaemonAuthenticateParams;
225
288
  get_models: DaemonGetModelsParams;
226
289
  get_byok_config: DaemonGetByokConfigParams;
227
290
  validate_byok_model: DaemonValidateByokModelParams;
@@ -231,10 +294,12 @@ export interface DaemonCommandParamsMap {
231
294
  delete_byok_config: DaemonDeleteByokConfigParams;
232
295
  check_byok_config: DaemonCheckByokConfigParams;
233
296
  list_plugins: DaemonListPluginsParams;
297
+ validate_plugins: DaemonValidatePluginsParams;
298
+ setproxy: DaemonSetProxyParams;
234
299
  get_usage_info: DaemonGetUsageInfoParams;
235
300
  account_info: DaemonAccountInfoParams;
236
301
  }
237
- export type DaemonCommandRequest = DaemonControlRequestFrame<"initialize", InitializeDaemonParams> | DaemonControlRequestFrame<"open_session", OpenSessionParams> | DaemonControlRequestFrame<"list_sessions", ListSessionsParams> | DaemonControlRequestFrame<"rename_session", RenameSessionParams> | DaemonControlRequestFrame<"close_session", CloseSessionParams> | DaemonControlRequestFrame<"delete_session", DeleteSessionParams> | DaemonControlRequestFrame<"shutdown", ShutdownParams> | DaemonControlRequestFrame<"get_models", DaemonGetModelsParams> | DaemonControlRequestFrame<"get_byok_config", DaemonGetByokConfigParams> | DaemonControlRequestFrame<"validate_byok_model", DaemonValidateByokModelParams> | DaemonControlRequestFrame<"list_byok_configs", DaemonListByokConfigsParams> | DaemonControlRequestFrame<"create_byok_config", DaemonCreateByokConfigParams> | DaemonControlRequestFrame<"update_byok_config", DaemonUpdateByokConfigParams> | DaemonControlRequestFrame<"delete_byok_config", DaemonDeleteByokConfigParams> | DaemonControlRequestFrame<"check_byok_config", DaemonCheckByokConfigParams> | DaemonControlRequestFrame<"list_plugins", DaemonListPluginsParams> | DaemonControlRequestFrame<"get_usage_info", DaemonGetUsageInfoParams> | DaemonControlRequestFrame<"account_info", DaemonAccountInfoParams>;
302
+ export type DaemonCommandRequest = DaemonControlRequestFrame<"initialize", InitializeDaemonParams> | DaemonControlRequestFrame<"open_session", OpenSessionParams> | DaemonControlRequestFrame<"list_sessions", ListSessionsParams> | DaemonControlRequestFrame<"rename_session", RenameSessionParams> | DaemonControlRequestFrame<"close_session", CloseSessionParams> | DaemonControlRequestFrame<"delete_session", DeleteSessionParams> | DaemonControlRequestFrame<"shutdown", ShutdownParams> | DaemonControlRequestFrame<"authenticate", DaemonAuthenticateParams> | DaemonControlRequestFrame<"get_models", DaemonGetModelsParams> | DaemonControlRequestFrame<"get_byok_config", DaemonGetByokConfigParams> | DaemonControlRequestFrame<"validate_byok_model", DaemonValidateByokModelParams> | DaemonControlRequestFrame<"list_byok_configs", DaemonListByokConfigsParams> | DaemonControlRequestFrame<"create_byok_config", DaemonCreateByokConfigParams> | DaemonControlRequestFrame<"update_byok_config", DaemonUpdateByokConfigParams> | DaemonControlRequestFrame<"delete_byok_config", DaemonDeleteByokConfigParams> | DaemonControlRequestFrame<"check_byok_config", DaemonCheckByokConfigParams> | DaemonControlRequestFrame<"list_plugins", DaemonListPluginsParams> | DaemonControlRequestFrame<"validate_plugins", DaemonValidatePluginsParams> | DaemonControlRequestFrame<"setproxy", DaemonSetProxyParams> | DaemonControlRequestFrame<"get_usage_info", DaemonGetUsageInfoParams> | DaemonControlRequestFrame<"account_info", DaemonAccountInfoParams>;
238
303
  export interface DaemonCommandResultMap {
239
304
  initialize: InitializeDaemonResult;
240
305
  open_session: OpenSessionResult;
@@ -243,6 +308,7 @@ export interface DaemonCommandResultMap {
243
308
  close_session: null;
244
309
  delete_session: null;
245
310
  shutdown: null;
311
+ authenticate: DaemonAuthenticateResult;
246
312
  get_models: DaemonGetModelsResult;
247
313
  get_byok_config: DaemonGetByokConfigResult;
248
314
  validate_byok_model: DaemonValidateByokModelResult;
@@ -252,6 +318,8 @@ export interface DaemonCommandResultMap {
252
318
  delete_byok_config: null;
253
319
  check_byok_config: DaemonCheckByokConfigResult;
254
320
  list_plugins: DaemonListPluginsResult;
321
+ validate_plugins: DaemonValidatePluginsResult;
322
+ setproxy: DaemonSetProxyResult;
255
323
  get_usage_info: DaemonGetUsageInfoResult;
256
324
  account_info: DaemonAccountInfoResult;
257
325
  }
@@ -4,7 +4,7 @@ import type { QueryTransportProvider } from '../core/transport.js';
4
4
  import type { DaemonGetModelsParams, DaemonValidateByokModelParams } from './protocol.js';
5
5
  import type { BYOKProviderInfo, ByokConfigCheckResult, ByokConfigInfo, ByokConfigReference, CheckByokConfigInput, CreateByokConfigInput, DeleteByokConfigInput, UpdateByokConfigInput } from '../protocol/control.js';
6
6
  import type { AccountInfo, ModelInfo, UsageInfo } from '../protocol/common.js';
7
- import type { PluginDetails } from '../types/plugins.js';
7
+ import type { PluginDetails, PluginValidationReport } from '../types/plugins.js';
8
8
  export type DaemonState = 'ready' | 'stopping' | 'stopped' | 'failed';
9
9
  export type SessionRuntimeState = 'pending' | 'ready' | 'inactive' | 'failed';
10
10
  export interface DaemonSessionInfo {
@@ -16,21 +16,52 @@ export interface DaemonSessionInfo {
16
16
  updatedAt: string;
17
17
  }
18
18
  export interface StartDaemonOptions {
19
- auth: InternalAuthOptions;
20
- /** Idle Worker timeout in milliseconds. Set to 0 to disable reclamation. */
21
- workerIdleTimeoutMs?: number;
19
+ /**
20
+ * Credentials for the daemon's own account plane and its in-process Sessions.
21
+ *
22
+ * **Optional.** When omitted the daemon starts in the "no credentials" state:
23
+ * the handshake does no identity work, and auth-needing calls answer
24
+ * `AUTH_PAYLOAD_REQUIRED` until you supply credentials via `daemon.auth(payload)`.
25
+ * When provided it is validated (shape) at the handshake and landed lazily on the
26
+ * first auth-needing consumer — never blocking `startDaemon` on the login network.
27
+ */
28
+ auth?: InternalAuthOptions;
22
29
  /**
23
30
  * Budget for the `daemon.initialize` round trip; defaults to 30s.
24
31
  *
25
- * The CLI lands the in-band credentials while handling initialize — token
26
- * exchange, user info, a possible host callback for the first token, and the
27
- * account tier prefill — so the wall time follows the host's network and the
28
- * CLI's own startup cost. Raise this when either is slow; initialize only
29
- * resolves once that work is complete.
32
+ * The handshake only validates the credential payload (when `auth` is given at
33
+ * all — it is optional) and installs the session plane; no auth network happens
34
+ * inside it. The CLI lands the credentials **on demand**: the first auth-needing
35
+ * call (an account-level query, a session's first inference, or an explicit
36
+ * `daemon.auth()`) starts the login. This budget therefore covers CLI startup and
37
+ * the handshake I/O only. Note that host-callback credential requests
38
+ * (`daemon.auth.fetch_job_token` / `fetch_service_account_token`) can arrive
39
+ * at any later point, whenever something first triggers the landing.
30
40
  */
31
41
  initializeTimeoutMs?: number;
32
42
  pathToQoderCLIExecutable?: string;
43
+ /**
44
+ * 运行 CLI 脚本入口的解释器(node/bun/deno 等),语义与 `query()` 的
45
+ * `Options.executable` 一致:只在解析出的 CLI 入口是脚本文件(.js/.mjs/.ts/...)
46
+ * 时生效,原生二进制忽略它。缺省用宿主 SDK 的 `process.execPath`——原生平台
47
+ * 把随包 Node 传在这里(`executable: runtime.interpreterPath` 配
48
+ * `pathToQoderCLIExecutable: runtime.executablePath`)。
49
+ */
50
+ executable?: string;
51
+ /** 解释器参数,插在 CLI 脚本路径之前;语义同 `Options.executableArgs`。 */
52
+ executableArgs?: string[];
53
+ /**
54
+ * daemon 进程级代理:握手时随 `daemon.initialize` 写入,作用于该 daemon 的
55
+ * 账号面与所有会话(`daemon_proxy_v1`)。运行期可用 `daemon.setProxy()` 切换。
56
+ */
33
57
  proxy?: string;
58
+ /**
59
+ * VPC private-deployment endpoint for the daemon process (CN deployments only).
60
+ * Written to `QODERCN_VPC_ENDPOINT` / `QODER_VPC_ENDPOINT` before spawn; the CLI
61
+ * reads that environment variable ahead of `settings.json`'s vpcInstanceName.
62
+ * Sessions share the daemon process, so this is not a per-Query option.
63
+ */
64
+ vpcEndpoint?: string;
34
65
  env?: Record<string, string | undefined>;
35
66
  /**
36
67
  * daemon 子进程 stderr 的实时回调。
@@ -53,6 +84,11 @@ export interface StartDaemonOptions {
53
84
  }
54
85
  export interface Daemon {
55
86
  readonly state: DaemonState;
87
+ /**
88
+ * 订阅连接/进程级失败(与 DaemonImpl 内部 acceptDaemonFailure 同源)。全局槽位靠它
89
+ * 感知「活跃 daemon 已死」并触发重建;普通调用方不需要订阅。返回取消订阅句柄。
90
+ */
91
+ onFailure(listener: (error: Error) => void): () => void;
56
92
  /**
57
93
  * 列出会话。`cwd` 给定时只列该工作目录名下的会话;缺省时返回所有目录的全部会话
58
94
  * (daemon 无 cwd 概念,「不传目录」即「不过滤」,由 CLI 侧全量扫盘)。
@@ -66,10 +102,51 @@ export interface Daemon {
66
102
  /** 删除会话。`cwd` 语义同 renameSession。 */
67
103
  deleteSession(localSessionId: string, cwd?: string): Promise<void>;
68
104
  /**
69
- * Account-level queries, answered from the credentials the daemon was
70
- * started with — never from a Session's. They require a daemon that accepted
71
- * `StartDaemonOptions.auth` and reported `daemon_queries_v1`; otherwise they
72
- * reject.
105
+ * Set or clear the daemon-level (process-wide) proxy for every Session.
106
+ * `undefined` / `null` / an empty string clears it. Requires
107
+ * `daemon_proxy_v1`; resolves with the normalized proxy (or null).
108
+ */
109
+ setProxy(proxy?: string | null): Promise<string | null>;
110
+ /**
111
+ * The daemon-level proxy currently in effect (`null` when unset/cleared, or
112
+ * when the CLI does not support `daemon_proxy_v1`).
113
+ */
114
+ getProxy(): string | null;
115
+ /**
116
+ * Explicitly lands credentials and reports whether the daemon is authenticated.
117
+ *
118
+ * The `daemon.initialize` handshake only *validates* the credential payload (and
119
+ * `StartDaemonOptions.auth` is optional); the CLI lands it **on demand** — the
120
+ * first auth-needing call (an account-level query, or a session's first inference)
121
+ * triggers the login implicitly.
122
+ *
123
+ * - `auth()` (no argument): authenticate with the credentials the daemon already
124
+ * holds (from `startDaemon`). Rejects with `AUTH_PAYLOAD_REQUIRED` if it holds
125
+ * none. Idempotent: concurrent and repeated calls share one landing.
126
+ * - `auth(payload)`: **override** the held credentials with `payload`, then
127
+ * authenticate. Use it to supply credentials to a daemon started without auth,
128
+ * or to switch accounts on an already-authenticated daemon. A host-callback arm
129
+ * (`jobToken` / `serviceAccount` token provider) rebinds the reverse-request
130
+ * bridge so the daemon's token fetch reaches the new callback. The override also
131
+ * updates the credentials a later `query({ daemon })` reuses.
132
+ *
133
+ * Resolves `false` for a non-fatal "no credential" outcome (e.g. the read-only
134
+ * borrow of the local qodercli login found nothing). Rejected credentials reject
135
+ * this promise with a `DaemonRpcError` carrying the CLI's error code — never a
136
+ * silent `false`.
137
+ */
138
+ auth(payload?: InternalAuthOptions): Promise<boolean>;
139
+ /**
140
+ * Account-level queries, answered from the daemon's own credentials — never
141
+ * from a Session's. `daemon_queries_v1` is always negotiated, so these are
142
+ * always callable; each answer depends on the daemon's auth state:
143
+ * authenticated → the data; pending (holds credentials, not yet landed) →
144
+ * triggers the landing then answers; no credentials → `AUTH_PAYLOAD_REQUIRED`
145
+ * (call `auth(payload)` first).
146
+ *
147
+ * Auth-needing queries trigger the credential landing on demand, so the first
148
+ * one may be as slow as a login; `getModels`' cache path answers without
149
+ * credentials and does not trigger it.
73
150
  *
74
151
  * Unlike the per-`Query` equivalents these do not swallow failures into
75
152
  * `null`: capability is already negotiated here, so a rejection is a real
@@ -84,6 +161,19 @@ export interface Daemon {
84
161
  deleteByokConfig(target: DeleteByokConfigInput): Promise<void>;
85
162
  checkByokConfig(input: CheckByokConfigInput): Promise<ByokConfigCheckResult>;
86
163
  listPlugins(): Promise<PluginDetails[]>;
164
+ /**
165
+ * Whether the daemon advertised `capability` in the initialize result.
166
+ * Capabilities are negotiated once per connection and never change.
167
+ */
168
+ supportsCapability(capability: string): boolean;
169
+ /**
170
+ * Statically validates local plugins in-process (no Session, no auth).
171
+ * Requires `daemon_validate_plugins_v1`; older daemons reject with a
172
+ * `DaemonProtocolError` and callers should fall back to the Worker path.
173
+ */
174
+ validatePlugins(paths: readonly string[], options?: {
175
+ configDir?: string;
176
+ }): Promise<PluginValidationReport[]>;
87
177
  getUsageInfo(): Promise<UsageInfo | null>;
88
178
  accountInfo(): Promise<AccountInfo>;
89
179
  stop(): Promise<void>;
@@ -95,7 +185,19 @@ interface InternalDaemonRuntime {
95
185
  cliPath?: string;
96
186
  env?: Record<string, string | undefined>;
97
187
  };
98
- readonly auth: InternalAuthOptions;
188
+ /**
189
+ * Credentials a daemon-bound `query()` reuses for its Session. Optional: a daemon
190
+ * started without auth has none until `daemon.auth(payload)` supplies them — that
191
+ * call updates this field so a later `query({ daemon })` sees the credentials
192
+ * instead of tripping query()'s "auth is required" client-side gate. Mutable for
193
+ * exactly that reason.
194
+ */
195
+ auth?: InternalAuthOptions;
196
+ /**
197
+ * The daemon-level proxy currently applied by the CLI (`null` when unset or
198
+ * when the CLI ignored the handshake proxy). Updated by `setProxy`.
199
+ */
200
+ proxy: string | null;
99
201
  readonly transportProvider: QueryTransportProvider<ProcessTransportOptions>;
100
202
  }
101
203
  export declare function getDaemonRuntime(daemon: Daemon): InternalDaemonRuntime | undefined;
package/dist/index.d.ts CHANGED
@@ -59,7 +59,8 @@ export { initDaemon, shutdownDaemon } from './daemon/global-daemon.js';
59
59
  export type { DaemonFallbackReason, DaemonInitResult, InitDaemonOptions, } from './daemon/global-daemon.js';
60
60
  export { startDaemon } from './daemon/start-daemon.js';
61
61
  export type { Daemon, DaemonState, DaemonSessionInfo, SessionRuntimeState, StartDaemonOptions, } from './daemon/start-daemon.js';
62
- export type { DaemonGetModelsParams, DaemonValidateByokModelParams, } from './daemon/protocol.js';
62
+ export type { DaemonGetModelsParams, DaemonValidateByokModelParams, DaemonValidatePluginsParams, DaemonSetProxyParams, } from './daemon/protocol.js';
63
+ export { DAEMON_PROXY_CAPABILITY, DAEMON_VALIDATE_PLUGINS_CAPABILITY, } from './daemon/protocol.js';
63
64
  export { DEFAULT_RUNTIME_TRANSPORT, } from './core/default-runtime.js';
64
65
  export { ProcessTransport, QoderCliProcessError, } from './core/process-transport.js';
65
66
  export { hasResolvableQoderWorkerRuntime, QoderWorkerRuntimeError, WorkerTransport, resolveQoderWorkerRuntimePath, } from './core/worker-transport.js';
@@ -75,13 +76,14 @@ export { importSessionToStore } from './session/session-import.js';
75
76
  export type { ImportSessionToStoreOptions } from './session/session-import.js';
76
77
  export type { SessionKey, SessionStore, SessionStoreEntry, SessionStoreFlush, } from './session/session-store.js';
77
78
  export { listPlugins, enablePlugin, disablePlugin, installPlugin, uninstallPlugin, validatePlugin, } from './global/plugins.js';
78
- export type { PluginScope, ListPluginsOptions, PluginToggleOptions, InstallPluginOptions, UninstallPluginOptions, PluginDetails, PluginResources, InstallPluginResult, UninstallPluginResult, PluginValidationSeverity, PluginValidationPhase, PluginValidationPosition, PluginValidationLocation, PluginValidationDiagnostic, PluginValidationReport, ValidatePluginOptions, } from './types/plugins.js';
79
+ export type { PluginScope, ListPluginsOptions, PluginToggleOptions, InstallPluginOptions, UninstallPluginOptions, PluginDetails, PluginResources, InstallPluginResult, UninstallPluginResult, PluginValidationSeverity, PluginValidationPhase, PluginValidationPosition, PluginValidationLocation, PluginValidationDiagnostic, PluginValidationReport, PluginValidationDaemon, ValidatePluginOptions, } from './types/plugins.js';
79
80
  export { submitFeedback } from './feedback.js';
80
81
  export type { SubmitFeedbackParams, SubmitFeedbackOptions, IntegrationMode, } from './feedback.js';
81
82
  export type { HostActionOptions, HostActionParams } from './types/host-action.js';
82
83
  export type { HostAction, HostActionEvent, } from './protocol/feedback.js';
83
84
  export { AbortError, ControlRequestError, CONTROL_REQUEST_TIMEOUT_CODE, FrameTooLargeError, QoderCliInitializationError, ModelPolicyTimeoutError, ProtocolVersionMismatchError, UnsupportedCliCapabilityError, } from './core/errors.js';
84
- export { DaemonRpcError } from './daemon/daemon-connection.js';
85
+ export { DaemonRpcError, DaemonSessionAlreadyOpenError, } from './daemon/daemon-connection.js';
86
+ export { DaemonHeartbeatTimeoutError, DAEMON_HEARTBEAT_TIMEOUT_MS, } from './daemon/daemon-connection.js';
85
87
  export { AuthAccessTokenEnvVarError, AuthServiceAccountEnvVarError, } from './core/runtime-launch-options.js';
86
88
  export { WIRE_PROTOCOL_VERSION } from './protocol/version.js';
87
89
  export type { ModelPromptPatch, ModelPromptPatches, } from './protocol/model-prompt-patches.js';