@ken-jo/agent-connector 0.1.0
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/LICENSE +202 -0
- package/NOTICE +6 -0
- package/README.md +348 -0
- package/dist/amp-QSFH2V7Y.js +217 -0
- package/dist/amp-QSFH2V7Y.js.map +1 -0
- package/dist/amp-TULUPXXL.js +237 -0
- package/dist/amp-TULUPXXL.js.map +1 -0
- package/dist/antigravity-CX2YLSY7.js +17 -0
- package/dist/antigravity-CX2YLSY7.js.map +1 -0
- package/dist/antigravity-STHZWMP3.js +171 -0
- package/dist/antigravity-STHZWMP3.js.map +1 -0
- package/dist/antigravity-cli-P6HADOAO.js +121 -0
- package/dist/antigravity-cli-P6HADOAO.js.map +1 -0
- package/dist/antigravity-cli-YQDFVV4U.js +86 -0
- package/dist/antigravity-cli-YQDFVV4U.js.map +1 -0
- package/dist/chunk-54EMKTTX.js +191 -0
- package/dist/chunk-54EMKTTX.js.map +1 -0
- package/dist/chunk-7L6ITC4T.js +54 -0
- package/dist/chunk-7L6ITC4T.js.map +1 -0
- package/dist/chunk-7MSDGHJW.js +316 -0
- package/dist/chunk-7MSDGHJW.js.map +1 -0
- package/dist/chunk-BCHWFTV4.js +196 -0
- package/dist/chunk-BCHWFTV4.js.map +1 -0
- package/dist/chunk-DBQMX75U.js +36 -0
- package/dist/chunk-DBQMX75U.js.map +1 -0
- package/dist/chunk-EXLS7BSI.js +29 -0
- package/dist/chunk-EXLS7BSI.js.map +1 -0
- package/dist/chunk-JNM3TPLX.js +593 -0
- package/dist/chunk-JNM3TPLX.js.map +1 -0
- package/dist/chunk-JWNT5KW7.js +197 -0
- package/dist/chunk-JWNT5KW7.js.map +1 -0
- package/dist/chunk-KHO5WNTP.js +30 -0
- package/dist/chunk-KHO5WNTP.js.map +1 -0
- package/dist/chunk-LTT56GI3.js +155 -0
- package/dist/chunk-LTT56GI3.js.map +1 -0
- package/dist/chunk-MEH7GC7R.js +111 -0
- package/dist/chunk-MEH7GC7R.js.map +1 -0
- package/dist/chunk-MQPNU2CY.js +64 -0
- package/dist/chunk-MQPNU2CY.js.map +1 -0
- package/dist/chunk-OPXAP3AC.js +12 -0
- package/dist/chunk-OPXAP3AC.js.map +1 -0
- package/dist/chunk-RK6VBLXP.js +50 -0
- package/dist/chunk-RK6VBLXP.js.map +1 -0
- package/dist/chunk-TIIP3RG2.js +420 -0
- package/dist/chunk-TIIP3RG2.js.map +1 -0
- package/dist/chunk-TRKWUNO4.js +702 -0
- package/dist/chunk-TRKWUNO4.js.map +1 -0
- package/dist/chunk-TRQKHP7D.js +93 -0
- package/dist/chunk-TRQKHP7D.js.map +1 -0
- package/dist/chunk-WCETQWWK.js +59 -0
- package/dist/chunk-WCETQWWK.js.map +1 -0
- package/dist/chunk-WV63X7IK.js +357 -0
- package/dist/chunk-WV63X7IK.js.map +1 -0
- package/dist/chunk-XD7MY6HB.js +343 -0
- package/dist/chunk-XD7MY6HB.js.map +1 -0
- package/dist/chunk-XNAM7F35.js +276 -0
- package/dist/chunk-XNAM7F35.js.map +1 -0
- package/dist/chunk-XUCWLJY5.js +238 -0
- package/dist/chunk-XUCWLJY5.js.map +1 -0
- package/dist/chunk-YNZU6PAV.js +800 -0
- package/dist/chunk-YNZU6PAV.js.map +1 -0
- package/dist/chunk-YT43MUSJ.js +146 -0
- package/dist/chunk-YT43MUSJ.js.map +1 -0
- package/dist/claude-code-HKDV7T32.js +637 -0
- package/dist/claude-code-HKDV7T32.js.map +1 -0
- package/dist/claude-code-W74MNTAC.js +652 -0
- package/dist/claude-code-W74MNTAC.js.map +1 -0
- package/dist/cli/sdk.d.ts +54 -0
- package/dist/cli/sdk.js +82 -0
- package/dist/cli/sdk.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -0
- package/dist/codebuff-3PMZJJHP.js +307 -0
- package/dist/codebuff-3PMZJJHP.js.map +1 -0
- package/dist/codebuff-QX57FJDN.js +220 -0
- package/dist/codebuff-QX57FJDN.js.map +1 -0
- package/dist/codex-JFQTS6LU.js +593 -0
- package/dist/codex-JFQTS6LU.js.map +1 -0
- package/dist/codex-Q3ATRRX3.js +364 -0
- package/dist/codex-Q3ATRRX3.js.map +1 -0
- package/dist/copilot-cli-MTCGHZ2T.js +622 -0
- package/dist/copilot-cli-MTCGHZ2T.js.map +1 -0
- package/dist/copilot-cli-SJGIXVSD.js +369 -0
- package/dist/copilot-cli-SJGIXVSD.js.map +1 -0
- package/dist/crush-I7PK3FGI.js +360 -0
- package/dist/crush-I7PK3FGI.js.map +1 -0
- package/dist/crush-LN5QFFR2.js +183 -0
- package/dist/crush-LN5QFFR2.js.map +1 -0
- package/dist/cursor-4TMXKJOZ.js +603 -0
- package/dist/cursor-4TMXKJOZ.js.map +1 -0
- package/dist/cursor-QZ6MTO74.js +194 -0
- package/dist/cursor-QZ6MTO74.js.map +1 -0
- package/dist/detect-WIAGVYS4.js +68 -0
- package/dist/detect-WIAGVYS4.js.map +1 -0
- package/dist/doctor-EZW2K4NI.js +372 -0
- package/dist/doctor-EZW2K4NI.js.map +1 -0
- package/dist/droid-KN6VRMMT.js +424 -0
- package/dist/droid-KN6VRMMT.js.map +1 -0
- package/dist/droid-W5WF5XEX.js +224 -0
- package/dist/droid-W5WF5XEX.js.map +1 -0
- package/dist/gemini-cli-CIRW37TU.js +369 -0
- package/dist/gemini-cli-CIRW37TU.js.map +1 -0
- package/dist/gemini-cli-Y35FFK4M.js +705 -0
- package/dist/gemini-cli-Y35FFK4M.js.map +1 -0
- package/dist/goose-A3M5GODH.js +212 -0
- package/dist/goose-A3M5GODH.js.map +1 -0
- package/dist/goose-ZTGTSUFJ.js +480 -0
- package/dist/goose-ZTGTSUFJ.js.map +1 -0
- package/dist/hermes-H2KV44GK.js +470 -0
- package/dist/hermes-H2KV44GK.js.map +1 -0
- package/dist/hermes-ZYLMOFLZ.js +196 -0
- package/dist/hermes-ZYLMOFLZ.js.map +1 -0
- package/dist/hook-YVDILAMZ.js +75 -0
- package/dist/hook-YVDILAMZ.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/install-HQRYZOQ2.js +60 -0
- package/dist/install-HQRYZOQ2.js.map +1 -0
- package/dist/jetbrains-copilot-HWA3DMH7.js +633 -0
- package/dist/jetbrains-copilot-HWA3DMH7.js.map +1 -0
- package/dist/kilo-NSWAVP6P.js +235 -0
- package/dist/kilo-NSWAVP6P.js.map +1 -0
- package/dist/kilo-QXJRCSLT.js +384 -0
- package/dist/kilo-QXJRCSLT.js.map +1 -0
- package/dist/kilo-cli-EAQTHXUW.js +587 -0
- package/dist/kilo-cli-EAQTHXUW.js.map +1 -0
- package/dist/kilo-cli-EVBKUODA.js +160 -0
- package/dist/kilo-cli-EVBKUODA.js.map +1 -0
- package/dist/kimi-EI3UM3ZV.js +155 -0
- package/dist/kimi-EI3UM3ZV.js.map +1 -0
- package/dist/kimi-GQ7VXNYT.js +423 -0
- package/dist/kimi-GQ7VXNYT.js.map +1 -0
- package/dist/kiro-UCLSLBW2.js +217 -0
- package/dist/kiro-UCLSLBW2.js.map +1 -0
- package/dist/kiro-VQTT6LO2.js +451 -0
- package/dist/kiro-VQTT6LO2.js.map +1 -0
- package/dist/leaderboard-K6ON3SWW.js +137 -0
- package/dist/leaderboard-K6ON3SWW.js.map +1 -0
- package/dist/mux-T2TV4CIQ.js +211 -0
- package/dist/mux-T2TV4CIQ.js.map +1 -0
- package/dist/mux-ZMLVAXOO.js +96 -0
- package/dist/mux-ZMLVAXOO.js.map +1 -0
- package/dist/omp-PB4VDKS6.js +586 -0
- package/dist/omp-PB4VDKS6.js.map +1 -0
- package/dist/openclaw-ETN2M63O.js +751 -0
- package/dist/openclaw-ETN2M63O.js.map +1 -0
- package/dist/openclaw-IYA7ASL6.js +150 -0
- package/dist/openclaw-IYA7ASL6.js.map +1 -0
- package/dist/opencode-FZ33JKUZ.js +734 -0
- package/dist/opencode-FZ33JKUZ.js.map +1 -0
- package/dist/opencode-HWT3RSMN.js +216 -0
- package/dist/opencode-HWT3RSMN.js.map +1 -0
- package/dist/package-DLOVE2E4.js +1132 -0
- package/dist/package-DLOVE2E4.js.map +1 -0
- package/dist/pi-CIU2QHT4.js +208 -0
- package/dist/pi-CIU2QHT4.js.map +1 -0
- package/dist/pi-UFX2OYFV.js +139 -0
- package/dist/pi-UFX2OYFV.js.map +1 -0
- package/dist/qwen-code-5POCNU6P.js +573 -0
- package/dist/qwen-code-5POCNU6P.js.map +1 -0
- package/dist/qwen-code-W7NUCDEN.js +134 -0
- package/dist/qwen-code-W7NUCDEN.js.map +1 -0
- package/dist/roo-code-IOIUXRDE.js +242 -0
- package/dist/roo-code-IOIUXRDE.js.map +1 -0
- package/dist/roo-code-ZLKH4A7N.js +229 -0
- package/dist/roo-code-ZLKH4A7N.js.map +1 -0
- package/dist/runtime/index.d.ts +157 -0
- package/dist/runtime/index.js +20 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/serve-HVWWY4B6.js +65 -0
- package/dist/serve-HVWWY4B6.js.map +1 -0
- package/dist/status-FE7TECZD.js +128 -0
- package/dist/status-FE7TECZD.js.map +1 -0
- package/dist/synthetic-UKR6SMGS.js +178 -0
- package/dist/synthetic-UKR6SMGS.js.map +1 -0
- package/dist/telemetry-36GESKQQ.js +349 -0
- package/dist/telemetry-36GESKQQ.js.map +1 -0
- package/dist/trae-JSNDZFKL.js +204 -0
- package/dist/trae-JSNDZFKL.js.map +1 -0
- package/dist/trae-PA26DPBW.js +157 -0
- package/dist/trae-PA26DPBW.js.map +1 -0
- package/dist/types-CYUB1OTr.d.ts +427 -0
- package/dist/uninstall-IGCKNOSF.js +72 -0
- package/dist/uninstall-IGCKNOSF.js.map +1 -0
- package/dist/upgrade-JV2VK242.js +104 -0
- package/dist/upgrade-JV2VK242.js.map +1 -0
- package/dist/usage-6ER6EMEY.js +324 -0
- package/dist/usage-6ER6EMEY.js.map +1 -0
- package/dist/usage-event-JKBI6XCF.js +48 -0
- package/dist/usage-event-JKBI6XCF.js.map +1 -0
- package/dist/vscode-copilot-CK3NO54M.js +715 -0
- package/dist/vscode-copilot-CK3NO54M.js.map +1 -0
- package/dist/warp-JNQA7PBU.js +126 -0
- package/dist/warp-JNQA7PBU.js.map +1 -0
- package/dist/warp-QNVDXOG4.js +199 -0
- package/dist/warp-QNVDXOG4.js.map +1 -0
- package/dist/zed-NNNX5MNT.js +272 -0
- package/dist/zed-NNNX5MNT.js.map +1 -0
- package/dist/zed-VRJKWYUR.js +209 -0
- package/dist/zed-VRJKWYUR.js.map +1 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/usage/readers/pi.ts"],"sourcesContent":["/**\n * usage/readers/pi — Pi (badlogic/pi-mono) + OMP (oh-my-pi fork) session reader.\n *\n * Faithful port of tokscale sessions/pi.rs. Pi-format JSONL lives under TWO\n * roots that share the identical schema (tokscale scans both under ClientId::Pi):\n * - ~/.pi/agent/sessions/<encoded-cwd>/*.jsonl → emitted as platformId \"pi\"\n * - ~/.omp/agent/sessions/<encoded-cwd>/*.jsonl → emitted as platformId \"omp\"\n * (Oh My Pi fork, https://github.com/can1357/oh-my-pi — same format, new root)\n * The pi root honors the PI_CODING_AGENT_DIR override (verbatim when non-empty);\n * the omp root is the fixed ~/.omp default.\n *\n * File shape (port of parse_pi_file):\n * - Line 0 MUST be the session header `{type:\"session\", id, cwd?, timestamp?}`.\n * If the first non-empty line is not a parseable `session` header, the WHOLE\n * file yields nothing (mirrors the Rust early-return on header failure).\n * - Subsequent lines are entries; only `type:\"message\"` with\n * `message.role==\"assistant\"` AND a present `message.usage`, `message.model`,\n * and `message.provider` produce a record. Any of those missing → skip line.\n *\n * Tokens (message.usage): input, output, cacheRead→cacheRead, cacheWrite→cacheWrite;\n * reasoning is always 0 (Pi does not report it). Each token clamped ≥ 0.\n * Provider is the explicit `message.provider` field (NOT inferred). Timestamp is\n * the entry's RFC3339 `timestamp` (→ epoch ms), falling back to the file mtime.\n * Session id is the header `id`; workspace is the header `cwd` normalized once.\n *\n * Dedup: pi.rs performs NO dedup (one row per assistant line; file path + line\n * index are implicitly unique within a session). We still emit a stable\n * dedupKey `<platformId>:<sessionId>:<lineIndex>` so the global insertion-order\n * de-dup backstop has a per-record key to key on.\n *\n * Confidence: \"host-reported\" (Pi logs real token counts).\n *\n * Fail-open: no root → []; unreadable/malformed file or line → skipped; a file\n * whose header is missing/invalid contributes nothing (never throws).\n */\n\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, resolve } from \"node:path\";\n\nimport type { PlatformId } from \"../../core/types.js\";\nimport type { UsageReader, UsageRecord } from \"../types.js\";\nimport { emptyTokens } from \"../aggregate.js\";\nimport { fileMtimeMs, readJsonlLines } from \"../jsonl.js\";\nimport { normalizeWorkspaceKey, workspaceLabelFromKey } from \"../normalize.js\";\nimport { isDir, walkFiles } from \"../paths.js\";\n\nconst PI_PLATFORM_ID: PlatformId = \"pi\";\nconst OMP_PLATFORM_ID: PlatformId = \"omp\";\n\n/** The two Pi-format roots, each tagged with the platformId it emits. */\ninterface PiRoot {\n readonly dir: string;\n readonly platformId: PlatformId;\n}\n\n/** Session header (first JSONL line). */\ninterface PiSessionHeader {\n type?: unknown;\n id?: unknown;\n timestamp?: unknown;\n cwd?: unknown;\n}\n\n/** Subsequent JSONL entry. */\ninterface PiSessionEntry {\n type?: unknown;\n timestamp?: unknown;\n message?: {\n role?: unknown;\n model?: unknown;\n provider?: unknown;\n usage?: {\n input?: unknown;\n output?: unknown;\n cacheRead?: unknown;\n cacheWrite?: unknown;\n };\n };\n}\n\n/** Coerce an unknown to a non-negative integer (0 on absence/garbage). */\nfunction toNonNegInt(v: unknown): number {\n const n = typeof v === \"number\" ? v : typeof v === \"string\" ? Number(v) : NaN;\n if (!Number.isFinite(n)) return 0;\n return Math.max(0, Math.trunc(n));\n}\n\n/** Parse an RFC3339 / numeric timestamp to epoch ms, or null when unusable. */\nfunction parseTs(v: unknown): number | null {\n if (typeof v === \"string\") {\n const ms = Date.parse(v);\n if (!Number.isNaN(ms)) return ms;\n const num = Number(v);\n if (Number.isFinite(num) && num > 0) return num >= 1e12 ? num : num * 1000;\n return null;\n }\n if (typeof v === \"number\" && Number.isFinite(v) && v > 0) {\n return v >= 1e12 ? v : v * 1000;\n }\n return null;\n}\n\n/**\n * Read an env override, treating empty/blank as unset (tokscale's\n * is_config_dir_overridden contract: an empty override must NOT resolve to \"\").\n * Relative paths resolve against the process CWD; \"~\" is expanded.\n */\nfunction envOverrideDir(name: string): string | undefined {\n const raw = process.env[name];\n if (raw == null || raw.trim() === \"\") return undefined;\n let p = raw.trim();\n if (p === \"~\") p = homedir();\n else if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) p = join(homedir(), p.slice(2));\n return isAbsolute(p) ? p : resolve(p);\n}\n\n/**\n * Resolve the two Pi-format roots, most-specific first. The pi root honors the\n * PI_CODING_AGENT_DIR override; the omp root is the fixed ~/.omp default. Roots\n * that are not existing directories are dropped (fail-open).\n */\nfunction piRoots(): PiRoot[] {\n const piDir = envOverrideDir(\"PI_CODING_AGENT_DIR\") ?? join(homedir(), \".pi\", \"agent\", \"sessions\");\n const ompDir = join(homedir(), \".omp\", \"agent\", \"sessions\");\n const candidates: PiRoot[] = [\n { dir: piDir, platformId: PI_PLATFORM_ID },\n { dir: ompDir, platformId: OMP_PLATFORM_ID },\n ];\n return candidates.filter((c) => isDir(c.dir));\n}\n\n/**\n * Parse one Pi JSONL session file into usage records (port of parse_pi_file).\n * Returns [] when the header is missing/invalid (whole-file early return) or no\n * assistant line qualifies. `platformId` tags which root the file came from.\n */\nfunction parsePiFile(path: string, platformId: PlatformId): UsageRecord[] {\n const lines = readJsonlLines(path);\n if (lines.length === 0) return [];\n\n const fallbackTs = fileMtimeMs(path);\n\n // ── Header (first non-empty parseable line) ────────────────────────────\n // readJsonlLines already drops blank/malformed lines; index 0 is the first\n // successfully-parsed line. It MUST be a `session` header or the file is void.\n const headerRaw = lines[0];\n if (typeof headerRaw !== \"object\" || headerRaw === null) return [];\n const header = headerRaw as PiSessionHeader;\n if (header.type !== \"session\") return [];\n if (typeof header.id !== \"string\" || header.id === \"\") return [];\n const sessionId = header.id;\n\n let projectKey: string | undefined;\n let projectLabel: string | undefined;\n if (typeof header.cwd === \"string\" && header.cwd !== \"\") {\n const key = normalizeWorkspaceKey(header.cwd);\n if (key !== undefined) {\n projectKey = key;\n projectLabel = workspaceLabelFromKey(key);\n }\n }\n\n const out: UsageRecord[] = [];\n\n // ── Entries (every line after the header) ──────────────────────────────\n for (let i = 1; i < lines.length; i++) {\n const raw = lines[i];\n if (typeof raw !== \"object\" || raw === null) continue;\n const entry = raw as PiSessionEntry;\n\n if (entry.type !== \"message\") continue;\n const message = entry.message;\n if (message === undefined || message === null) continue;\n if (message.role !== \"assistant\") continue;\n\n const usage = message.usage;\n if (usage === undefined || usage === null) continue;\n if (typeof message.model !== \"string\" || message.model === \"\") continue;\n if (typeof message.provider !== \"string\" || message.provider === \"\") continue;\n\n const modelId = message.model;\n const providerId = message.provider;\n\n const ts = parseTs(entry.timestamp) ?? fallbackTs;\n\n const tokens = emptyTokens();\n tokens.input = toNonNegInt(usage.input);\n tokens.output = toNonNegInt(usage.output);\n tokens.cacheRead = toNonNegInt(usage.cacheRead);\n tokens.cacheWrite = toNonNegInt(usage.cacheWrite);\n // reasoning stays 0 (Pi does not report it).\n\n const record: UsageRecord = {\n platformId,\n modelId,\n providerId,\n sessionId,\n tokens,\n ts,\n messageCount: 1,\n // No host dedup key in Pi; synthesize a per-record key (path-implicit\n // uniqueness within a session, mirrored by session id + line index).\n dedupKey: `${platformId}:${sessionId}:${i}`,\n confidence: \"host-reported\",\n };\n if (projectKey !== undefined) record.projectKey = projectKey;\n if (projectLabel !== undefined) record.projectLabel = projectLabel;\n out.push(record);\n }\n\n return out;\n}\n\n/** The Pi / OMP usage reader singleton (registry lists it under \"pi\"). */\nconst piReader: UsageReader = {\n platformId: PI_PLATFORM_ID,\n kind: \"local\",\n async read({ sinceMs }: { sinceMs?: number }): Promise<UsageRecord[]> {\n const roots = piRoots();\n if (roots.length === 0) return []; // no ~/.pi or ~/.omp → fail-open\n\n const records: UsageRecord[] = [];\n for (const root of roots) {\n // <root>/<encoded-cwd>/*.jsonl\n const files = walkFiles(root.dir, (name) => name.endsWith(\".jsonl\"));\n for (const file of files) {\n const rows = parsePiFile(file, root.platformId);\n for (const row of rows) {\n if (sinceMs !== undefined && row.ts < sinceMs) continue;\n records.push(row);\n }\n }\n }\n return records;\n },\n};\n\nexport default piReader;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
writeTomlString
|
|
4
|
+
} from "./chunk-OPXAP3AC.js";
|
|
5
|
+
import {
|
|
6
|
+
resolveEnvRefsDeep
|
|
7
|
+
} from "./chunk-DBQMX75U.js";
|
|
8
|
+
import {
|
|
9
|
+
BaseAdapter
|
|
10
|
+
} from "./chunk-TIIP3RG2.js";
|
|
11
|
+
import "./chunk-7L6ITC4T.js";
|
|
12
|
+
import {
|
|
13
|
+
buildHomeBinHookCommand,
|
|
14
|
+
buildServeWrapperCommand,
|
|
15
|
+
isHomeBinHookCommand,
|
|
16
|
+
shouldWrapForTelemetry
|
|
17
|
+
} from "./chunk-MEH7GC7R.js";
|
|
18
|
+
import "./chunk-TRQKHP7D.js";
|
|
19
|
+
|
|
20
|
+
// src/adapters/qwen-code/index.ts
|
|
21
|
+
import { existsSync } from "fs";
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
import { join } from "path";
|
|
24
|
+
var HOST = "qwen-code";
|
|
25
|
+
var MCP_ROOT_KEY = "mcpServers";
|
|
26
|
+
var QwenCodeAdapter = class extends BaseAdapter {
|
|
27
|
+
id = HOST;
|
|
28
|
+
name = "Qwen CLI";
|
|
29
|
+
paradigm = "json-stdio";
|
|
30
|
+
capabilities = {
|
|
31
|
+
preToolUse: true,
|
|
32
|
+
postToolUse: true,
|
|
33
|
+
preCompact: true,
|
|
34
|
+
sessionStart: true,
|
|
35
|
+
sessionEnd: true,
|
|
36
|
+
userPromptSubmit: true,
|
|
37
|
+
stop: true,
|
|
38
|
+
notification: true,
|
|
39
|
+
// Qwen's PreToolUse can rewrite input (updatedInput). It CANNOT rewrite
|
|
40
|
+
// already-emitted tool output — `updatedMCPToolOutput` does not exist in
|
|
41
|
+
// qwen 0.17.1, so canModifyOutput is false (like the Claude-family hosts).
|
|
42
|
+
canModifyArgs: true,
|
|
43
|
+
canModifyOutput: false,
|
|
44
|
+
canInjectSessionContext: true,
|
|
45
|
+
transports: ["stdio", "sse", "http"],
|
|
46
|
+
// Content surfaces: Qwen ships native slash commands (TOML) and subagents
|
|
47
|
+
// (md+frontmatter). It has no Agent-Skills surface, so supportsSkills stays
|
|
48
|
+
// false and the BaseAdapter skip/warn default handles any declared skills.
|
|
49
|
+
supportsCommands: true,
|
|
50
|
+
supportsSubagents: true
|
|
51
|
+
};
|
|
52
|
+
// ── Detection ────────────────────────────────────────────────────────────
|
|
53
|
+
detectInstalled(projectDir) {
|
|
54
|
+
const userDir = join(homedir(), ".qwen");
|
|
55
|
+
const userSettings = join(userDir, "settings.json");
|
|
56
|
+
const projectDirQwen = join(projectDir, ".qwen");
|
|
57
|
+
const projectSettings = join(projectDirQwen, "settings.json");
|
|
58
|
+
const userInstalled = existsSync(userDir) || existsSync(userSettings);
|
|
59
|
+
const projInstalled = existsSync(projectDirQwen) || existsSync(projectSettings);
|
|
60
|
+
const installed = userInstalled || projInstalled;
|
|
61
|
+
const scope = projInstalled && !userInstalled ? "project" : "user";
|
|
62
|
+
const configPath = scope === "project" ? projectSettings : userSettings;
|
|
63
|
+
return {
|
|
64
|
+
id: this.id,
|
|
65
|
+
name: this.name,
|
|
66
|
+
installed,
|
|
67
|
+
paradigm: this.paradigm,
|
|
68
|
+
capabilities: this.capabilities,
|
|
69
|
+
configPath,
|
|
70
|
+
scope,
|
|
71
|
+
reason: installed ? scope === "project" ? `found project Qwen CLI config at ${projectSettings}` : `found Qwen CLI config under ${userDir}` : `no Qwen CLI config at ${userDir}`,
|
|
72
|
+
confidence: installed ? "high" : "low"
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// ── Native paths ─────────────────────────────────────────────────────────
|
|
76
|
+
getConfigDir(ctx) {
|
|
77
|
+
return ctx.scope === "project" ? join(ctx.projectDir, ".qwen") : join(homedir(), ".qwen");
|
|
78
|
+
}
|
|
79
|
+
/** MCP servers live in settings.json under `mcpServers`. */
|
|
80
|
+
getServerConfigPath(ctx) {
|
|
81
|
+
return join(this.getConfigDir(ctx), "settings.json");
|
|
82
|
+
}
|
|
83
|
+
/** Hooks live in the SAME settings.json under the sibling `hooks` key. */
|
|
84
|
+
getHookConfigPath(ctx) {
|
|
85
|
+
return join(this.getConfigDir(ctx), "settings.json");
|
|
86
|
+
}
|
|
87
|
+
// ── MCP server install / uninstall ───────────────────────────────────────
|
|
88
|
+
installServer(ctx) {
|
|
89
|
+
const { connector } = ctx;
|
|
90
|
+
const override = connector.platforms[HOST]?.server;
|
|
91
|
+
if (!connector.server || override === false) {
|
|
92
|
+
return [
|
|
93
|
+
{
|
|
94
|
+
platform: this.id,
|
|
95
|
+
action: "skip",
|
|
96
|
+
detail: connector.server ? "server registration disabled for qwen-code" : "connector declares no MCP server"
|
|
97
|
+
}
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
const server = override && typeof override === "object" ? { ...connector.server, ...override } : connector.server;
|
|
101
|
+
const serverPath = this.getServerConfigPath(ctx);
|
|
102
|
+
const entry = this.renderServerEntry(ctx, server);
|
|
103
|
+
return [
|
|
104
|
+
this.upsertServerInJson(serverPath, MCP_ROOT_KEY, connector.id, entry, ctx.dryRun)
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
uninstallServer(ctx) {
|
|
108
|
+
const serverPath = this.getServerConfigPath(ctx);
|
|
109
|
+
return [
|
|
110
|
+
this.removeServerFromJson(serverPath, MCP_ROOT_KEY, ctx.connector.id, ctx.dryRun)
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Render a normalized ServerDef into Qwen's native mcpServers entry. As a
|
|
115
|
+
* Gemini-CLI fork, the transport is encoded by WHICH KEY is present (NOT a
|
|
116
|
+
* `type` field): command/args/env (stdio), url (sse), httpUrl (http).
|
|
117
|
+
*/
|
|
118
|
+
renderServerEntry(ctx, server) {
|
|
119
|
+
const transport = server.transport;
|
|
120
|
+
if (transport === "stdio") {
|
|
121
|
+
let command = server.command ?? "";
|
|
122
|
+
let args = [...server.args ?? []];
|
|
123
|
+
if (shouldWrapForTelemetry(server, ctx.connector.telemetry)) {
|
|
124
|
+
const wrapped = buildServeWrapperCommand(
|
|
125
|
+
ctx.homeBinPath,
|
|
126
|
+
ctx.connector.id,
|
|
127
|
+
command,
|
|
128
|
+
args,
|
|
129
|
+
ctx.scope,
|
|
130
|
+
this.id
|
|
131
|
+
);
|
|
132
|
+
command = wrapped.command;
|
|
133
|
+
args = wrapped.args;
|
|
134
|
+
}
|
|
135
|
+
const entry2 = {
|
|
136
|
+
type: "stdio",
|
|
137
|
+
command: resolveEnvRefsDeep(command),
|
|
138
|
+
args: args.map((a) => resolveEnvRefsDeep(a))
|
|
139
|
+
};
|
|
140
|
+
const env = this.renderEnv(server.env);
|
|
141
|
+
if (env) entry2.env = env;
|
|
142
|
+
if (server.cwd) entry2.cwd = resolveEnvRefsDeep(server.cwd);
|
|
143
|
+
return entry2;
|
|
144
|
+
}
|
|
145
|
+
if (transport === "sse") {
|
|
146
|
+
const entry2 = { url: resolveEnvRefsDeep(server.url ?? "") };
|
|
147
|
+
const headers2 = this.renderEnv(server.headers);
|
|
148
|
+
if (headers2) entry2.headers = headers2;
|
|
149
|
+
return entry2;
|
|
150
|
+
}
|
|
151
|
+
const entry = { httpUrl: resolveEnvRefsDeep(server.url ?? "") };
|
|
152
|
+
const headers = this.renderEnv(server.headers);
|
|
153
|
+
if (headers) entry.headers = headers;
|
|
154
|
+
return entry;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Render env/header values. Qwen's settings loader does not interpret our
|
|
158
|
+
* `${env:VAR}` dialect, so refs resolve to literals at install time — the safe
|
|
159
|
+
* default shared with the Gemini / Codex adapters.
|
|
160
|
+
*/
|
|
161
|
+
renderEnv(env) {
|
|
162
|
+
if (!env || Object.keys(env).length === 0) return void 0;
|
|
163
|
+
return resolveEnvRefsDeep({ ...env });
|
|
164
|
+
}
|
|
165
|
+
// ── Hook install / uninstall ─────────────────────────────────────────────
|
|
166
|
+
installHooks(ctx) {
|
|
167
|
+
const { connector } = ctx;
|
|
168
|
+
if (connector.platforms[HOST]?.hooks === false) {
|
|
169
|
+
return [{ platform: this.id, action: "skip", detail: "hooks disabled for qwen-code" }];
|
|
170
|
+
}
|
|
171
|
+
if (connector.hookEvents.length === 0) {
|
|
172
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no hooks" }];
|
|
173
|
+
}
|
|
174
|
+
const settingsPath = this.getHookConfigPath(ctx);
|
|
175
|
+
const settings = this.readJson(settingsPath) ?? {};
|
|
176
|
+
const hooks = settings.hooks ??= {};
|
|
177
|
+
const changes = [];
|
|
178
|
+
let mutated = false;
|
|
179
|
+
for (const event of connector.hookEvents) {
|
|
180
|
+
const command = buildHomeBinHookCommand(ctx.homeBinPath, HOST, event, connector.id);
|
|
181
|
+
const matcher = connector.hooks[event]?.matcher ?? "";
|
|
182
|
+
const entry = {
|
|
183
|
+
matcher,
|
|
184
|
+
hooks: [{ type: "command", command }]
|
|
185
|
+
};
|
|
186
|
+
const bucket = hooks[event] ??= [];
|
|
187
|
+
const existingIdx = bucket.findIndex((e) => this.entryHasOurCommand(e, ctx));
|
|
188
|
+
if (existingIdx >= 0) {
|
|
189
|
+
if (JSON.stringify(bucket[existingIdx]) === JSON.stringify(entry)) {
|
|
190
|
+
changes.push({
|
|
191
|
+
platform: this.id,
|
|
192
|
+
action: "skip",
|
|
193
|
+
path: settingsPath,
|
|
194
|
+
detail: `hooks.${event} already registered`
|
|
195
|
+
});
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
bucket[existingIdx] = entry;
|
|
199
|
+
changes.push({
|
|
200
|
+
platform: this.id,
|
|
201
|
+
action: "update",
|
|
202
|
+
path: settingsPath,
|
|
203
|
+
detail: `hooks.${event}`
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
bucket.push(entry);
|
|
207
|
+
changes.push({
|
|
208
|
+
platform: this.id,
|
|
209
|
+
action: "create",
|
|
210
|
+
path: settingsPath,
|
|
211
|
+
detail: `hooks.${event}`
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
mutated = true;
|
|
215
|
+
}
|
|
216
|
+
if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);
|
|
217
|
+
return changes;
|
|
218
|
+
}
|
|
219
|
+
uninstallHooks(ctx) {
|
|
220
|
+
const settingsPath = this.getHookConfigPath(ctx);
|
|
221
|
+
const settings = this.readJson(settingsPath);
|
|
222
|
+
const hooks = settings?.hooks;
|
|
223
|
+
if (!settings || !hooks) {
|
|
224
|
+
return [
|
|
225
|
+
{
|
|
226
|
+
platform: this.id,
|
|
227
|
+
action: "skip",
|
|
228
|
+
path: settingsPath,
|
|
229
|
+
detail: "no hooks section present"
|
|
230
|
+
}
|
|
231
|
+
];
|
|
232
|
+
}
|
|
233
|
+
const changes = [];
|
|
234
|
+
let mutated = false;
|
|
235
|
+
for (const event of Object.keys(hooks)) {
|
|
236
|
+
const bucket = hooks[event];
|
|
237
|
+
if (!Array.isArray(bucket)) continue;
|
|
238
|
+
const next = [];
|
|
239
|
+
let removed = 0;
|
|
240
|
+
for (const e of bucket) {
|
|
241
|
+
const innerBefore = e.hooks?.length ?? 0;
|
|
242
|
+
const inner = (e.hooks ?? []).filter((h) => !this.isOurCommand(h.command, ctx));
|
|
243
|
+
removed += innerBefore - inner.length;
|
|
244
|
+
if (inner.length > 0) next.push({ matcher: e.matcher ?? "", hooks: inner });
|
|
245
|
+
}
|
|
246
|
+
if (removed > 0) {
|
|
247
|
+
if (next.length > 0) hooks[event] = next;
|
|
248
|
+
else delete hooks[event];
|
|
249
|
+
changes.push({
|
|
250
|
+
platform: this.id,
|
|
251
|
+
action: "remove",
|
|
252
|
+
path: settingsPath,
|
|
253
|
+
detail: `hooks.${event} (${removed})`
|
|
254
|
+
});
|
|
255
|
+
mutated = true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (mutated) this.writeJson(settingsPath, settings, ctx.dryRun);
|
|
259
|
+
if (changes.length === 0) {
|
|
260
|
+
changes.push({
|
|
261
|
+
platform: this.id,
|
|
262
|
+
action: "skip",
|
|
263
|
+
path: settingsPath,
|
|
264
|
+
detail: "no matching hook entries"
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return changes;
|
|
268
|
+
}
|
|
269
|
+
entryHasOurCommand(entry, ctx) {
|
|
270
|
+
return (entry.hooks ?? []).some((h) => this.isOurCommand(h.command, ctx));
|
|
271
|
+
}
|
|
272
|
+
/** True when a hook command references our home binary AND this connector id
|
|
273
|
+
* (anchored so a shared-prefix id can't collide — see isHomeBinHookCommand). */
|
|
274
|
+
isOurCommand(command, ctx) {
|
|
275
|
+
return isHomeBinHookCommand(command, ctx.homeBinPath, ctx.connector.id);
|
|
276
|
+
}
|
|
277
|
+
// ── Content surfaces: commands / subagents ───────────────────────────────
|
|
278
|
+
// CONTENT-ONLY: pure native-file writers under <qwenDir>/{commands,agents}. No
|
|
279
|
+
// runtime dispatch, no home-bin pointer, no telemetry wrap. Each method is
|
|
280
|
+
// idempotent (byte-identical → skip) via BaseAdapter.writeContentFile and
|
|
281
|
+
// reversible via removeContentFile. Honors platforms["qwen-code"] per-surface
|
|
282
|
+
// false to skip. Qwen has NO Agent-Skills surface, so skills are left to the
|
|
283
|
+
// BaseAdapter skip/warn default (supportsSkills stays false).
|
|
284
|
+
commandsDir(ctx) {
|
|
285
|
+
return join(this.getConfigDir(ctx), "commands");
|
|
286
|
+
}
|
|
287
|
+
agentsDir(ctx) {
|
|
288
|
+
return join(this.getConfigDir(ctx), "agents");
|
|
289
|
+
}
|
|
290
|
+
/** Native command file path: <qwenDir>/commands/<name>.toml. */
|
|
291
|
+
commandPath(ctx, name) {
|
|
292
|
+
return join(this.commandsDir(ctx), `${name}.toml`);
|
|
293
|
+
}
|
|
294
|
+
/** Native subagent file path: <qwenDir>/agents/<name>.md. */
|
|
295
|
+
subagentPath(ctx, name) {
|
|
296
|
+
return join(this.agentsDir(ctx), `${name}.md`);
|
|
297
|
+
}
|
|
298
|
+
// ── Commands ──────────────────────────────────────────────────────────────
|
|
299
|
+
installCommands(ctx) {
|
|
300
|
+
const { connector } = ctx;
|
|
301
|
+
if (connector.platforms[HOST]?.commands === false) {
|
|
302
|
+
return [{ platform: this.id, action: "skip", detail: "commands disabled for qwen-code" }];
|
|
303
|
+
}
|
|
304
|
+
if (connector.commands.length === 0) {
|
|
305
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
|
|
306
|
+
}
|
|
307
|
+
return connector.commands.map(
|
|
308
|
+
(cmd) => this.writeContentFile(
|
|
309
|
+
this.commandPath(ctx, cmd.name),
|
|
310
|
+
this.renderCommand(cmd),
|
|
311
|
+
ctx.dryRun
|
|
312
|
+
)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
uninstallCommands(ctx) {
|
|
316
|
+
const { connector } = ctx;
|
|
317
|
+
if (connector.commands.length === 0) {
|
|
318
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no commands" }];
|
|
319
|
+
}
|
|
320
|
+
return connector.commands.map(
|
|
321
|
+
(cmd) => this.removeContentFile(this.commandPath(ctx, cmd.name), ctx.dryRun)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
/** Render a command to Qwen's native TOML (description, prompt). */
|
|
325
|
+
renderCommand(cmd) {
|
|
326
|
+
const obj = {};
|
|
327
|
+
if (cmd.description !== void 0) obj.description = cmd.description;
|
|
328
|
+
obj.prompt = cmd.prompt;
|
|
329
|
+
return writeTomlString(obj);
|
|
330
|
+
}
|
|
331
|
+
// ── Subagents ──────────────────────────────────────────────────────────────
|
|
332
|
+
installSubagents(ctx) {
|
|
333
|
+
const { connector } = ctx;
|
|
334
|
+
if (connector.platforms[HOST]?.subagents === false) {
|
|
335
|
+
return [{ platform: this.id, action: "skip", detail: "subagents disabled for qwen-code" }];
|
|
336
|
+
}
|
|
337
|
+
if (connector.subagents.length === 0) {
|
|
338
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
|
|
339
|
+
}
|
|
340
|
+
return connector.subagents.map(
|
|
341
|
+
(agent) => this.writeContentFile(
|
|
342
|
+
this.subagentPath(ctx, agent.name),
|
|
343
|
+
this.renderSubagent(agent),
|
|
344
|
+
ctx.dryRun
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
uninstallSubagents(ctx) {
|
|
349
|
+
const { connector } = ctx;
|
|
350
|
+
if (connector.subagents.length === 0) {
|
|
351
|
+
return [{ platform: this.id, action: "skip", detail: "connector declares no subagents" }];
|
|
352
|
+
}
|
|
353
|
+
return connector.subagents.map(
|
|
354
|
+
(agent) => this.removeContentFile(this.subagentPath(ctx, agent.name), ctx.dryRun)
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
/** Render a subagent to md+frontmatter (name, description, tools, model) + prompt body. */
|
|
358
|
+
renderSubagent(agent) {
|
|
359
|
+
const frontmatter = {
|
|
360
|
+
name: agent.name,
|
|
361
|
+
description: agent.description
|
|
362
|
+
};
|
|
363
|
+
const allow = agent.tools?.allow;
|
|
364
|
+
if (allow && allow.length > 0) frontmatter.tools = allow.join(", ");
|
|
365
|
+
if (agent.model !== void 0) frontmatter.model = agent.model;
|
|
366
|
+
if (agent.extra) Object.assign(frontmatter, agent.extra);
|
|
367
|
+
return this.renderFrontmatterMd(frontmatter, agent.prompt);
|
|
368
|
+
}
|
|
369
|
+
// ── Diagnostics ──────────────────────────────────────────────────────────
|
|
370
|
+
getHealthChecks(ctx) {
|
|
371
|
+
const settingsPath = this.getHookConfigPath(ctx);
|
|
372
|
+
const connectorId = ctx.connector.id;
|
|
373
|
+
const homeBin = ctx.homeBinPath;
|
|
374
|
+
const hookEvents = ctx.connector.hookEvents;
|
|
375
|
+
const checks = [
|
|
376
|
+
{
|
|
377
|
+
name: `${this.name}: settings.json present`,
|
|
378
|
+
check: () => existsSync(settingsPath) ? { status: "OK", detail: settingsPath } : { status: "FAIL", detail: `not found: ${settingsPath}` }
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: `${this.name}: hook command registered`,
|
|
382
|
+
check: () => {
|
|
383
|
+
if (hookEvents.length === 0) {
|
|
384
|
+
return { status: "OK", detail: "no hooks declared" };
|
|
385
|
+
}
|
|
386
|
+
const settings = this.readJson(settingsPath);
|
|
387
|
+
if (!settings) return { status: "FAIL", detail: `cannot read ${settingsPath}` };
|
|
388
|
+
const hooks = settings.hooks ?? {};
|
|
389
|
+
const registered = Object.values(hooks).some(
|
|
390
|
+
(entries) => (entries ?? []).some(
|
|
391
|
+
(e) => (e.hooks ?? []).some(
|
|
392
|
+
(h) => isHomeBinHookCommand(h.command, homeBin, connectorId)
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
);
|
|
396
|
+
return registered ? { status: "OK", detail: "hook command present" } : { status: "FAIL", detail: `no hook for ${connectorId} in ${settingsPath}` };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
];
|
|
400
|
+
for (const cmd of ctx.connector.commands) {
|
|
401
|
+
const p = this.commandPath(ctx, cmd.name);
|
|
402
|
+
checks.push({
|
|
403
|
+
name: `${this.name}: command ${cmd.name} present`,
|
|
404
|
+
check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
for (const agent of ctx.connector.subagents) {
|
|
408
|
+
const p = this.subagentPath(ctx, agent.name);
|
|
409
|
+
checks.push({
|
|
410
|
+
name: `${this.name}: subagent ${agent.name} present`,
|
|
411
|
+
check: () => existsSync(p) ? { status: "OK", detail: p } : { status: "FAIL", detail: `not found: ${p}` }
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
return checks;
|
|
415
|
+
}
|
|
416
|
+
// ── Runtime: parse Qwen stdin JSON → normalized event ────────────────────
|
|
417
|
+
parseEvent(event, raw) {
|
|
418
|
+
const input = raw ?? {};
|
|
419
|
+
const connectorId = typeof input.connector === "string" ? input.connector : "";
|
|
420
|
+
const sessionId = extractSessionId(input);
|
|
421
|
+
const projectDir = typeof input.cwd === "string" ? input.cwd : void 0;
|
|
422
|
+
const base = {
|
|
423
|
+
hostPlatform: HOST,
|
|
424
|
+
connectorId,
|
|
425
|
+
sessionId,
|
|
426
|
+
raw,
|
|
427
|
+
...projectDir !== void 0 ? { projectDir } : {}
|
|
428
|
+
};
|
|
429
|
+
switch (event) {
|
|
430
|
+
case "PreToolUse": {
|
|
431
|
+
const ev = {
|
|
432
|
+
...base,
|
|
433
|
+
toolName: input.tool_name ?? "",
|
|
434
|
+
toolInput: input.tool_input ?? {}
|
|
435
|
+
};
|
|
436
|
+
return ev;
|
|
437
|
+
}
|
|
438
|
+
case "PostToolUse": {
|
|
439
|
+
const ev = {
|
|
440
|
+
...base,
|
|
441
|
+
toolName: input.tool_name ?? "",
|
|
442
|
+
toolInput: input.tool_input ?? {},
|
|
443
|
+
...toolResponseToString(input.tool_response) !== void 0 ? { toolOutput: toolResponseToString(input.tool_response) } : {}
|
|
444
|
+
};
|
|
445
|
+
return ev;
|
|
446
|
+
}
|
|
447
|
+
case "SessionStart": {
|
|
448
|
+
const ev = {
|
|
449
|
+
...base,
|
|
450
|
+
source: normalizeSessionSource(input.source)
|
|
451
|
+
};
|
|
452
|
+
return ev;
|
|
453
|
+
}
|
|
454
|
+
case "SessionEnd": {
|
|
455
|
+
const ev = {
|
|
456
|
+
...base,
|
|
457
|
+
...typeof input.reason === "string" ? { reason: input.reason } : {}
|
|
458
|
+
};
|
|
459
|
+
return ev;
|
|
460
|
+
}
|
|
461
|
+
case "UserPromptSubmit": {
|
|
462
|
+
const ev = {
|
|
463
|
+
...base,
|
|
464
|
+
prompt: typeof input.prompt === "string" ? input.prompt : ""
|
|
465
|
+
};
|
|
466
|
+
return ev;
|
|
467
|
+
}
|
|
468
|
+
case "PreCompact": {
|
|
469
|
+
const ev = {
|
|
470
|
+
...base,
|
|
471
|
+
...input.trigger === "auto" || input.trigger === "manual" ? { trigger: input.trigger } : {}
|
|
472
|
+
};
|
|
473
|
+
return ev;
|
|
474
|
+
}
|
|
475
|
+
case "Stop": {
|
|
476
|
+
const ev = {
|
|
477
|
+
...base,
|
|
478
|
+
...typeof input.stop_hook_active === "boolean" ? { stopHookActive: input.stop_hook_active } : {}
|
|
479
|
+
};
|
|
480
|
+
return ev;
|
|
481
|
+
}
|
|
482
|
+
case "Notification": {
|
|
483
|
+
const ev = {
|
|
484
|
+
...base,
|
|
485
|
+
message: typeof input.message === "string" ? input.message : ""
|
|
486
|
+
};
|
|
487
|
+
return ev;
|
|
488
|
+
}
|
|
489
|
+
default: {
|
|
490
|
+
const _never = event;
|
|
491
|
+
throw new Error(`unsupported qwen-code hook event: ${String(_never)}`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
// ── Runtime: normalized response → Qwen native hook reply ────────────────
|
|
496
|
+
formatReply(event, response) {
|
|
497
|
+
const hookEventName = event;
|
|
498
|
+
const decision = response.decision ?? "allow";
|
|
499
|
+
if (decision === "deny") {
|
|
500
|
+
return this.stdout({
|
|
501
|
+
hookSpecificOutput: {
|
|
502
|
+
hookEventName,
|
|
503
|
+
permissionDecision: "deny",
|
|
504
|
+
permissionDecisionReason: response.reason ?? "Blocked by hook"
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (decision === "ask") {
|
|
509
|
+
return this.stdout({
|
|
510
|
+
hookSpecificOutput: {
|
|
511
|
+
hookEventName,
|
|
512
|
+
permissionDecision: "ask",
|
|
513
|
+
permissionDecisionReason: response.reason ?? "Confirmation required by hook"
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (decision === "modify") {
|
|
518
|
+
if (event === "PreToolUse" && response.updatedInput) {
|
|
519
|
+
return this.stdout({
|
|
520
|
+
hookSpecificOutput: { hookEventName, updatedInput: response.updatedInput }
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (decision === "context" && response.additionalContext) {
|
|
525
|
+
return this.stdout({
|
|
526
|
+
hookSpecificOutput: { hookEventName, additionalContext: response.additionalContext }
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return { exitCode: 0 };
|
|
530
|
+
}
|
|
531
|
+
stdout(payload) {
|
|
532
|
+
return { exitCode: 0, stdout: JSON.stringify(payload) };
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function extractSessionId(input) {
|
|
536
|
+
if (typeof input.session_id === "string" && input.session_id !== "") {
|
|
537
|
+
return input.session_id;
|
|
538
|
+
}
|
|
539
|
+
if (typeof input.transcript_path === "string") {
|
|
540
|
+
const m = input.transcript_path.match(/([a-f0-9-]{36})\.jsonl$/);
|
|
541
|
+
if (m && m[1]) return m[1];
|
|
542
|
+
}
|
|
543
|
+
return "";
|
|
544
|
+
}
|
|
545
|
+
function toolResponseToString(value) {
|
|
546
|
+
if (value == null) return void 0;
|
|
547
|
+
if (typeof value === "string") return value;
|
|
548
|
+
try {
|
|
549
|
+
return JSON.stringify(value);
|
|
550
|
+
} catch {
|
|
551
|
+
return String(value);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function normalizeSessionSource(source) {
|
|
555
|
+
switch (source) {
|
|
556
|
+
case "compact":
|
|
557
|
+
return "compact";
|
|
558
|
+
case "resume":
|
|
559
|
+
return "resume";
|
|
560
|
+
case "clear":
|
|
561
|
+
return "clear";
|
|
562
|
+
default:
|
|
563
|
+
return "startup";
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
var adapter = new QwenCodeAdapter();
|
|
567
|
+
var qwen_code_default = adapter;
|
|
568
|
+
export {
|
|
569
|
+
QwenCodeAdapter,
|
|
570
|
+
adapter,
|
|
571
|
+
qwen_code_default as default
|
|
572
|
+
};
|
|
573
|
+
//# sourceMappingURL=qwen-code-5POCNU6P.js.map
|