@mxalbert/context-mode 2.0.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/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +31 -0
- package/.codex-plugin/hooks.json +65 -0
- package/.codex-plugin/mcp.json +13 -0
- package/.codex-plugin/plugin.json +31 -0
- package/.openclaw-plugin/index.ts +12 -0
- package/.openclaw-plugin/openclaw.plugin.json +38 -0
- package/.openclaw-plugin/package.json +28 -0
- package/LICENSE +94 -0
- package/README.md +1619 -0
- package/bin/statusline.mjs +378 -0
- package/build/adapters/antigravity/index.d.ts +51 -0
- package/build/adapters/antigravity/index.js +169 -0
- package/build/adapters/antigravity-cli/index.d.ts +51 -0
- package/build/adapters/antigravity-cli/index.js +342 -0
- package/build/adapters/base.d.ts +92 -0
- package/build/adapters/base.js +135 -0
- package/build/adapters/claude-code/config.d.ts +8 -0
- package/build/adapters/claude-code/config.js +8 -0
- package/build/adapters/claude-code/hooks.d.ts +106 -0
- package/build/adapters/claude-code/hooks.js +183 -0
- package/build/adapters/claude-code/index.d.ts +80 -0
- package/build/adapters/claude-code/index.js +569 -0
- package/build/adapters/claude-code-base.d.ts +49 -0
- package/build/adapters/claude-code-base.js +113 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +42 -0
- package/build/adapters/codex/config.d.ts +8 -0
- package/build/adapters/codex/config.js +8 -0
- package/build/adapters/codex/hooks.d.ts +55 -0
- package/build/adapters/codex/hooks.js +64 -0
- package/build/adapters/codex/index.d.ts +87 -0
- package/build/adapters/codex/index.js +995 -0
- package/build/adapters/codex/paths.d.ts +1 -0
- package/build/adapters/codex/paths.js +12 -0
- package/build/adapters/codex/usage.d.ts +107 -0
- package/build/adapters/codex/usage.js +227 -0
- package/build/adapters/copilot-base.d.ts +94 -0
- package/build/adapters/copilot-base.js +297 -0
- package/build/adapters/copilot-cli/hooks.d.ts +33 -0
- package/build/adapters/copilot-cli/hooks.js +64 -0
- package/build/adapters/copilot-cli/index.d.ts +48 -0
- package/build/adapters/copilot-cli/index.js +341 -0
- package/build/adapters/cursor/config.d.ts +4 -0
- package/build/adapters/cursor/config.js +4 -0
- package/build/adapters/cursor/hooks.d.ts +55 -0
- package/build/adapters/cursor/hooks.js +82 -0
- package/build/adapters/cursor/index.d.ts +64 -0
- package/build/adapters/cursor/index.js +517 -0
- package/build/adapters/detect.d.ts +136 -0
- package/build/adapters/detect.js +623 -0
- package/build/adapters/gemini-cli/config.d.ts +8 -0
- package/build/adapters/gemini-cli/config.js +8 -0
- package/build/adapters/gemini-cli/hooks.d.ts +83 -0
- package/build/adapters/gemini-cli/hooks.js +115 -0
- package/build/adapters/gemini-cli/index.d.ts +80 -0
- package/build/adapters/gemini-cli/index.js +483 -0
- package/build/adapters/jetbrains-copilot/config.d.ts +8 -0
- package/build/adapters/jetbrains-copilot/config.js +8 -0
- package/build/adapters/jetbrains-copilot/hooks.d.ts +59 -0
- package/build/adapters/jetbrains-copilot/hooks.js +87 -0
- package/build/adapters/jetbrains-copilot/index.d.ts +31 -0
- package/build/adapters/jetbrains-copilot/index.js +131 -0
- package/build/adapters/kimi/config.d.ts +8 -0
- package/build/adapters/kimi/config.js +8 -0
- package/build/adapters/kimi/hooks.d.ts +28 -0
- package/build/adapters/kimi/hooks.js +34 -0
- package/build/adapters/kimi/index.d.ts +66 -0
- package/build/adapters/kimi/index.js +537 -0
- package/build/adapters/kimi/paths.d.ts +21 -0
- package/build/adapters/kimi/paths.js +52 -0
- package/build/adapters/kimi/usage.d.ts +82 -0
- package/build/adapters/kimi/usage.js +217 -0
- package/build/adapters/kiro/hooks.d.ts +61 -0
- package/build/adapters/kiro/hooks.js +94 -0
- package/build/adapters/kiro/index.d.ts +61 -0
- package/build/adapters/kiro/index.js +301 -0
- package/build/adapters/omp/index.d.ts +65 -0
- package/build/adapters/omp/index.js +182 -0
- package/build/adapters/omp/plugin.d.ts +81 -0
- package/build/adapters/omp/plugin.js +331 -0
- package/build/adapters/omp/usage.d.ts +49 -0
- package/build/adapters/omp/usage.js +110 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +74 -0
- package/build/adapters/openclaw/index.js +439 -0
- package/build/adapters/openclaw/mcp-tools.d.ts +54 -0
- package/build/adapters/openclaw/mcp-tools.js +207 -0
- package/build/adapters/openclaw/plugin.d.ts +141 -0
- package/build/adapters/openclaw/plugin.js +818 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/openclaw/usage.d.ts +34 -0
- package/build/adapters/openclaw/usage.js +52 -0
- package/build/adapters/openclaw/workspace-router.d.ts +29 -0
- package/build/adapters/openclaw/workspace-router.js +64 -0
- package/build/adapters/opencode/config.d.ts +8 -0
- package/build/adapters/opencode/config.js +8 -0
- package/build/adapters/opencode/hooks.d.ts +38 -0
- package/build/adapters/opencode/hooks.js +50 -0
- package/build/adapters/opencode/index.d.ts +144 -0
- package/build/adapters/opencode/index.js +638 -0
- package/build/adapters/opencode/plugin.d.ts +249 -0
- package/build/adapters/opencode/plugin.js +1536 -0
- package/build/adapters/opencode/v2.d.ts +117 -0
- package/build/adapters/opencode/v2.js +224 -0
- package/build/adapters/opencode/zod3tov4.d.ts +1 -0
- package/build/adapters/opencode/zod3tov4.js +132 -0
- package/build/adapters/pi/extension.d.ts +81 -0
- package/build/adapters/pi/extension.js +861 -0
- package/build/adapters/pi/index.d.ts +57 -0
- package/build/adapters/pi/index.js +173 -0
- package/build/adapters/pi/mcp-bridge.d.ts +268 -0
- package/build/adapters/pi/mcp-bridge.js +894 -0
- package/build/adapters/qwen-code/hooks.d.ts +26 -0
- package/build/adapters/qwen-code/hooks.js +29 -0
- package/build/adapters/qwen-code/index.d.ts +40 -0
- package/build/adapters/qwen-code/index.js +361 -0
- package/build/adapters/qwen-code/usage.d.ts +90 -0
- package/build/adapters/qwen-code/usage.js +222 -0
- package/build/adapters/types.d.ts +360 -0
- package/build/adapters/types.js +118 -0
- package/build/adapters/vscode-copilot/config.d.ts +8 -0
- package/build/adapters/vscode-copilot/config.js +8 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +71 -0
- package/build/adapters/vscode-copilot/hooks.js +98 -0
- package/build/adapters/vscode-copilot/index.d.ts +32 -0
- package/build/adapters/vscode-copilot/index.js +222 -0
- package/build/adapters/zed/index.d.ts +40 -0
- package/build/adapters/zed/index.js +163 -0
- package/build/cli.d.ts +34 -0
- package/build/cli.js +1828 -0
- package/build/db-base.d.ts +250 -0
- package/build/db-base.js +756 -0
- package/build/executor.d.ts +72 -0
- package/build/executor.js +678 -0
- package/build/exit-classify.d.ts +19 -0
- package/build/exit-classify.js +12 -0
- package/build/fetch/blocks.d.ts +145 -0
- package/build/fetch/blocks.js +244 -0
- package/build/fetch/extract.d.ts +68 -0
- package/build/fetch/extract.js +123 -0
- package/build/fetch/page-store.d.ts +66 -0
- package/build/fetch/page-store.js +174 -0
- package/build/fetch-cache.d.ts +13 -0
- package/build/fetch-cache.js +15 -0
- package/build/lifecycle.d.ts +124 -0
- package/build/lifecycle.js +278 -0
- package/build/package-identity.d.ts +26 -0
- package/build/package-identity.js +28 -0
- package/build/runPool.d.ts +36 -0
- package/build/runPool.js +51 -0
- package/build/runtime.d.ts +71 -0
- package/build/runtime.js +659 -0
- package/build/search/auto-memory.d.ts +47 -0
- package/build/search/auto-memory.js +170 -0
- package/build/search/ctx-search-schema.d.ts +90 -0
- package/build/search/ctx-search-schema.js +135 -0
- package/build/search/flood-guard.d.ts +57 -0
- package/build/search/flood-guard.js +80 -0
- package/build/search/unified.d.ts +56 -0
- package/build/search/unified.js +104 -0
- package/build/security.d.ts +206 -0
- package/build/security.js +780 -0
- package/build/server.d.ts +240 -0
- package/build/server.js +5034 -0
- package/build/session/analytics.d.ts +773 -0
- package/build/session/analytics.js +2420 -0
- package/build/session/db.d.ts +468 -0
- package/build/session/db.js +1330 -0
- package/build/session/error-classifier.d.ts +87 -0
- package/build/session/error-classifier.js +303 -0
- package/build/session/event-emit.d.ts +48 -0
- package/build/session/event-emit.js +101 -0
- package/build/session/extract.d.ts +333 -0
- package/build/session/extract.js +2688 -0
- package/build/session/model-prices.json +429 -0
- package/build/session/persist-tool-calls.d.ts +54 -0
- package/build/session/persist-tool-calls.js +105 -0
- package/build/session/pricing.d.ts +64 -0
- package/build/session/pricing.js +151 -0
- package/build/session/project-attribution.d.ts +73 -0
- package/build/session/project-attribution.js +245 -0
- package/build/session/purge.d.ts +138 -0
- package/build/session/purge.js +240 -0
- package/build/session/retrieval-marker.d.ts +39 -0
- package/build/session/retrieval-marker.js +65 -0
- package/build/session/snapshot.d.ts +46 -0
- package/build/session/snapshot.js +532 -0
- package/build/store-directory.d.ts +56 -0
- package/build/store-directory.js +254 -0
- package/build/store.d.ts +147 -0
- package/build/store.js +1714 -0
- package/build/truncate.d.ts +57 -0
- package/build/truncate.js +142 -0
- package/build/types.d.ts +117 -0
- package/build/types.js +20 -0
- package/build/util/claude-config.d.ts +32 -0
- package/build/util/claude-config.js +84 -0
- package/build/util/hook-config.d.ts +27 -0
- package/build/util/hook-config.js +76 -0
- package/build/util/jsonc.d.ts +14 -0
- package/build/util/jsonc.js +104 -0
- package/build/util/plugin-cache-integrity.d.ts +51 -0
- package/build/util/plugin-cache-integrity.js +146 -0
- package/build/util/project-dir.d.ts +149 -0
- package/build/util/project-dir.js +340 -0
- package/build/util/sibling-mcp.d.ts +79 -0
- package/build/util/sibling-mcp.js +181 -0
- package/cli.bundle.mjs +1495 -0
- package/configs/antigravity/GEMINI.md +68 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/antigravity-cli/hooks/hooks.json +37 -0
- package/configs/antigravity-cli/hooks.json +37 -0
- package/configs/antigravity-cli/mcp_config.json +10 -0
- package/configs/antigravity-cli/plugin.json +14 -0
- package/configs/antigravity-cli/rules/context-mode.md +77 -0
- package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
- package/configs/claude-code/CLAUDE.md +91 -0
- package/configs/codex/AGENTS.md +99 -0
- package/configs/codex/config.toml +8 -0
- package/configs/codex/hooks.json +47 -0
- package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
- package/configs/copilot-cli/.mcp.json +12 -0
- package/configs/copilot-cli/README.md +47 -0
- package/configs/copilot-cli/hooks.json +41 -0
- package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
- package/configs/cursor/context-mode.mdc +76 -0
- package/configs/cursor/hooks.json +21 -0
- package/configs/cursor/mcp.json +7 -0
- package/configs/gemini-cli/GEMINI.md +88 -0
- package/configs/gemini-cli/mcp.json +7 -0
- package/configs/gemini-cli/settings.json +60 -0
- package/configs/jetbrains-copilot/copilot-instructions.md +80 -0
- package/configs/jetbrains-copilot/hooks.json +16 -0
- package/configs/jetbrains-copilot/mcp.json +7 -0
- package/configs/kilo/AGENTS.md +84 -0
- package/configs/kilo/kilo.json +6 -0
- package/configs/kimi/hooks.json +54 -0
- package/configs/kiro/KIRO.md +84 -0
- package/configs/kiro/agent.json +18 -0
- package/configs/kiro/mcp.json +7 -0
- package/configs/omp/SYSTEM.md +85 -0
- package/configs/omp/mcp.json +7 -0
- package/configs/openclaw/AGENTS.md +87 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/configs/opencode/AGENTS.md +84 -0
- package/configs/opencode/opencode.json +6 -0
- package/configs/pi/AGENTS.md +3 -0
- package/configs/qwen-code/QWEN.md +91 -0
- package/configs/vscode-copilot/copilot-instructions.md +80 -0
- package/configs/vscode-copilot/hooks.json +16 -0
- package/configs/vscode-copilot/mcp.json +7 -0
- package/configs/zed/AGENTS.md +68 -0
- package/hooks/antigravity-cli/payload.mjs +98 -0
- package/hooks/antigravity-cli/posttooluse.mjs +138 -0
- package/hooks/antigravity-cli/pretooluse.mjs +78 -0
- package/hooks/antigravity-cli/stop.mjs +58 -0
- package/hooks/auto-injection.mjs +102 -0
- package/hooks/cache-heal-utils.mjs +379 -0
- package/hooks/codex/platform.mjs +1 -0
- package/hooks/codex/posttooluse.mjs +66 -0
- package/hooks/codex/precompact.mjs +70 -0
- package/hooks/codex/pretooluse.mjs +42 -0
- package/hooks/codex/sessionstart.mjs +121 -0
- package/hooks/codex/stop.mjs +138 -0
- package/hooks/codex/userpromptsubmit.mjs +75 -0
- package/hooks/copilot-cli/posttooluse.mjs +79 -0
- package/hooks/copilot-cli/precompact.mjs +66 -0
- package/hooks/copilot-cli/pretooluse.mjs +41 -0
- package/hooks/copilot-cli/sessionstart.mjs +121 -0
- package/hooks/copilot-cli/stop.mjs +59 -0
- package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
- package/hooks/core/codex-caps.mjs +112 -0
- package/hooks/core/formatters.mjs +356 -0
- package/hooks/core/mcp-ready.mjs +109 -0
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +1050 -0
- package/hooks/core/stdin.mjs +90 -0
- package/hooks/core/tool-naming.mjs +57 -0
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/cursor/hooks.json +31 -0
- package/hooks/cursor/posttooluse.mjs +72 -0
- package/hooks/cursor/pretooluse.mjs +26 -0
- package/hooks/cursor/sessionstart.mjs +97 -0
- package/hooks/cursor/stop.mjs +48 -0
- package/hooks/ensure-deps.mjs +254 -0
- package/hooks/formatters/claude-code.mjs +113 -0
- package/hooks/formatters/cursor.mjs +37 -0
- package/hooks/formatters/gemini-cli.mjs +55 -0
- package/hooks/formatters/vscode-copilot.mjs +55 -0
- package/hooks/gemini-cli/aftermodel.mjs +70 -0
- package/hooks/gemini-cli/aftertool.mjs +61 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/gemini-cli/beforetool.mjs +27 -0
- package/hooks/gemini-cli/precompress.mjs +55 -0
- package/hooks/gemini-cli/sessionstart.mjs +137 -0
- package/hooks/heal-partial-install.mjs +712 -0
- package/hooks/hooks.json +143 -0
- package/hooks/jetbrains-copilot/posttooluse.mjs +61 -0
- package/hooks/jetbrains-copilot/precompact.mjs +55 -0
- package/hooks/jetbrains-copilot/pretooluse.mjs +27 -0
- package/hooks/jetbrains-copilot/sessionstart.mjs +124 -0
- package/hooks/kimi/platform.mjs +1 -0
- package/hooks/kimi/posttooluse.mjs +72 -0
- package/hooks/kimi/precompact.mjs +80 -0
- package/hooks/kimi/pretooluse.mjs +42 -0
- package/hooks/kimi/sessionend.mjs +61 -0
- package/hooks/kimi/sessionstart.mjs +113 -0
- package/hooks/kimi/stop.mjs +132 -0
- package/hooks/kimi/userpromptsubmit.mjs +90 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/posttooluse.mjs +51 -0
- package/hooks/kiro/pretooluse.mjs +64 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/normalize-hooks.mjs +323 -0
- package/hooks/platform-bridge.mjs +340 -0
- package/hooks/posttooluse.mjs +225 -0
- package/hooks/precompact.mjs +100 -0
- package/hooks/pretooluse.mjs +227 -0
- package/hooks/qwen-code/platform.mjs +1 -0
- package/hooks/qwen-code/stop.mjs +168 -0
- package/hooks/routing-block.mjs +105 -0
- package/hooks/run-hook.mjs +95 -0
- package/hooks/security.bundle.mjs +2 -0
- package/hooks/session-attribution.bundle.mjs +1 -0
- package/hooks/session-db.bundle.mjs +145 -0
- package/hooks/session-directive.mjs +520 -0
- package/hooks/session-extract.bundle.mjs +3 -0
- package/hooks/session-helpers.mjs +428 -0
- package/hooks/session-loaders.mjs +448 -0
- package/hooks/session-snapshot.bundle.mjs +32 -0
- package/hooks/sessionstart.mjs +464 -0
- package/hooks/stop.mjs +82 -0
- package/hooks/suppress-stderr.mjs +25 -0
- package/hooks/userpromptsubmit.mjs +108 -0
- package/hooks/vscode-copilot/posttooluse.mjs +61 -0
- package/hooks/vscode-copilot/precompact.mjs +55 -0
- package/hooks/vscode-copilot/pretooluse.mjs +27 -0
- package/hooks/vscode-copilot/sessionstart.mjs +129 -0
- package/openclaw.plugin.json +38 -0
- package/package.json +132 -0
- package/scripts/heal-better-sqlite3.mjs +430 -0
- package/scripts/heal-installed-plugins.mjs +635 -0
- package/scripts/plugin-cache-integrity.mjs +248 -0
- package/scripts/postinstall.mjs +397 -0
- package/server.bundle.mjs +1421 -0
- package/server.js +5 -0
- package/skills/.ignore +7 -0
- package/skills/context-mode/SKILL.md +300 -0
- package/skills/context-mode/references/anti-patterns.md +283 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
- package/skills/ctx-doctor/SKILL.md +22 -0
- package/skills/ctx-index/SKILL.md +46 -0
- package/skills/ctx-insight/SKILL.md +24 -0
- package/skills/ctx-purge/SKILL.md +49 -0
- package/skills/ctx-search/SKILL.md +35 -0
- package/skills/ctx-stats/SKILL.md +26 -0
- package/skills/ctx-upgrade/SKILL.md +31 -0
- package/start.mjs +616 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/qwen-code/usage — per-turn token capture from the session JSONL.
|
|
3
|
+
*
|
|
4
|
+
* Qwen Code is a Gemini-CLI fork and normalizes EVERY backend (Gemini-native,
|
|
5
|
+
* OpenAI-compat/DashScope, Anthropic) to the same canonical token shape:
|
|
6
|
+
* `GenerateContentResponseUsageMetadata` { promptTokenCount, candidatesTokenCount,
|
|
7
|
+
* cachedContentTokenCount, thoughtsTokenCount, totalTokenCount }
|
|
8
|
+
* (matrix §1: turn.ts:96,417 + converter.ts:1145-1148). That metadata is
|
|
9
|
+
* persisted, per API call, into the session record file as a `ChatRecord`
|
|
10
|
+
* carrying `.usageMetadata` + `.model`
|
|
11
|
+
* (refs: packages/core/src/services/chatRecordingService.ts:259,261,919 file at
|
|
12
|
+
* ~/.qwen/tmp/<project_id>/chats/<sessionId>.jsonl — :451 location comment,
|
|
13
|
+
* :600,628-629 path build).
|
|
14
|
+
*
|
|
15
|
+
* CRITICAL (matrix §4): qwen-code's hook payloads carry tool I/O ONLY — token
|
|
16
|
+
* usage is unreachable through the hook stream (grep of hookEventHandler.ts /
|
|
17
|
+
* hookSystem.ts / toolHookTriggers.ts for token|usageMetadata|usage → zero
|
|
18
|
+
* matches). The ONLY live capture path is a tail of the session JSONL. This
|
|
19
|
+
* module is therefore the JSONL-tail counterpart to claude-code's
|
|
20
|
+
* `extractTranscriptUsageSince` (src/session/extract.ts) — same cursor-gated,
|
|
21
|
+
* char-algorithmic, NO-regex parse, same `buildAgentUsageEvent` emission path.
|
|
22
|
+
*
|
|
23
|
+
* Per matrix §3 each ChatRecord.usageMetadata is INCREMENTAL per API call
|
|
24
|
+
* (cumulative session totals are derived downstream via += in
|
|
25
|
+
* uiTelemetry.ts:237-241), so summing the NEW records since the cursor yields
|
|
26
|
+
* the exact billed delta with no double-count.
|
|
27
|
+
*
|
|
28
|
+
* No native USD — cost_usd is derived from the pricing catalog inside
|
|
29
|
+
* buildAgentUsageEvent (native_cost_usd omitted). Pure, null-safe, NO regex.
|
|
30
|
+
*/
|
|
31
|
+
import { createHash } from "node:crypto";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { platform } from "node:os";
|
|
34
|
+
import { buildAgentUsageEvent } from "../../session/extract.js";
|
|
35
|
+
/** Floor-and-clamp a token field to a non-negative integer (mirrors omp/usage). */
|
|
36
|
+
function tokenNum(v) {
|
|
37
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
38
|
+
return 0;
|
|
39
|
+
const n = Math.floor(v);
|
|
40
|
+
return n > 0 ? n : 0;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse ONE qwen `ChatRecord` into the `buildAgentUsageEvent` input shape, or
|
|
44
|
+
* null when the record carries no usage / sums to zero.
|
|
45
|
+
*
|
|
46
|
+
* Mapping → builder shape (AgentUsageCounts):
|
|
47
|
+
* promptTokenCount → input_tokens
|
|
48
|
+
* candidatesTokenCount → output_tokens
|
|
49
|
+
* thoughtsTokenCount → ADDED into output_tokens (Gemini-lineage bills
|
|
50
|
+
* reasoning/thoughts as output — same fold as
|
|
51
|
+
* parseGeminiUsage in src/session/extract.ts)
|
|
52
|
+
* cachedContentTokenCount → cache_read_tokens (when present)
|
|
53
|
+
* model_id → ChatRecord.model
|
|
54
|
+
*
|
|
55
|
+
* No native cost — native_cost_usd omitted (catalog-derived). NO regex.
|
|
56
|
+
*/
|
|
57
|
+
export function parseQwenUsage(record) {
|
|
58
|
+
if (!record || typeof record !== "object" || Array.isArray(record))
|
|
59
|
+
return null;
|
|
60
|
+
const rec = record;
|
|
61
|
+
const um = rec.usageMetadata;
|
|
62
|
+
if (!um || typeof um !== "object")
|
|
63
|
+
return null;
|
|
64
|
+
const usage = um;
|
|
65
|
+
const input = tokenNum(usage.promptTokenCount);
|
|
66
|
+
const candidates = tokenNum(usage.candidatesTokenCount);
|
|
67
|
+
const thoughts = tokenNum(usage.thoughtsTokenCount);
|
|
68
|
+
const cached = tokenNum(usage.cachedContentTokenCount);
|
|
69
|
+
// Gemini-lineage bills reasoning (thoughts) as output tokens — fold into output.
|
|
70
|
+
const output = candidates + thoughts;
|
|
71
|
+
// All token fields zero → not a billable record. buildAgentUsageEvent would
|
|
72
|
+
// also reject this, but short-circuit keeps the contract explicit.
|
|
73
|
+
if (input <= 0 && output <= 0 && cached <= 0)
|
|
74
|
+
return null;
|
|
75
|
+
const model_id = typeof rec.model === "string" ? rec.model : "";
|
|
76
|
+
return {
|
|
77
|
+
model_id,
|
|
78
|
+
input_tokens: input,
|
|
79
|
+
output_tokens: output,
|
|
80
|
+
cache_creation_tokens: 0, // qwen exposes no cache-creation field
|
|
81
|
+
cache_read_tokens: cached,
|
|
82
|
+
native_cost_usd: null, // catalog-derived (no native cost on qwen records)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Stable cursor identity for a ChatRecord: prefer `id`, fall back to `messageId`. */
|
|
86
|
+
function recordId(rec) {
|
|
87
|
+
if (typeof rec.id === "string" && rec.id.length > 0)
|
|
88
|
+
return rec.id;
|
|
89
|
+
if (typeof rec.messageId === "string" && rec.messageId.length > 0)
|
|
90
|
+
return rec.messageId;
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Cursor-aware tail of the qwen session JSONL. Emits one priced `agent_usage`
|
|
95
|
+
* event PER distinct model across the records NEW since `cursor`, so re-reading
|
|
96
|
+
* the (append-only, ever-growing) JSONL each Stop never double-counts.
|
|
97
|
+
*
|
|
98
|
+
* - cursor null/empty → process ALL records.
|
|
99
|
+
* - cursor found → process records STRICTLY AFTER it.
|
|
100
|
+
* - cursor set but NOT found → compaction/rotation dropped it: bounded
|
|
101
|
+
* fallback processes ONLY THE LAST record (never re-emit full history).
|
|
102
|
+
*
|
|
103
|
+
* `cursor` returns the id of the LAST id-bearing record seen (whether or not it
|
|
104
|
+
* carried usage), so the next call resumes exactly past it. When no record
|
|
105
|
+
* carries an id, the input cursor is returned unchanged.
|
|
106
|
+
*
|
|
107
|
+
* One linear walk, JSON.parse per line, NO regex — mirrors
|
|
108
|
+
* extractTranscriptUsageSince's structure exactly.
|
|
109
|
+
*/
|
|
110
|
+
export function extractQwenUsageSince(jsonlText, cursor) {
|
|
111
|
+
const inputCursor = typeof cursor === "string" && cursor.length > 0 ? cursor : null;
|
|
112
|
+
if (typeof jsonlText !== "string" || jsonlText.length === 0) {
|
|
113
|
+
return { events: [], cursor: inputCursor };
|
|
114
|
+
}
|
|
115
|
+
const rows = [];
|
|
116
|
+
let start = 0;
|
|
117
|
+
for (let i = 0; i <= jsonlText.length; i++) {
|
|
118
|
+
if (i !== jsonlText.length && jsonlText.charCodeAt(i) !== 10 /* \n */)
|
|
119
|
+
continue;
|
|
120
|
+
const line = jsonlText.slice(start, i).trim();
|
|
121
|
+
start = i + 1;
|
|
122
|
+
if (line.length === 0)
|
|
123
|
+
continue;
|
|
124
|
+
let obj;
|
|
125
|
+
try {
|
|
126
|
+
const p = JSON.parse(line);
|
|
127
|
+
if (!p || typeof p !== "object" || Array.isArray(p))
|
|
128
|
+
continue;
|
|
129
|
+
obj = p;
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
rows.push({ id: recordId(obj), counts: parseQwenUsage(obj) });
|
|
135
|
+
}
|
|
136
|
+
if (rows.length === 0)
|
|
137
|
+
return { events: [], cursor: inputCursor };
|
|
138
|
+
// Cursor always advances to the last id-bearing record's id (or stays as the
|
|
139
|
+
// input cursor when no record carries an id).
|
|
140
|
+
let lastId = inputCursor;
|
|
141
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
142
|
+
if (rows[i].id !== null) {
|
|
143
|
+
lastId = rows[i].id;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Select the slice to sum.
|
|
148
|
+
let slice;
|
|
149
|
+
if (inputCursor === null) {
|
|
150
|
+
slice = rows; // all records
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
let foundAt = -1;
|
|
154
|
+
for (let i = 0; i < rows.length; i++) {
|
|
155
|
+
if (rows[i].id === inputCursor) {
|
|
156
|
+
foundAt = i;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (foundAt >= 0) {
|
|
161
|
+
slice = rows.slice(foundAt + 1); // strictly after the cursor
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// Compaction/rotation: cursor fell off the front. Bounded fallback — last
|
|
165
|
+
// record only. Never re-emit the whole history.
|
|
166
|
+
slice = rows.slice(rows.length - 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Sum the selected records per model, then emit via the shared builder.
|
|
170
|
+
const sums = new Map();
|
|
171
|
+
for (const row of slice) {
|
|
172
|
+
const c = row.counts;
|
|
173
|
+
if (!c)
|
|
174
|
+
continue;
|
|
175
|
+
const cur = sums.get(c.model_id) ?? { input: 0, output: 0, cacheCreate: 0, cacheRead: 0 };
|
|
176
|
+
cur.input += c.input_tokens;
|
|
177
|
+
cur.output += c.output_tokens;
|
|
178
|
+
cur.cacheCreate += c.cache_creation_tokens;
|
|
179
|
+
cur.cacheRead += c.cache_read_tokens;
|
|
180
|
+
sums.set(c.model_id, cur);
|
|
181
|
+
}
|
|
182
|
+
const events = [];
|
|
183
|
+
for (const [model_id, s] of sums) {
|
|
184
|
+
const ev = buildAgentUsageEvent({
|
|
185
|
+
model_id,
|
|
186
|
+
input_tokens: s.input,
|
|
187
|
+
output_tokens: s.output,
|
|
188
|
+
cache_creation_tokens: s.cacheCreate,
|
|
189
|
+
cache_read_tokens: s.cacheRead,
|
|
190
|
+
});
|
|
191
|
+
if (ev)
|
|
192
|
+
events.push(ev);
|
|
193
|
+
}
|
|
194
|
+
return { events, cursor: lastId };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Hash a project root into qwen-code's `<project_id>` directory segment.
|
|
198
|
+
*
|
|
199
|
+
* EXACT port of qwen's `getProjectHash`
|
|
200
|
+
* (refs/platforms/qwen-code/packages/core/src/utils/paths.ts:262 —
|
|
201
|
+
* `crypto.createHash('sha256').update(normalizedPath).digest('hex')`). On
|
|
202
|
+
* Windows qwen lowercases the path first (case-insensitive FS); we mirror that
|
|
203
|
+
* so a hook running on win32 resolves the same tmp dir qwen itself wrote.
|
|
204
|
+
* Pure, deterministic, NO regex.
|
|
205
|
+
*/
|
|
206
|
+
export function qwenProjectHash(projectRoot) {
|
|
207
|
+
const normalized = platform() === "win32" ? projectRoot.toLowerCase() : projectRoot;
|
|
208
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Build the canonical session JSONL path qwen-code writes its ChatRecords to:
|
|
212
|
+
* <qwenHome>/tmp/<sha256(projectRoot)>/chats/<sessionId>.jsonl
|
|
213
|
+
* (refs chatRecordingService.ts:451 location + storage.ts:316-320
|
|
214
|
+
* getProjectTempDir → getGlobalTempDir(<qwenHome>/tmp) + getProjectHash).
|
|
215
|
+
*
|
|
216
|
+
* `qwenHome` is normally `<homedir>/.qwen`. Pure path join — does NOT touch the
|
|
217
|
+
* FS, so it is fully unit-testable; existence probing + the glob fallback live
|
|
218
|
+
* in the Stop hook (which cannot import this TS at runtime). NO regex.
|
|
219
|
+
*/
|
|
220
|
+
export function qwenChatJsonlPath(qwenHome, projectRoot, sessionId) {
|
|
221
|
+
return join(qwenHome, "tmp", qwenProjectHash(projectRoot), "chats", `${sessionId}.jsonl`);
|
|
222
|
+
}
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adapters/types — Platform adapter interface for multi-platform hook support.
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract that each platform adapter must implement.
|
|
5
|
+
* Three paradigms exist across supported platforms:
|
|
6
|
+
* A) JSON stdin/stdout — Claude Code, Gemini/Qwen family CLIs, Copilot/Codex/Kimi,
|
|
7
|
+
* Cursor, Kiro, Antigravity CLI (`agy`)
|
|
8
|
+
* B) TS Plugin Functions — OpenCode, KiloCode, OpenClaw
|
|
9
|
+
* C) MCP-only (no hooks) — Antigravity IDE, Zed, Pi/OMP MCP-only paths
|
|
10
|
+
*
|
|
11
|
+
* The MCP server layer is 100% portable and needs no adapter.
|
|
12
|
+
* Only the hook layer requires platform-specific adapters.
|
|
13
|
+
*/
|
|
14
|
+
export type HookParadigm = "json-stdio" | "ts-plugin" | "mcp-only";
|
|
15
|
+
export interface PlatformCapabilities {
|
|
16
|
+
/** Platform supports PreToolUse / BeforeTool / tool.execute.before hooks. */
|
|
17
|
+
preToolUse: boolean;
|
|
18
|
+
/** Platform supports PostToolUse / AfterTool / tool.execute.after hooks. */
|
|
19
|
+
postToolUse: boolean;
|
|
20
|
+
/** Platform supports PreCompact / PreCompress / session.compacting hooks. */
|
|
21
|
+
preCompact: boolean;
|
|
22
|
+
/** Platform supports SessionStart / session.created hooks. */
|
|
23
|
+
sessionStart: boolean;
|
|
24
|
+
/** Platform allows modifying tool input arguments via hooks. */
|
|
25
|
+
canModifyArgs: boolean;
|
|
26
|
+
/** Platform allows modifying tool output via PostToolUse hooks. */
|
|
27
|
+
canModifyOutput: boolean;
|
|
28
|
+
/** Platform allows injecting context during session start or compaction. */
|
|
29
|
+
canInjectSessionContext: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Normalized PreToolUse event — platform-agnostic representation. */
|
|
32
|
+
export interface PreToolUseEvent {
|
|
33
|
+
/** Tool name being invoked (e.g., "Bash", "Read", "WebFetch"). */
|
|
34
|
+
toolName: string;
|
|
35
|
+
/** Tool input arguments as key-value pairs. */
|
|
36
|
+
toolInput: Record<string, unknown>;
|
|
37
|
+
/** Session ID extracted by the adapter. */
|
|
38
|
+
sessionId: string;
|
|
39
|
+
/** Project directory (if available). */
|
|
40
|
+
projectDir?: string;
|
|
41
|
+
/** Raw platform-specific input (for passthrough if needed). */
|
|
42
|
+
raw: unknown;
|
|
43
|
+
}
|
|
44
|
+
/** Normalized PostToolUse event — platform-agnostic representation. */
|
|
45
|
+
export interface PostToolUseEvent {
|
|
46
|
+
/** Tool name that was invoked. */
|
|
47
|
+
toolName: string;
|
|
48
|
+
/** Tool input arguments. */
|
|
49
|
+
toolInput: Record<string, unknown>;
|
|
50
|
+
/** Tool output/response (if available). */
|
|
51
|
+
toolOutput?: string;
|
|
52
|
+
/** Whether the tool call resulted in an error. */
|
|
53
|
+
isError?: boolean;
|
|
54
|
+
/** Session ID extracted by the adapter. */
|
|
55
|
+
sessionId: string;
|
|
56
|
+
/** Project directory (if available). */
|
|
57
|
+
projectDir?: string;
|
|
58
|
+
/** Raw platform-specific input. */
|
|
59
|
+
raw: unknown;
|
|
60
|
+
}
|
|
61
|
+
/** Normalized PreCompact event. */
|
|
62
|
+
export interface PreCompactEvent {
|
|
63
|
+
/** Session ID. */
|
|
64
|
+
sessionId: string;
|
|
65
|
+
/** Project directory (if available). */
|
|
66
|
+
projectDir?: string;
|
|
67
|
+
/** Raw platform-specific input. */
|
|
68
|
+
raw: unknown;
|
|
69
|
+
}
|
|
70
|
+
/** Normalized SessionStart event. */
|
|
71
|
+
export interface SessionStartEvent {
|
|
72
|
+
/** Session ID. */
|
|
73
|
+
sessionId: string;
|
|
74
|
+
/** Lifecycle source: fresh start, compaction, resume, or clear. */
|
|
75
|
+
source: "startup" | "compact" | "resume" | "clear";
|
|
76
|
+
/** Project directory (if available). */
|
|
77
|
+
projectDir?: string;
|
|
78
|
+
/** Raw platform-specific input. */
|
|
79
|
+
raw: unknown;
|
|
80
|
+
}
|
|
81
|
+
/** Response from PreToolUse hook — can block, modify, inject context, or pass through. */
|
|
82
|
+
export interface PreToolUseResponse {
|
|
83
|
+
/**
|
|
84
|
+
* "allow" = pass through (no action)
|
|
85
|
+
* "deny" = block tool execution
|
|
86
|
+
* "modify" = change input args
|
|
87
|
+
* "context" = inject additional context (soft guidance)
|
|
88
|
+
* "ask" = prompt user for confirmation (security policy match)
|
|
89
|
+
*/
|
|
90
|
+
decision: "allow" | "deny" | "modify" | "context" | "ask";
|
|
91
|
+
/** Reason for denial (shown to the model). */
|
|
92
|
+
reason?: string;
|
|
93
|
+
/** Modified tool input (only when decision = "modify"). */
|
|
94
|
+
updatedInput?: Record<string, unknown>;
|
|
95
|
+
/** Additional context to inject (only when decision = "context"). */
|
|
96
|
+
additionalContext?: string;
|
|
97
|
+
}
|
|
98
|
+
/** Response from PostToolUse hook — can inject context or modify output. */
|
|
99
|
+
export interface PostToolUseResponse {
|
|
100
|
+
/** Additional context to inject after tool output. */
|
|
101
|
+
additionalContext?: string;
|
|
102
|
+
/** Modified tool output (if platform supports it). */
|
|
103
|
+
updatedOutput?: string;
|
|
104
|
+
}
|
|
105
|
+
/** Response from PreCompact hook — injects context before compaction. */
|
|
106
|
+
export interface PreCompactResponse {
|
|
107
|
+
/** Context to preserve across compaction. */
|
|
108
|
+
context?: string;
|
|
109
|
+
}
|
|
110
|
+
/** Response from SessionStart hook — injects context at session start. */
|
|
111
|
+
export interface SessionStartResponse {
|
|
112
|
+
/** Context to inject at session start. */
|
|
113
|
+
context?: string;
|
|
114
|
+
}
|
|
115
|
+
/** A single hook entry in platform configuration. */
|
|
116
|
+
export interface HookEntry {
|
|
117
|
+
/** Tool matcher pattern (empty = match all). */
|
|
118
|
+
matcher: string;
|
|
119
|
+
/** Hook commands/handlers to execute. */
|
|
120
|
+
hooks: Array<{
|
|
121
|
+
type: string;
|
|
122
|
+
command: string;
|
|
123
|
+
}>;
|
|
124
|
+
}
|
|
125
|
+
/** Hook registration map — maps hook types to their entries. */
|
|
126
|
+
export type HookRegistration = Record<string, HookEntry[]>;
|
|
127
|
+
/**
|
|
128
|
+
* HookAdapter — contract for platform-specific hook implementations.
|
|
129
|
+
*
|
|
130
|
+
* Each supported platform (Claude Code, Gemini CLI, OpenCode, etc.)
|
|
131
|
+
* provides an adapter that normalizes its hook I/O into a common format.
|
|
132
|
+
*/
|
|
133
|
+
export interface HookAdapter {
|
|
134
|
+
/** Human-readable platform name (e.g., "Claude Code", "Gemini CLI"). */
|
|
135
|
+
readonly name: string;
|
|
136
|
+
/** Hook I/O paradigm used by this platform. */
|
|
137
|
+
readonly paradigm: HookParadigm;
|
|
138
|
+
/** What this platform supports. */
|
|
139
|
+
readonly capabilities: PlatformCapabilities;
|
|
140
|
+
/** Parse raw PreToolUse input into normalized form. */
|
|
141
|
+
parsePreToolUseInput(raw: unknown): PreToolUseEvent;
|
|
142
|
+
/** Parse raw PostToolUse input into normalized form. */
|
|
143
|
+
parsePostToolUseInput(raw: unknown): PostToolUseEvent;
|
|
144
|
+
/** Parse raw PreCompact input (optional — not all platforms support it). */
|
|
145
|
+
parsePreCompactInput?(raw: unknown): PreCompactEvent;
|
|
146
|
+
/** Parse raw SessionStart input (optional — not all platforms support it). */
|
|
147
|
+
parseSessionStartInput?(raw: unknown): SessionStartEvent;
|
|
148
|
+
/** Format a PreToolUse response into platform-specific output. */
|
|
149
|
+
formatPreToolUseResponse(response: PreToolUseResponse): unknown;
|
|
150
|
+
/** Format a PostToolUse response into platform-specific output. */
|
|
151
|
+
formatPostToolUseResponse(response: PostToolUseResponse): unknown;
|
|
152
|
+
/** Format a PreCompact response into platform-specific output. */
|
|
153
|
+
formatPreCompactResponse?(response: PreCompactResponse): unknown;
|
|
154
|
+
/** Format a SessionStart response into platform-specific output. */
|
|
155
|
+
formatSessionStartResponse?(response: SessionStartResponse): unknown;
|
|
156
|
+
/** Path to the platform's settings file (e.g., ~/.claude/settings.json). */
|
|
157
|
+
getSettingsPath(): string;
|
|
158
|
+
/**
|
|
159
|
+
* Directory where session data is stored.
|
|
160
|
+
*
|
|
161
|
+
* NOTE — C2 narrowing (2026-05): this is the ONLY storage-path concern an
|
|
162
|
+
* adapter exposes. Per-project DB paths are derived by callers via
|
|
163
|
+
* `resolveSessionDbPath({ projectDir, sessionsDir: adapter.getSessionDir() })`
|
|
164
|
+
* (see `src/session/db.ts`). Per-project events.md paths follow the same
|
|
165
|
+
* `<sessionDir>/<hash><suffix>-events.md` shape and are computed inline at
|
|
166
|
+
* the small number of call sites that need them (server.ts, hooks).
|
|
167
|
+
*/
|
|
168
|
+
getSessionDir(): string;
|
|
169
|
+
/**
|
|
170
|
+
* Platform config directory.
|
|
171
|
+
*
|
|
172
|
+
* Contract: ALWAYS returns an absolute path. Never returns a relative
|
|
173
|
+
* segment, never returns an empty string. This eliminates the leaky-seam
|
|
174
|
+
* where callers could not tell whether the return needed further resolution.
|
|
175
|
+
*
|
|
176
|
+
* Resolution rules:
|
|
177
|
+
* - Home-rooted platforms (claude-code, codex, qwen, gemini, antigravity,
|
|
178
|
+
* zed, opencode, …) return paths under `homedir()` / XDG / APPDATA.
|
|
179
|
+
* - Project-scoped platforms (cursor → `.cursor`, vscode-copilot &
|
|
180
|
+
* jetbrains-copilot → `.github`, kiro → `.kiro`, openclaw → project root)
|
|
181
|
+
* resolve their segment against the supplied `projectDir`. When
|
|
182
|
+
* `projectDir` is omitted, `process.cwd()` is used as the fallback.
|
|
183
|
+
*
|
|
184
|
+
* @param projectDir Optional project root used to resolve project-scoped
|
|
185
|
+
* adapters. Ignored by home-rooted adapters.
|
|
186
|
+
*/
|
|
187
|
+
getConfigDir(projectDir?: string): string;
|
|
188
|
+
/**
|
|
189
|
+
* Names of platform-native instruction/rule files that act as the
|
|
190
|
+
* project's "user CLAUDE.md equivalent" (e.g., ["CLAUDE.md"],
|
|
191
|
+
* ["AGENTS.md"], ["GEMINI.md"]). Auto-memory scans for these in the
|
|
192
|
+
* project root and config dir, and rule-detection emits "rule" events
|
|
193
|
+
* when they are read.
|
|
194
|
+
*/
|
|
195
|
+
getInstructionFiles(): string[];
|
|
196
|
+
/**
|
|
197
|
+
* Directory where persistent per-user memory is stored
|
|
198
|
+
* (e.g., ~/.claude/memory, ~/.codex/memories). Auto-memory scans
|
|
199
|
+
* *.md files in this directory.
|
|
200
|
+
*
|
|
201
|
+
* When `projectDir` is supplied, the path MUST be project-scoped (issue
|
|
202
|
+
* #663) so two projects running in parallel cannot read each other's
|
|
203
|
+
* memory. Adapters scope via `hashProjectDirCanonical(projectDir)`.
|
|
204
|
+
* Callers that pre-date this contract may omit `projectDir`; in that
|
|
205
|
+
* case the unscoped legacy path is returned.
|
|
206
|
+
*/
|
|
207
|
+
getMemoryDir(projectDir?: string): string;
|
|
208
|
+
/** Generate hook registration config for this platform. */
|
|
209
|
+
generateHookConfig(pluginRoot: string): HookRegistration;
|
|
210
|
+
/** Read current platform settings. */
|
|
211
|
+
readSettings(): Record<string, unknown> | null;
|
|
212
|
+
/** Write platform settings. */
|
|
213
|
+
writeSettings(settings: Record<string, unknown>): void;
|
|
214
|
+
/** Validate that hooks are properly configured for this platform. */
|
|
215
|
+
validateHooks(pluginRoot: string): DiagnosticResult[];
|
|
216
|
+
/**
|
|
217
|
+
* Adapter-defined per-platform health checks (Algo-D1).
|
|
218
|
+
*
|
|
219
|
+
* OPTIONAL. Adapters that don't override return nothing — they don't
|
|
220
|
+
* have this class of check today. claude-code overrides with hook-script
|
|
221
|
+
* existence checks that join `pluginRoot + scriptName` directly via
|
|
222
|
+
* `existsSync`, so doctor never round-trips through a regex on a hook
|
|
223
|
+
* command (the #548 root cause).
|
|
224
|
+
*
|
|
225
|
+
* Adapter #16 with hook scripts inherits the contract by overriding;
|
|
226
|
+
* adapter #17 without hook scripts simply doesn't override. The doctor
|
|
227
|
+
* iterates `adapter.getHealthChecks?.(pluginRoot) ?? []` and renders
|
|
228
|
+
* each — no per-adapter wiring in the doctor body.
|
|
229
|
+
*/
|
|
230
|
+
getHealthChecks?(pluginRoot: string): readonly HealthCheck[];
|
|
231
|
+
/** Check if the plugin is registered/enabled on this platform. */
|
|
232
|
+
checkPluginRegistration(): DiagnosticResult;
|
|
233
|
+
/**
|
|
234
|
+
* Get the installed version from this platform's registry/marketplace, or
|
|
235
|
+
* "standalone" when no platform-owned plugin version exists.
|
|
236
|
+
*/
|
|
237
|
+
getInstalledVersion(): string;
|
|
238
|
+
/** Configure all hooks for this platform. Returns change descriptions. */
|
|
239
|
+
configureAllHooks(pluginRoot: string): string[];
|
|
240
|
+
/** Backup platform settings before modification. Returns backup path or null. */
|
|
241
|
+
backupSettings(): string | null;
|
|
242
|
+
/** Set executable permissions on hook scripts. Returns paths that were set. */
|
|
243
|
+
setHookPermissions(pluginRoot: string): string[];
|
|
244
|
+
/** Update platform's plugin registry to point to given path and version. */
|
|
245
|
+
updatePluginRegistry(pluginRoot: string, version: string): void;
|
|
246
|
+
}
|
|
247
|
+
/** Result from a platform-specific diagnostic check. */
|
|
248
|
+
export interface DiagnosticResult {
|
|
249
|
+
/** What was checked. */
|
|
250
|
+
check: string;
|
|
251
|
+
/** Pass, fail, or warning. */
|
|
252
|
+
status: "pass" | "fail" | "warn";
|
|
253
|
+
/** Human-readable message. */
|
|
254
|
+
message: string;
|
|
255
|
+
/** Suggested fix command (if applicable). */
|
|
256
|
+
fix?: string;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Adapter-defined health check (Algo-D1).
|
|
260
|
+
*
|
|
261
|
+
* Lighter-weight than `DiagnosticResult`: adapters declare a name and a
|
|
262
|
+
* synchronous `check()` thunk. The doctor renders the result. The
|
|
263
|
+
* thunk-style intentionally avoids forcing adapters into async — the
|
|
264
|
+
* existsSync probe used by claude-code is sync and the doctor invokes it
|
|
265
|
+
* directly without an `await`. Adapters needing async work return a
|
|
266
|
+
* pre-resolved status (the check ran at thunk-creation time) or extend
|
|
267
|
+
* `validateHooks()` instead.
|
|
268
|
+
*/
|
|
269
|
+
export interface HealthCheck {
|
|
270
|
+
/** Human-readable check title (e.g. "Hook script exists: pretooluse.mjs"). */
|
|
271
|
+
readonly name: string;
|
|
272
|
+
/** Synchronous check thunk. Returns OK or FAIL with optional detail. */
|
|
273
|
+
check(): {
|
|
274
|
+
status: "OK" | "FAIL";
|
|
275
|
+
detail?: string;
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Build a cross-platform `node <script>` command string.
|
|
280
|
+
*
|
|
281
|
+
* Fixes two Windows bugs:
|
|
282
|
+
* #369 — Bare `node` fails on Windows Git Bash (MSYS) because PATH
|
|
283
|
+
* resolution is unreliable. Uses `process.execPath` instead.
|
|
284
|
+
* #372 — MSYS rewrites absolute paths on non-C: drives (e.g.
|
|
285
|
+
* `C:\Users\...` → `D:\c\Users\...`). Forward slashes +
|
|
286
|
+
* double-quoting prevents the translation.
|
|
287
|
+
*
|
|
288
|
+
* Safe on macOS/Linux — quoting and forward slashes are no-ops there.
|
|
289
|
+
*/
|
|
290
|
+
export declare function buildNodeCommand(scriptPath: string, opts?: {
|
|
291
|
+
platform?: string;
|
|
292
|
+
jsRuntime?: string;
|
|
293
|
+
}): string;
|
|
294
|
+
/**
|
|
295
|
+
* Build a cross-platform hook spawn command using the resolved JS runtime
|
|
296
|
+
* (issue #738). Identical wire-format to {@link buildNodeCommand} — two
|
|
297
|
+
* double-quoted, forward-slashed tokens separated by whitespace — so it
|
|
298
|
+
* round-trips through {@link parseNodeCommand} unchanged.
|
|
299
|
+
*
|
|
300
|
+
* The only difference is the runtime path: when a Bun ≥1.0 install is
|
|
301
|
+
* detected at process start, that path is used in place of `process.execPath`.
|
|
302
|
+
* Hooks run end-to-end in pure JS (no native modules) so swapping the
|
|
303
|
+
* runtime is a no-op for output but cuts ~40-60ms of Node cold-start per
|
|
304
|
+
* tool call.
|
|
305
|
+
*
|
|
306
|
+
* Why a SEPARATE helper instead of repurposing {@link buildNodeCommand}:
|
|
307
|
+
* `buildNodeCommand` is also called by openclaw plugin (doctor / upgrade
|
|
308
|
+
* command suggestions in `src/adapters/openclaw/plugin.ts`). Those CLI
|
|
309
|
+
* targets MUST stay on Node because they load better-sqlite3, which has
|
|
310
|
+
* no Bun-compatible prebuild yet (#543). Keeping the two helpers separate
|
|
311
|
+
* makes the audit trivial: anything emitting a hook spawn command uses
|
|
312
|
+
* `buildHookRuntimeCommand`; anything emitting a user-visible CLI command
|
|
313
|
+
* stays on `buildNodeCommand`.
|
|
314
|
+
*
|
|
315
|
+
* `opts.platform` is forwarded to {@link isInProcessPluginPlatform} so the
|
|
316
|
+
* existing opencode/kilo in-process JS-runtime substitution still works
|
|
317
|
+
* (those platforms inject their own runtime via `opts.jsRuntime`).
|
|
318
|
+
*/
|
|
319
|
+
export declare function buildHookRuntimeCommand(scriptPath: string, opts?: {
|
|
320
|
+
platform?: string;
|
|
321
|
+
jsRuntime?: string;
|
|
322
|
+
}): string;
|
|
323
|
+
/**
|
|
324
|
+
* Strict inverse of `buildNodeCommand`.
|
|
325
|
+
*
|
|
326
|
+
* Returns `{ nodePath, scriptPath }` ONLY when `cmd` could have been
|
|
327
|
+
* produced by `buildNodeCommand` — i.e. exactly two double-quoted args
|
|
328
|
+
* separated by whitespace. Anything else (bare `node …`, single quotes,
|
|
329
|
+
* unquoted ambiguous input, CLI dispatcher entries) returns `null`.
|
|
330
|
+
*
|
|
331
|
+
* Why strict: the legacy `\S+\.mjs` fallback in
|
|
332
|
+
* `src/util/hook-config.ts:24` and the two-step regex in
|
|
333
|
+
* `src/adapters/claude-code/hooks.ts:178` silently grabbed the path tail
|
|
334
|
+
* after the last whitespace whenever the host wire-format dropped quotes,
|
|
335
|
+
* producing the #548 doubled-path FAIL when `pluginRoot` contained
|
|
336
|
+
* spaces (e.g. `C:\Users\High Ground Services\…`). A canonical inverse
|
|
337
|
+
* lets every emit (`buildNodeCommand`) round-trip through every parse
|
|
338
|
+
* (`parseNodeCommand`) without inventing fallbacks. Adapter #16 inherits
|
|
339
|
+
* the contract by importing one module.
|
|
340
|
+
*/
|
|
341
|
+
export declare function parseNodeCommand(cmd: string): {
|
|
342
|
+
nodePath: string;
|
|
343
|
+
scriptPath: string;
|
|
344
|
+
} | null;
|
|
345
|
+
/** Known JS runtime binary names (base filename without extension). */
|
|
346
|
+
export declare const JS_RUNTIMES: ReadonlySet<string>;
|
|
347
|
+
/** Platforms where context-mode runs as an in-process TS plugin (not MCP stdio). */
|
|
348
|
+
export declare const IN_PROCESS_PLUGIN_PLATFORMS: ReadonlySet<string>;
|
|
349
|
+
export declare function isInProcessPluginPlatform(p: string | undefined): boolean;
|
|
350
|
+
/** Supported platform identifiers. */
|
|
351
|
+
export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "kilo" | "openclaw" | "codex" | "vscode-copilot" | "jetbrains-copilot" | "copilot-cli" | "cursor" | "antigravity" | "antigravity-cli" | "kiro" | "pi" | "omp" | "kimi" | "zed" | "qwen-code" | "unknown";
|
|
352
|
+
/** Detection signal used to identify which platform is running. */
|
|
353
|
+
export interface DetectionSignal {
|
|
354
|
+
/** Platform identifier. */
|
|
355
|
+
platform: PlatformId;
|
|
356
|
+
/** Confidence: env var match > config dir match > fallback. */
|
|
357
|
+
confidence: "high" | "medium" | "low";
|
|
358
|
+
/** How it was detected. */
|
|
359
|
+
reason: string;
|
|
360
|
+
}
|