qwen-agent-server 0.11.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/embed.js ADDED
@@ -0,0 +1,92 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // embed.ts — direct-HTTP dispatch for `qwen_embed`.
4
+ //
5
+ // llama-server exposes /v1/embeddings (OpenAI-compat) when started with
6
+ // `--embedding` and an embedding-capable model (e.g. bge-m3,
7
+ // qwen3-embedding-0.6b). The SDK is text-chat only and doesn't surface
8
+ // this endpoint; we POST directly via the shared OpenAI-compat helper.
9
+ import { createLogger } from "./log.js";
10
+ import { dispatchOpenAIPost } from "./openai-compat.js";
11
+ const log = createLogger("qwen-embed");
12
+ const DEFAULT_TIMEOUT_MS = 60_000;
13
+ /**
14
+ * POST to a backend's /v1/embeddings with one-or-many text inputs.
15
+ * Never throws; failures encoded in result.error.
16
+ */
17
+ export async function dispatchEmbed(backend, texts, opts = {}) {
18
+ const timeout_ms = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS;
19
+ const body = {
20
+ model: backend.model,
21
+ input: texts.length === 1 ? texts[0] : texts,
22
+ };
23
+ if (opts.encoding_format !== undefined) {
24
+ body.encoding_format = opts.encoding_format;
25
+ }
26
+ const outcome = await dispatchOpenAIPost(backend, "/v1/embeddings", body, {
27
+ timeout_ms,
28
+ });
29
+ if (!outcome.ok) {
30
+ if (outcome.status !== undefined) {
31
+ log.warn({
32
+ backend_id: backend.id,
33
+ status: outcome.status,
34
+ body_excerpt: outcome.body_text?.slice(0, 200),
35
+ }, "embed dispatch HTTP failure");
36
+ }
37
+ return {
38
+ ok: false,
39
+ elapsed_ms: outcome.elapsed_ms,
40
+ backend_id: backend.id,
41
+ error: outcome.error,
42
+ };
43
+ }
44
+ let parsed;
45
+ try {
46
+ parsed = JSON.parse(outcome.body_text);
47
+ }
48
+ catch (err) {
49
+ return {
50
+ ok: false,
51
+ elapsed_ms: outcome.elapsed_ms,
52
+ backend_id: backend.id,
53
+ error: {
54
+ code: "backend_error",
55
+ message: `non-JSON response: ${err.message}`,
56
+ },
57
+ };
58
+ }
59
+ if (!Array.isArray(parsed.data) || parsed.data.length === 0) {
60
+ return {
61
+ ok: false,
62
+ elapsed_ms: outcome.elapsed_ms,
63
+ backend_id: backend.id,
64
+ error: { code: "no_data", message: "backend returned empty data array" },
65
+ };
66
+ }
67
+ // Reassemble in input order (data[].index can be sparse on some servers).
68
+ const ordered = [...parsed.data].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
69
+ const embeddings = ordered
70
+ .map((d) => d.embedding)
71
+ .filter((e) => Array.isArray(e));
72
+ if (embeddings.length !== texts.length) {
73
+ return {
74
+ ok: false,
75
+ elapsed_ms: outcome.elapsed_ms,
76
+ backend_id: backend.id,
77
+ error: {
78
+ code: "no_data",
79
+ message: `expected ${texts.length} embeddings, got ${embeddings.length}`,
80
+ },
81
+ };
82
+ }
83
+ return {
84
+ ok: true,
85
+ embeddings,
86
+ ...(parsed.usage !== undefined ? { usage: parsed.usage } : {}),
87
+ ...(parsed.model !== undefined ? { model: parsed.model } : {}),
88
+ elapsed_ms: outcome.elapsed_ms,
89
+ backend_id: backend.id,
90
+ };
91
+ }
92
+ //# sourceMappingURL=embed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embed.js","sourceRoot":"","sources":["../src/embed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,497 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Per-spawn extension loadout helpers — RDR-002.
4
+ //
5
+ // This module exposes the supervisor-side bridge between Claude's
6
+ // orchestrator and the Qwen Code CLI's extensions surface:
7
+ //
8
+ // resolveQwenRealBin(env, whichFn?) — resolve the real qwen binary
9
+ // path the wrapper script will exec. Called once at supervisor
10
+ // startup; result is cached on the handlers/pool context and
11
+ // forwarded to every session via QueryOptions.env.QWEN_REAL_BIN.
12
+ //
13
+ // resolveWrapperPath() — absolute path to the bash wrapper shipped
14
+ // in this package at scripts/qwen-extensions-wrapper.sh. The
15
+ // wrapper is a fixed file; per-session variation is via env vars
16
+ // (QWEN_REAL_BIN, QWEN_AGENT_EXTENSIONS).
17
+ //
18
+ // parseInstalledExtensions(stdout) — pure parser for `qwen
19
+ // extensions list` output. Returns the list of installed names
20
+ // (lowercased) or [] on empty / unparseable input. Never throws.
21
+ //
22
+ // createInstalledExtensionsCache(qwenRealBin, execFn?) — async
23
+ // factory returning a cache object with get/reload/size methods.
24
+ // Initial population shells out to `<qwenRealBin> extensions list`;
25
+ // execFn is injected for testability.
26
+ //
27
+ // Subsequent phases will add the resolveExtensions(opts, sessionDefault,
28
+ // installedCache) algorithm and the qwen_spawn handler integration.
29
+ import { execFile, execFileSync } from "node:child_process";
30
+ import { statSync } from "node:fs";
31
+ import { dirname, resolve } from "node:path";
32
+ import { fileURLToPath } from "node:url";
33
+ import { createLogger } from "./log.js";
34
+ import { readConfigDefaultExtensions } from "./backends.js";
35
+ const log = createLogger("qwen-extensions");
36
+ /**
37
+ * Default `which` implementation used when a caller doesn't inject one.
38
+ * Returns the resolved absolute path or null if the command is not on
39
+ * PATH. Never throws.
40
+ */
41
+ function defaultWhich(cmd) {
42
+ try {
43
+ const out = execFileSync("/usr/bin/env", ["which", cmd], {
44
+ encoding: "utf8",
45
+ stdio: ["ignore", "pipe", "ignore"],
46
+ }).trim();
47
+ return out === "" ? null : out;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Resolve the real qwen binary path the wrapper script will `exec`.
55
+ *
56
+ * Policy (RDR-002 §The wrapper-script bridge → QWEN_REAL_BIN bullet):
57
+ *
58
+ * 1. If `env.QWEN_REAL_BIN` is set and non-empty, honour it verbatim.
59
+ * Verify the path exists and has any executable bit set; throw
60
+ * with a descriptive message on miss. The supervisor exits
61
+ * non-zero at startup rather than failing at first spawn.
62
+ * 2. Else, run `which qwen`. If empty/null, throw — the supervisor
63
+ * cannot start without a resolvable qwen binary.
64
+ *
65
+ * The `whichFn` parameter is injected for testability; production code
66
+ * leaves it undefined and `defaultWhich` is used.
67
+ */
68
+ export function resolveQwenRealBin(env, whichFn) {
69
+ const override = env["QWEN_REAL_BIN"];
70
+ if (override !== undefined && override !== "") {
71
+ let mode;
72
+ try {
73
+ const stat = statSync(override);
74
+ if (!stat.isFile()) {
75
+ throw new Error(`QWEN_REAL_BIN=${override} is not a regular file`);
76
+ }
77
+ mode = stat.mode;
78
+ }
79
+ catch (err) {
80
+ // Re-throw our own descriptive errors; wrap fs errors with the path.
81
+ if (err instanceof Error && err.message.startsWith("QWEN_REAL_BIN=")) {
82
+ throw err;
83
+ }
84
+ throw new Error(`QWEN_REAL_BIN=${override} does not exist or is not accessible`);
85
+ }
86
+ if ((mode & 0o111) === 0) {
87
+ throw new Error(`QWEN_REAL_BIN=${override} exists but is not executable (mode bits 0o111 unset)`);
88
+ }
89
+ return override;
90
+ }
91
+ const which = whichFn ?? defaultWhich;
92
+ const found = which("qwen");
93
+ if (found === null || found === "") {
94
+ throw new Error("QWEN_REAL_BIN unset and 'qwen' not on PATH — install Qwen Code or set QWEN_REAL_BIN");
95
+ }
96
+ return found;
97
+ }
98
+ /**
99
+ * Absolute path to the wrapper script shipped at
100
+ * `mcp-bridges/qwen-agent-server/scripts/qwen-extensions-wrapper.sh`.
101
+ *
102
+ * Resolution is anchored on `import.meta.url` so the same code works
103
+ * whether the module loads from `src/` (during tests) or from `dist/`
104
+ * (after `tsc` build) — both sit one level below the package root.
105
+ */
106
+ export function resolveWrapperPath() {
107
+ const here = dirname(fileURLToPath(import.meta.url));
108
+ return resolve(here, "..", "scripts", "qwen-extensions-wrapper.sh");
109
+ }
110
+ // ─────────────────────────────────────────────────────────────────
111
+ // Installed-extensions cache
112
+ /**
113
+ * Strip ANSI SGR escape sequences (chalk emits these around status
114
+ * glyphs) so the parser can match plain-text content.
115
+ */
116
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
117
+ /**
118
+ * First-line header of an extension block emitted by
119
+ * `extensionToOutputString` (cli.js:456690):
120
+ *
121
+ * <glyph> <name> (<version>)
122
+ *
123
+ * where `<glyph>` is `✓` (U+2713) or `✗` (U+2717) and `<name>` is the
124
+ * `config.name` field of the extension manifest. The glyph is REQUIRED:
125
+ * `extensionToOutputString` only emits a leading-space-only line in
126
+ * `inline2 = true` mode, which `handleList` (cli.js:456770) does not
127
+ * pass. Requiring the glyph narrows the regex so unrelated lines that
128
+ * happen to end with `(something)` cannot accidentally register as
129
+ * extension names if a future block-separator change causes the
130
+ * `\n{2,}` split to miss boundaries.
131
+ *
132
+ * The version sub-pattern `[^()]+` deliberately rejects nested parens,
133
+ * which keeps the second-line ` Source: ... (Type: ...)` from
134
+ * accidentally matching when block boundaries don't separate cleanly.
135
+ */
136
+ const HEADER_RE = /^\s*[✓✗]\s+(.+?)\s+\([^()]+\)\s*$/;
137
+ /**
138
+ * Parse `qwen extensions list` stdout and return the lowercased
139
+ * `config.name` of each installed extension.
140
+ *
141
+ * Fail-soft per RDR-002 audit-note #4: empty input, the
142
+ * "No extensions installed." sentinel, and unrecognized output all
143
+ * yield `[]` rather than throwing — an upstream output-format change
144
+ * degrades gracefully (cache populates empty; future spawns reject
145
+ * unknown names) instead of bricking the supervisor.
146
+ */
147
+ export function parseInstalledExtensions(stdout) {
148
+ return parseInstalledExtensionsRich(stdout).map((e) => e.name);
149
+ }
150
+ /**
151
+ * Parse `qwen extensions list` stdout into structured per-extension
152
+ * records. Mirrors `extensionToOutputString` in cli.js:456690 — each
153
+ * block is joined by `\n\n` and starts with `<glyph> <name> (<version>)`.
154
+ *
155
+ * Fail-soft: on empty / sentinel / unparseable input, returns `[]`.
156
+ * Individual fields that don't match expected line patterns are simply
157
+ * omitted from the record; we never throw.
158
+ */
159
+ export function parseInstalledExtensionsRich(stdout) {
160
+ if (typeof stdout !== "string")
161
+ return [];
162
+ const cleaned = stdout.replace(ANSI_RE, "");
163
+ if (cleaned.trim() === "")
164
+ return [];
165
+ if (/no extensions installed/i.test(cleaned))
166
+ return [];
167
+ const blocks = cleaned.split(/\n{2,}/);
168
+ const out = [];
169
+ for (const block of blocks) {
170
+ const lines = block.split("\n");
171
+ const firstLine = lines[0]?.trim() ?? "";
172
+ if (firstLine === "")
173
+ continue;
174
+ // Header match: glyph + name + (version)
175
+ const headerMatch = /^\s*([✓✗])\s+(.+?)\s+\(([^()]+)\)\s*$/.exec(firstLine);
176
+ if (!headerMatch)
177
+ continue;
178
+ const glyph = headerMatch[1];
179
+ const name = headerMatch[2]?.trim();
180
+ const version = headerMatch[3]?.trim();
181
+ if (!name)
182
+ continue;
183
+ const info = { name: name.toLowerCase() };
184
+ if (version)
185
+ info.version = version;
186
+ info.enabled_workspace = glyph === "✓";
187
+ // Field lines and list-section accumulation. Format reference:
188
+ // ` Path: <path>`
189
+ // ` Source: <source> (Type: <type>)` [optional]
190
+ // ` Enabled (User): <bool>`
191
+ // ` Enabled (Workspace): <bool>`
192
+ // ` Context files:` then ` <file>` lines
193
+ // ` Commands:` then ` /<cmd>` lines
194
+ // ` Skills:` then ` <skill>` lines
195
+ // ` Agents:` then ` <agent>` lines
196
+ // ` MCP servers:` then ` <name>` lines
197
+ let currentList = null;
198
+ for (let i = 1; i < lines.length; i++) {
199
+ const line = lines[i];
200
+ if (line === undefined)
201
+ continue;
202
+ const trimmed = line.trim();
203
+ if (trimmed === "")
204
+ continue;
205
+ // List-item lines start with two spaces of indent; field lines start with one.
206
+ const isListItem = /^ {2,}\S/.test(line) && !/^\s*\w[\w\s]*?:/.test(trimmed);
207
+ if (isListItem && currentList !== null) {
208
+ // Strip the leading slash for commands ("/foo" → "foo") to match
209
+ // how the supervisor's resolveExtensions expects them.
210
+ const item = trimmed.replace(/^\//, "");
211
+ if (item)
212
+ currentList.push(item);
213
+ continue;
214
+ }
215
+ currentList = null;
216
+ const fieldMatch = /^\s*([\w\s()]+?):\s*(.*)$/.exec(line);
217
+ if (!fieldMatch)
218
+ continue;
219
+ const key = (fieldMatch[1] ?? "").trim().toLowerCase();
220
+ const val = (fieldMatch[2] ?? "").trim();
221
+ if (key === "path") {
222
+ if (val)
223
+ info.path = val;
224
+ }
225
+ else if (key === "source") {
226
+ // Strip trailing "(Type: ...)" suffix — source is just the identifier.
227
+ if (val)
228
+ info.source = val.replace(/\s*\(Type:\s*[^)]*\)\s*$/, "");
229
+ }
230
+ else if (key === "enabled (user)") {
231
+ info.enabled_user = /^true$/i.test(val);
232
+ }
233
+ else if (key === "enabled (workspace)") {
234
+ info.enabled_workspace = /^true$/i.test(val);
235
+ }
236
+ else if (key === "context files") {
237
+ info.context_files = [];
238
+ currentList = info.context_files;
239
+ }
240
+ else if (key === "commands") {
241
+ info.commands = [];
242
+ currentList = info.commands;
243
+ }
244
+ else if (key === "skills") {
245
+ info.skills = [];
246
+ currentList = info.skills;
247
+ }
248
+ else if (key === "agents") {
249
+ info.agents = [];
250
+ currentList = info.agents;
251
+ }
252
+ else if (key === "mcp servers") {
253
+ info.mcp_servers = [];
254
+ currentList = info.mcp_servers;
255
+ }
256
+ }
257
+ // Drop empty list arrays so JSON stays compact.
258
+ for (const k of ["context_files", "commands", "skills", "agents", "mcp_servers"]) {
259
+ if (info[k] && info[k]?.length === 0)
260
+ delete info[k];
261
+ }
262
+ out.push(info);
263
+ }
264
+ return out;
265
+ }
266
+ export const defaultExecExtensionsList = (qwenRealBin) => new Promise((res, rej) => {
267
+ execFile(qwenRealBin, ["extensions", "list"], { encoding: "utf8" }, (err, stdout) => {
268
+ if (err) {
269
+ rej(err);
270
+ return;
271
+ }
272
+ res(stdout);
273
+ });
274
+ });
275
+ /**
276
+ * Shell out to `<qwenRealBin> extensions list`, parse the rich form, and
277
+ * return the structured per-extension records. Throws on exec failure
278
+ * (qwen binary missing, etc.). Returns `[]` if output is empty or
279
+ * unparseable — same fail-soft contract as the bare-name parser.
280
+ *
281
+ * Used by the `qwen_extensions` MCP tool to give callers the full
282
+ * installed-extensions inventory (versions, paths, source, declared
283
+ * commands/skills/agents/MCP servers) without going through the
284
+ * cache (which only retains names).
285
+ */
286
+ export async function listInstalledExtensions(qwenRealBin, execFn = defaultExecExtensionsList) {
287
+ const stdout = await execFn(qwenRealBin);
288
+ return parseInstalledExtensionsRich(stdout);
289
+ }
290
+ /**
291
+ * Construct an `InstalledExtensionsCache` and prime it once.
292
+ *
293
+ * - Exec errors propagate (fail-fast at startup) — the supervisor
294
+ * should not start if the qwen binary cannot be invoked.
295
+ * - Output that is non-empty but unparseable is treated as an empty
296
+ * set; a structured-log warning records the first 200 chars of the
297
+ * output so an operator can diagnose without a crash.
298
+ */
299
+ export async function createInstalledExtensionsCache(qwenRealBin, execFn) {
300
+ const exec = execFn ?? defaultExecExtensionsList;
301
+ let names = new Set();
302
+ async function loadOnce() {
303
+ const stdout = await exec(qwenRealBin);
304
+ const parsed = parseInstalledExtensions(stdout);
305
+ if (parsed.length === 0 &&
306
+ stdout.trim() !== "" &&
307
+ !/no extensions installed/i.test(stdout)) {
308
+ log.warn({ stdout_preview: stdout.slice(0, 200) }, "qwen extensions list output did not match expected format; cache populated empty");
309
+ }
310
+ return new Set(parsed);
311
+ }
312
+ names = await loadOnce();
313
+ return {
314
+ get: () => names,
315
+ reload: async () => {
316
+ names = await loadOnce();
317
+ return names;
318
+ },
319
+ size: () => names.size,
320
+ };
321
+ }
322
+ /**
323
+ * Thrown by `resolveExtensions` when the resolved set contains a name
324
+ * the supervisor's installed-extensions cache does not know, or when
325
+ * the caller asked for enable/disable without a session-default base
326
+ * to mutate. Caught by the qwen_spawn handler and translated into a
327
+ * `{ error: { code: 'spawn_error', message } }` envelope.
328
+ */
329
+ export class ExtensionResolutionError extends Error {
330
+ unknown;
331
+ constructor(message, unknown = []) {
332
+ super(message);
333
+ this.name = "ExtensionResolutionError";
334
+ this.unknown = unknown;
335
+ }
336
+ }
337
+ /**
338
+ * Read the supervisor's session-default extension set.
339
+ *
340
+ * Resolution priority (highest first):
341
+ * 1. `QWEN_DEFAULT_EXTENSIONS` env var (back-compat / one-shot override)
342
+ * 2. `default_extensions` field in `~/.qwen-coprocessor-stack/config.json`
343
+ * 3. "leave-defaults" sentinel — wrapper drops --extensions; CLI defaults
344
+ * (all enabled per extension-enablement.json) apply
345
+ *
346
+ * The config-file source is mtime-cached at the `readConfig()` layer in
347
+ * backends.ts, so re-invocation on every spawn is cheap.
348
+ */
349
+ export function getSessionDefaultExtensions(env) {
350
+ // 1. env override
351
+ const raw = env["QWEN_DEFAULT_EXTENSIONS"];
352
+ if (raw !== undefined && raw !== "") {
353
+ return dedupeLower(raw.split(",").map((s) => s.trim()).filter((s) => s !== ""));
354
+ }
355
+ // 2. config file
356
+ const fromFile = readConfigDefaultExtensions();
357
+ if (fromFile && fromFile.length > 0) {
358
+ return dedupeLower(fromFile);
359
+ }
360
+ // 3. unset → CLI defaults apply
361
+ return "leave-defaults";
362
+ }
363
+ function dedupeLower(input) {
364
+ const seen = new Set();
365
+ const out = [];
366
+ for (const name of input) {
367
+ const lower = name.toLowerCase();
368
+ if (lower === "" || seen.has(lower))
369
+ continue;
370
+ seen.add(lower);
371
+ out.push(lower);
372
+ }
373
+ return out;
374
+ }
375
+ /**
376
+ * Framework-required extension set — RDR-002 §Framework-required
377
+ * extensions. Empty today: the supervisor's contract with the inner
378
+ * Qwen (write-authority gating, ask_user_question exclusion,
379
+ * system-prompt preamble, multi-turn streamInput) is enforced via
380
+ * QueryOptions and does not require any extension to be loaded. Adding
381
+ * even one would change the supervisor's contract and must be
382
+ * RDR-tracked.
383
+ *
384
+ * If this set ever becomes non-empty: names here are supervisor-
385
+ * controlled and must be validated against the installed-extensions
386
+ * cache once at supervisor startup; the per-spawn `resolveExtensions`
387
+ * step-7 union does NOT re-validate framework-required names (step 7
388
+ * runs after step 6 in the RDR algorithm).
389
+ */
390
+ const FRAMEWORK_REQUIRED_EXTENSIONS = [];
391
+ /**
392
+ * Step 7 — union with framework-required. Adds any framework-required
393
+ * names not already in the resolved set, lowercased and dedup-safe.
394
+ * Idempotent / no-op today because the framework-required set is empty.
395
+ *
396
+ * Exported for direct testability of the non-empty path: an inline
397
+ * call with a non-empty `frameworkRequired` argument exercises the
398
+ * union logic that would otherwise be unreachable from
399
+ * `resolveExtensions` while `FRAMEWORK_REQUIRED_EXTENSIONS` is empty.
400
+ */
401
+ export function unionFrameworkRequired(base, frameworkRequired = FRAMEWORK_REQUIRED_EXTENSIONS) {
402
+ if (frameworkRequired.length === 0)
403
+ return base;
404
+ const seen = new Set(base);
405
+ const out = [...base];
406
+ for (const name of frameworkRequired) {
407
+ const lower = name.toLowerCase();
408
+ if (!seen.has(lower)) {
409
+ out.push(lower);
410
+ seen.add(lower);
411
+ }
412
+ }
413
+ return out;
414
+ }
415
+ /**
416
+ * Resolve the active extension set for a single qwen_spawn call,
417
+ * implementing steps 1–9 of RDR-002 §Resolution-algorithm.
418
+ *
419
+ * Steps (verbatim from the RDR):
420
+ * 1. Determine session-default — caller passes it in.
421
+ * 2. Compute base — `only` wins exact-set semantics; otherwise
422
+ * session-default is the base.
423
+ * 3. Apply `enable` additively.
424
+ * 4. Apply `disable` subtractively (disable wins on overlap).
425
+ * 5. enable / disable independent.
426
+ * 6. Validate against installedCache; throw ExtensionResolutionError
427
+ * with the unknown names if any.
428
+ * 7. Union with framework-required (today: empty).
429
+ * 8. Render — non-empty → comma-list; explicit empty → "none".
430
+ * 9. "leave-defaults" → null envValue.
431
+ */
432
+ export function resolveExtensions(opts, sessionDefault, installedCache) {
433
+ const onlyProvided = opts?.only !== undefined;
434
+ const enableProvided = opts?.enable !== undefined && opts.enable.length > 0;
435
+ const disableProvided = opts?.disable !== undefined && opts.disable.length > 0;
436
+ // Step 2: compute base.
437
+ // 2a: only wins (enable/disable IGNORED).
438
+ // 2b: else base is session-default with enable/disable applied.
439
+ let base;
440
+ if (onlyProvided) {
441
+ base = dedupeLower(opts.only);
442
+ }
443
+ else if (sessionDefault === "leave-defaults") {
444
+ if (enableProvided || disableProvided) {
445
+ // Cannot compute a deterministic resolved set without enumerating
446
+ // the implicit CLI-defaults set; reject so the caller gets a
447
+ // visible error rather than silent surprise.
448
+ throw new ExtensionResolutionError("cannot apply opts.extensions.enable/disable when QWEN_DEFAULT_EXTENSIONS is unset; " +
449
+ "set a session default or use opts.extensions.only to specify the exact set");
450
+ }
451
+ // Step 9: no mutations and no base — leave-defaults short-circuits
452
+ // before validation/union (no resolved set to validate or union into).
453
+ return { envValue: null, resolved: "leave-defaults" };
454
+ }
455
+ else {
456
+ // Session-default is a concrete list. Apply enable additively, then
457
+ // disable subtractively (steps 3–5).
458
+ base = dedupeLower(sessionDefault);
459
+ if (enableProvided) {
460
+ const additions = dedupeLower(opts.enable);
461
+ const seen = new Set(base);
462
+ for (const name of additions) {
463
+ if (!seen.has(name)) {
464
+ base.push(name);
465
+ seen.add(name);
466
+ }
467
+ }
468
+ }
469
+ if (disableProvided) {
470
+ const removals = new Set(dedupeLower(opts.disable));
471
+ base = base.filter((name) => !removals.has(name));
472
+ }
473
+ }
474
+ // Step 6: validate caller-supplied names against the installed cache.
475
+ validateInstalled(base, installedCache);
476
+ // Step 7: union with framework-required. Names here are supervisor-
477
+ // controlled and pre-validated at startup; not re-validated per spawn.
478
+ base = unionFrameworkRequired(base);
479
+ // Step 8: render. An empty base reached by subtraction or an explicit
480
+ // only=[] renders as "none". The only path that produces leave-defaults
481
+ // is the no-op branch above.
482
+ if (base.length === 0) {
483
+ return { envValue: "none", resolved: "none" };
484
+ }
485
+ return { envValue: base.join(","), resolved: base };
486
+ }
487
+ function validateInstalled(names, installed) {
488
+ const unknown = [];
489
+ for (const name of names) {
490
+ if (!installed.has(name))
491
+ unknown.push(name);
492
+ }
493
+ if (unknown.length > 0) {
494
+ throw new ExtensionResolutionError(`unknown extension(s): ${unknown.join(", ")}`, unknown);
495
+ }
496
+ }
497
+ //# sourceMappingURL=extensions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.js","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"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"}
package/dist/log.js ADDED
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Shared pino logger factory.
4
+ //
5
+ // All loggers in this package MUST write to stderr (fd 2), never stdout.
6
+ // This package runs as an MCP stdio server: stdout is reserved exclusively
7
+ // for JSON-RPC protocol frames. Any non-JSONRPC bytes on stdout corrupt the
8
+ // channel for strict MCP clients (e.g. the official Python SDK, which
9
+ // pydantic-validates every received line as a JSONRPCMessage).
10
+ //
11
+ // Discovered via nexus spike_d bench, 2026-05-15 — Claude Code's MCP plugin
12
+ // happened to be lenient with non-JSONRPC stdout, masking the bug until a
13
+ // strict client connected.
14
+ import pino from "pino";
15
+ // Single shared destination — pino docs recommend reusing one destination
16
+ // stream across loggers rather than constructing one per logger.
17
+ const STDERR = pino.destination(2);
18
+ export function createLogger(name) {
19
+ return pino({ name }, STDERR);
20
+ }
21
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,8BAA8B;AAC9B,EAAE;AACF,yEAAyE;AACzE,2EAA2E;AAC3E,4EAA4E;AAC5E,sEAAsE;AACtE,+DAA+D;AAC/D,EAAE;AACF,4EAA4E;AAC5E,0EAA0E;AAC1E,2BAA2B;AAE3B,OAAO,IAAqB,MAAM,MAAM,CAAC;AAEzC,0EAA0E;AAC1E,iEAAiE;AACjE,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;AAEnC,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;AAChC,CAAC"}