@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,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./platform.mjs";
|
|
3
|
+
import "../suppress-stderr.mjs";
|
|
4
|
+
import "../ensure-deps.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Qwen Code Stop hook — record turn-end state + capture per-turn token cost.
|
|
7
|
+
*
|
|
8
|
+
* Stop fires at the end of an assistant turn, not at true session shutdown, so
|
|
9
|
+
* we record a `turn_end` marker (session_end stays reserved for a genuine
|
|
10
|
+
* terminal lifecycle event) and tail the session JSONL for new usage.
|
|
11
|
+
*
|
|
12
|
+
* COST CAPTURE: qwen-code's hook stdin carries tool I/O ONLY — token usage is
|
|
13
|
+
* unreachable through the hook stream (matrix §4). The single live capture path
|
|
14
|
+
* is a cursor-gated tail of the session record file at
|
|
15
|
+
* ~/.qwen/tmp/<project_id>/chats/<sessionId>.jsonl
|
|
16
|
+
* where <project_id> = sha256(projectRoot) hex
|
|
17
|
+
* (refs chatRecordingService.ts:451 + storage.ts:316-320 getProjectTempDir;
|
|
18
|
+
* paths.ts:262 getProjectHash). The extractor lives in
|
|
19
|
+
* src/adapters/qwen-code/usage.ts and is re-exported through the
|
|
20
|
+
* session-extract bundle (src/session/extract.ts), reachable here via
|
|
21
|
+
* loadExtract() — exactly the kimi wire.jsonl pattern.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { readStdin, parseStdin, getSessionId, getSessionDBPath, getInputProjectDir } from "../session-helpers.mjs";
|
|
25
|
+
import { createSessionLoaders, attributeAndInsertEvents } from "../session-loaders.mjs";
|
|
26
|
+
import { dirname, join, resolve } from "node:path";
|
|
27
|
+
import { fileURLToPath } from "node:url";
|
|
28
|
+
import { homedir, platform } from "node:os";
|
|
29
|
+
import { createHash } from "node:crypto";
|
|
30
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
31
|
+
|
|
32
|
+
const HOOK_DIR = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
const { loadSessionDB, loadExtract, loadProjectAttribution } = createSessionLoaders(HOOK_DIR);
|
|
34
|
+
|
|
35
|
+
// Qwen Code session options. Mirrors the QwenCodeAdapter config (index.ts):
|
|
36
|
+
// config dir ~/.qwen, project dir via QWEN_PROJECT_DIR (no config-dir env var,
|
|
37
|
+
// no session-id env var — session_id arrives on the hook stdin). Kept local
|
|
38
|
+
// because session-helpers.mjs ships no QWEN_OPTS export.
|
|
39
|
+
const QWEN_OPTS = {
|
|
40
|
+
configDir: ".qwen",
|
|
41
|
+
configDirEnv: undefined,
|
|
42
|
+
projectDirEnv: "QWEN_PROJECT_DIR",
|
|
43
|
+
sessionIdEnv: undefined,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the qwen config dir (~/.qwen). Qwen Code exposes no documented
|
|
48
|
+
* config-dir override env var (unlike CODEX_HOME / KIMI_CODE_HOME), so this is
|
|
49
|
+
* a fixed home-rooted path. NO regex.
|
|
50
|
+
*/
|
|
51
|
+
function resolveQwenHome() {
|
|
52
|
+
return resolve(homedir(), ".qwen");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* EXACT port of qwen's getProjectHash (paths.ts:262): sha256 hex of the project
|
|
57
|
+
* root, lowercased first on win32 for case-insensitive FS parity. The TS twin
|
|
58
|
+
* is `qwenProjectHash` in src/adapters/qwen-code/usage.ts (unit-tested); the
|
|
59
|
+
* hook cannot import that TS at runtime, so the logic is duplicated here — same
|
|
60
|
+
* precedent as kimi's resolveKimiConfigDir. NO regex.
|
|
61
|
+
*/
|
|
62
|
+
function qwenProjectHash(projectRoot) {
|
|
63
|
+
const normalized = platform() === "win32" ? String(projectRoot).toLowerCase() : String(projectRoot);
|
|
64
|
+
return createHash("sha256").update(normalized).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve <qwenHome>/tmp/<sha256(projectRoot)>/chats/<sessionId>.jsonl.
|
|
69
|
+
*
|
|
70
|
+
* Primary path is fully deterministic from the hashing scheme above. Should the
|
|
71
|
+
* hash diverge (e.g. an upstream qwen normalization we did not pin), we fall
|
|
72
|
+
* back to a glob of every <qwenHome>/tmp/<*>/chats dir for <sessionId>.jsonl —
|
|
73
|
+
* a pure directory walk + filename equality test, NO regex. Best-effort:
|
|
74
|
+
* returns null on any failure so cost capture never blocks the turn.
|
|
75
|
+
*/
|
|
76
|
+
function resolveQwenChatJsonlPath(projectDir, sessionId) {
|
|
77
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return null;
|
|
78
|
+
const qwenHome = resolveQwenHome();
|
|
79
|
+
const fileName = `${sessionId}.jsonl`;
|
|
80
|
+
|
|
81
|
+
// 1) Canonical hashed path.
|
|
82
|
+
try {
|
|
83
|
+
if (typeof projectDir === "string" && projectDir.length > 0) {
|
|
84
|
+
const direct = join(qwenHome, "tmp", qwenProjectHash(projectDir), "chats", fileName);
|
|
85
|
+
if (existsSync(direct)) return direct;
|
|
86
|
+
}
|
|
87
|
+
} catch { /* fall through to glob */ }
|
|
88
|
+
|
|
89
|
+
// 2) Glob fallback — scan every tmp/<hash>/chats for this session file.
|
|
90
|
+
try {
|
|
91
|
+
const tmpRoot = join(qwenHome, "tmp");
|
|
92
|
+
if (!existsSync(tmpRoot)) return null;
|
|
93
|
+
let projectDirs;
|
|
94
|
+
try { projectDirs = readdirSync(tmpRoot, { withFileTypes: true }); } catch { return null; }
|
|
95
|
+
for (const entry of projectDirs) {
|
|
96
|
+
if (!entry.isDirectory()) continue;
|
|
97
|
+
const candidate = join(tmpRoot, entry.name, "chats", fileName);
|
|
98
|
+
try { if (existsSync(candidate)) return candidate; } catch { /* try next */ }
|
|
99
|
+
}
|
|
100
|
+
} catch { /* best-effort */ }
|
|
101
|
+
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const raw = await readStdin();
|
|
107
|
+
const input = parseStdin(raw);
|
|
108
|
+
const projectDir = getInputProjectDir(input, QWEN_OPTS);
|
|
109
|
+
|
|
110
|
+
const { SessionDB } = await loadSessionDB();
|
|
111
|
+
const dbPath = getSessionDBPath(QWEN_OPTS, projectDir);
|
|
112
|
+
const db = new SessionDB({ dbPath });
|
|
113
|
+
const sessionId = getSessionId(input, QWEN_OPTS);
|
|
114
|
+
|
|
115
|
+
db.ensureSession(sessionId, projectDir);
|
|
116
|
+
// SessionEvent contract requires {type, category, data, priority}. insertEvent
|
|
117
|
+
// hashes `data` for the dedup key, so encode the turn snapshot into `data`.
|
|
118
|
+
const payload = {
|
|
119
|
+
stop_hook_active: input.stop_hook_active ?? false,
|
|
120
|
+
last_assistant_message: typeof input.last_assistant_message === "string"
|
|
121
|
+
? input.last_assistant_message.slice(0, 2000)
|
|
122
|
+
: null,
|
|
123
|
+
};
|
|
124
|
+
db.insertEvent(sessionId, {
|
|
125
|
+
type: "turn_end",
|
|
126
|
+
category: "session",
|
|
127
|
+
data: JSON.stringify(payload),
|
|
128
|
+
priority: 1,
|
|
129
|
+
}, "Stop");
|
|
130
|
+
|
|
131
|
+
// ─── qwen-code per-turn cost capture (cursor-gated, no double-count) ───
|
|
132
|
+
// Usage lives ONLY on the session chats JSONL ChatRecords, never in hook
|
|
133
|
+
// stdin. Tail the file, sum NEW usage records since a per-session high-water
|
|
134
|
+
// cursor, and forward. Best-effort — a missing file or read/extract failure
|
|
135
|
+
// must never block the turn_end write or the session, so the whole block is
|
|
136
|
+
// wrapped and swallowed.
|
|
137
|
+
try {
|
|
138
|
+
const jsonlPath = resolveQwenChatJsonlPath(projectDir, sessionId);
|
|
139
|
+
if (jsonlPath) {
|
|
140
|
+
let jsonlText = null;
|
|
141
|
+
try {
|
|
142
|
+
jsonlText = readFileSync(jsonlPath, "utf-8");
|
|
143
|
+
} catch {
|
|
144
|
+
// unreadable/missing chats JSONL — skip capture this turn.
|
|
145
|
+
}
|
|
146
|
+
if (jsonlText) {
|
|
147
|
+
const { extractQwenUsageSince } = await loadExtract();
|
|
148
|
+
const { resolveProjectAttributions } = await loadProjectAttribution();
|
|
149
|
+
const cursor = db.getUsageCursor(sessionId);
|
|
150
|
+
const { events, cursor: next } = extractQwenUsageSince(jsonlText, cursor);
|
|
151
|
+
if (events.length > 0) {
|
|
152
|
+
// attributeAndInsertEvents both INSERTS locally and FORWARDS to the
|
|
153
|
+
// platform (gated on ~/.context-mode/platform.json).
|
|
154
|
+
attributeAndInsertEvents(db, sessionId, events, input, projectDir, "Stop", resolveProjectAttributions);
|
|
155
|
+
}
|
|
156
|
+
if (next) db.setUsageCursor(sessionId, next);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch {
|
|
160
|
+
// Best-effort cost capture — never block the session on failure.
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
db.close();
|
|
164
|
+
} catch {
|
|
165
|
+
// Qwen Code hooks must not block the session.
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
process.stdout.write("{}\n");
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared routing block for context-mode hooks.
|
|
3
|
+
* Single source of truth — imported by pretooluse.mjs and sessionstart.mjs.
|
|
4
|
+
*
|
|
5
|
+
* Factory functions accept a tool namer `t(bareTool) => platformSpecificName`
|
|
6
|
+
* so each platform gets correct tool names in guidance messages.
|
|
7
|
+
*
|
|
8
|
+
* Backward compat: static exports (ROUTING_BLOCK, READ_GUIDANCE, etc.)
|
|
9
|
+
* default to claude-code naming convention.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createToolNamer } from "./core/tool-naming.mjs";
|
|
13
|
+
|
|
14
|
+
// ── Factory functions ─────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export function createRoutingBlock(t, options = {}) {
|
|
17
|
+
const { includeCommands = true, toolSearchBootstrap = false } = options;
|
|
18
|
+
return `
|
|
19
|
+
<context_window_protection>
|
|
20
|
+
<priority_instructions>
|
|
21
|
+
Every byte a tool returns enters your conversation memory and costs reasoning capacity for the rest of the session. The context-mode tools let you do the work in a sandbox and surface only the derived answer — the raw bytes stay out. Think-in-Code: program the analysis, do not compute it by reading raw data into your conversation.
|
|
22
|
+
</priority_instructions>
|
|
23
|
+
${toolSearchBootstrap ? `
|
|
24
|
+
<deferred_tool_bootstrap>
|
|
25
|
+
The context-mode tools below may be DEFERRED in your harness — their schemas are not loaded yet, so calling them directly fails (e.g. "tool not found" / InputValidationError). Load them ONCE before your first ctx_* call:
|
|
26
|
+
ToolSearch(query: "select:${t("ctx_batch_execute")},${t("ctx_search")},${t("ctx_execute")},${t("ctx_execute_file")},${t("ctx_fetch_and_index")}")
|
|
27
|
+
After that they are callable. If any ctx_* call fails as not-found, ToolSearch it and retry — do NOT fall back to Bash/Read just because the schema was not loaded yet.
|
|
28
|
+
</deferred_tool_bootstrap>
|
|
29
|
+
` : ''}
|
|
30
|
+
<tool_selection_hierarchy>
|
|
31
|
+
0. MEMORY: ${t("ctx_search")}(sort: "timeline")
|
|
32
|
+
- On resume or compaction, query prior decisions, errors, plans, user prompts before asking the user — auto-captured session memory is searchable.
|
|
33
|
+
1. GATHER: ${t("ctx_batch_execute")}(commands, queries)
|
|
34
|
+
- Primary research tool. Runs commands in parallel, auto-indexes each output, and (when queries are passed) returns matching sections in the same round trip — no follow-up search call.
|
|
35
|
+
- Each command: {label: "section header", command: "shell command"}; the label becomes the FTS5 chunk title — descriptive labels improve search.
|
|
36
|
+
2. FOLLOW-UP: ${t("ctx_search")}(queries: ["q1", "q2", ...])
|
|
37
|
+
- Multiple related questions about anything already indexed (your captures + session memory). Batch every question in one array; the ranking pipeline runs per-query and the round-trip cost is paid once.
|
|
38
|
+
3. PROCESSING: ${t("ctx_execute")}(language, code) | ${t("ctx_execute_file")}(path, language, code)
|
|
39
|
+
- Derive answers FROM data: filter, count, aggregate, parse, transform. Only what you console.log() enters your conversation; the raw bytes stay in the sandbox.
|
|
40
|
+
</tool_selection_hierarchy>
|
|
41
|
+
|
|
42
|
+
<when_not_to_use>
|
|
43
|
+
- You intend to PROCESS the output (filter, count, parse, aggregate) → use ${t("ctx_batch_execute")} or ${t("ctx_execute")}. Bash stays correct when you intend to OBSERVE a short fixed output (git status on a clean tree, whoami, pwd) or when you are mutating state (git, mkdir, rm, mv, navigation).
|
|
44
|
+
- You want to analyze, summarize, or extract from a file → use ${t("ctx_execute_file")}. Read stays correct when you intend to Edit the file (Edit needs the exact bytes in your conversation to match against).
|
|
45
|
+
- WebFetch → use ${t("ctx_fetch_and_index")}; full network access, results indexed for ${t("ctx_search")}, raw page bytes never enter your conversation.
|
|
46
|
+
- ${t("ctx_execute")} and ${t("ctx_execute_file")} for file writes → these run code in a subprocess and discard the sandbox FS; they are for analysis, processing, and computation only.
|
|
47
|
+
</when_not_to_use>
|
|
48
|
+
|
|
49
|
+
<file_writing_policy>
|
|
50
|
+
File writes use the native Write or Edit tool — ${t("ctx_execute")}, ${t("ctx_execute_file")}, and Bash subprocesses do not persist edits to the host filesystem.
|
|
51
|
+
Applies to all file types: code, configs, plans, specs, YAML, JSON, markdown.
|
|
52
|
+
</file_writing_policy>
|
|
53
|
+
|
|
54
|
+
<output_constraints>
|
|
55
|
+
<artifact_policy>
|
|
56
|
+
Write artifacts (code, configs, PRDs) to files. Return only: file path + 1-line description.
|
|
57
|
+
</artifact_policy>
|
|
58
|
+
</output_constraints>
|
|
59
|
+
<session_continuity>
|
|
60
|
+
Skills, roles, and decisions captured earlier in this session are a memory aid, not a standing order. Treat them as context that may help — the user's most recent message always takes precedence. If a captured directive conflicts with what the user now asks, follow the user; a past phrase does not bind you.
|
|
61
|
+
</session_continuity>
|
|
62
|
+
${includeCommands ? `
|
|
63
|
+
<ctx_commands>
|
|
64
|
+
"ctx stats" | "ctx-stats" | "/ctx-stats" | context savings question
|
|
65
|
+
→ Call stats MCP tool, display full output verbatim.
|
|
66
|
+
|
|
67
|
+
"ctx doctor" | "ctx-doctor" | "/ctx-doctor" | diagnose context-mode
|
|
68
|
+
→ Call doctor MCP tool, run returned shell command, display as checklist.
|
|
69
|
+
|
|
70
|
+
"ctx upgrade" | "ctx-upgrade" | "/ctx-upgrade" | update context-mode
|
|
71
|
+
→ Call upgrade MCP tool, run returned shell command, display as checklist.
|
|
72
|
+
|
|
73
|
+
"ctx purge" | "ctx-purge" | "/ctx-purge" | wipe/reset knowledge base
|
|
74
|
+
→ Call purge MCP tool with confirm: true. Warn: irreversible.
|
|
75
|
+
|
|
76
|
+
After /clear or /compact: knowledge base preserved. Tell user: "context-mode knowledge base preserved. Use \`ctx purge\` to start fresh."
|
|
77
|
+
</ctx_commands>
|
|
78
|
+
` : ''}
|
|
79
|
+
</context_window_protection>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createReadGuidance(t) {
|
|
83
|
+
return '<context_guidance>\n <tip>\n Reading to Edit the file? Read is correct — Edit needs the exact bytes in your conversation to match against.\n Reading to analyze, summarize, or extract from the file? Use ' + t("ctx_execute_file") + '(path, language, code) — the bytes stay in the sandbox and only what your code prints enters your conversation.\n </tip>\n</context_guidance>';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createGrepGuidance(t) {
|
|
87
|
+
return '<context_guidance>\n <tip>\n Grep results may be larger than you expect. When you intend to count, filter, or aggregate matches (not just spot-check one), run the search through ' + t("ctx_execute") + '(language: "javascript", code: "...") — the raw match list stays in the sandbox and only your derived answer enters your conversation. Use language: "shell" only when the code matches the host shell (PowerShell on Windows, POSIX shell on Unix).\n </tip>\n</context_guidance>';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function createBashGuidance(t) {
|
|
91
|
+
return '<context_guidance>\n <tip>\n When you intend to PROCESS the output (filter, count, parse, aggregate), use ' + t("ctx_batch_execute") + '(commands, queries) for multiple commands or ' + t("ctx_execute") + '(language: "javascript", code: "...") for one — the raw output stays in the sandbox and only what you print enters your conversation. Shell stays the right surface when you intend to OBSERVE a short fixed output or when you are mutating state (git, mkdir, rm, mv, navigation); if you use ' + t("ctx_execute") + '(language: "shell"), write syntax for the host shell.\n </tip>\n</context_guidance>';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createExternalMcpGuidance(t) {
|
|
95
|
+
return '<context_guidance>\n <tip>\n External MCP tools commonly return large payloads (channel history, file content, search results) that enter your conversation in full. When you intend to filter, count, or aggregate that data, pipe it through ' + t("ctx_execute") + '(language, code) — the raw payload stays in the sandbox and only the derived answer enters your conversation. For docs-style fetches you will want to query later, prefer ' + t("ctx_fetch_and_index") + '(url, source) then ' + t("ctx_search") + '(queries).\n </tip>\n</context_guidance>';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Backward compat: static exports defaulting to claude-code ──
|
|
99
|
+
|
|
100
|
+
const _t = createToolNamer("claude-code");
|
|
101
|
+
export const ROUTING_BLOCK = createRoutingBlock(_t);
|
|
102
|
+
export const READ_GUIDANCE = createReadGuidance(_t);
|
|
103
|
+
export const GREP_GUIDANCE = createGrepGuidance(_t);
|
|
104
|
+
export const BASH_GUIDANCE = createBashGuidance(_t);
|
|
105
|
+
export const EXTERNAL_MCP_GUIDANCE = createExternalMcpGuidance(_t);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* run-hook.mjs — Universal crash-resilient wrapper for context-mode hook entries (#414).
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* - hooks/hooks.json declares commands as `node "${CLAUDE_PLUGIN_ROOT}/hooks/X.mjs"`.
|
|
7
|
+
* On Windows shells (Git Bash, cmd.exe) the placeholder may mangle and resolution
|
|
8
|
+
* can fail with `cjs/loader:1479 MODULE_NOT_FOUND` — silent ghost hooks.
|
|
9
|
+
* - Top-level `import "./suppress-stderr.mjs"` style side-effects throw at
|
|
10
|
+
* parse time. A `try {}` inside the same file CANNOT catch a parse-time
|
|
11
|
+
* import failure, and `process.on('uncaughtException')` is also installed
|
|
12
|
+
* too late. The fix is to dynamic-import the side-effects from inside this
|
|
13
|
+
* wrapper, where the handler is guaranteed to be live.
|
|
14
|
+
*
|
|
15
|
+
* Contract:
|
|
16
|
+
* - logs every failure to <configDir>/context-mode/hook-errors.log,
|
|
17
|
+
* where configDir honors $CLAUDE_CONFIG_DIR (incl. leading ~) and
|
|
18
|
+
* falls back to ~/.claude — same contract as session-helpers.mjs (#453)
|
|
19
|
+
* - never propagates a non-zero exit (Claude Code surfaces non-zero as a
|
|
20
|
+
* "non-blocking hook error" on every tool call, which spams the user)
|
|
21
|
+
* - one-liner adoption for new hooks:
|
|
22
|
+
* import { runHook } from "./run-hook.mjs";
|
|
23
|
+
* await runHook(async () => { ...body... });
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { resolve, join } from "node:path";
|
|
28
|
+
import { existsSync, mkdirSync, appendFileSync } from "node:fs";
|
|
29
|
+
|
|
30
|
+
// Inlined to keep this wrapper dependency-free (parse-time imports must be
|
|
31
|
+
// failure-proof). Mirrors session-helpers.mjs::resolveConfigDir for #453.
|
|
32
|
+
function resolveClaudeConfigDir() {
|
|
33
|
+
const envVal = process.env.CLAUDE_CONFIG_DIR;
|
|
34
|
+
if (envVal) {
|
|
35
|
+
if (envVal.startsWith("~")) return join(homedir(), envVal.replace(/^~[/\\]?/, ""));
|
|
36
|
+
return envVal;
|
|
37
|
+
}
|
|
38
|
+
return resolve(homedir(), ".claude");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function logError(err) {
|
|
42
|
+
try {
|
|
43
|
+
const dir = resolve(resolveClaudeConfigDir(), "context-mode");
|
|
44
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
45
|
+
const line = `[${new Date().toISOString()}] pid=${process.pid} ${err?.stack || err?.message || String(err)}\n`;
|
|
46
|
+
appendFileSync(resolve(dir, "hook-errors.log"), line);
|
|
47
|
+
} catch {
|
|
48
|
+
/* never fail logging */
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Install process-level safety nets BEFORE any user code runs.
|
|
53
|
+
// Caveat: these only catch failures inside dynamically-loaded modules
|
|
54
|
+
// (which is precisely what runHook does). Static top-level imports in
|
|
55
|
+
// THIS file would still bypass these — keep this file's imports minimal
|
|
56
|
+
// and fail-safe (only node: built-ins above).
|
|
57
|
+
process.on("uncaughtException", (err) => {
|
|
58
|
+
logError(err);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
});
|
|
61
|
+
process.on("unhandledRejection", (err) => {
|
|
62
|
+
logError(err);
|
|
63
|
+
process.exit(0);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run a hook handler with full crash-resilience.
|
|
68
|
+
*
|
|
69
|
+
* Order of operations:
|
|
70
|
+
* 1. Dynamic-import suppress-stderr.mjs (best-effort — non-fatal)
|
|
71
|
+
* 2. Dynamic-import ensure-deps.mjs (best-effort — non-fatal)
|
|
72
|
+
* 3. Invoke handler — any throw is logged and we exit 0
|
|
73
|
+
*
|
|
74
|
+
* @param {() => Promise<void> | void} handler
|
|
75
|
+
*/
|
|
76
|
+
export async function runHook(handler) {
|
|
77
|
+
try {
|
|
78
|
+
await import("./suppress-stderr.mjs");
|
|
79
|
+
} catch (e) {
|
|
80
|
+
logError(e);
|
|
81
|
+
/* continue — non-fatal */
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await import("./ensure-deps.mjs");
|
|
85
|
+
} catch (e) {
|
|
86
|
+
logError(e);
|
|
87
|
+
/* continue — handler may still work */
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await handler();
|
|
91
|
+
} catch (e) {
|
|
92
|
+
logError(e);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var P=(t,n)=>()=>(t&&(n=t(t=0)),n);var A,O=P(()=>{"use strict";A={"claude-code":"claude-code","gemini-cli-mcp-client":"gemini-cli","antigravity-client":"antigravity","antigravity-cli":"antigravity-cli",agy:"antigravity-cli","cursor-vscode":"cursor","Visual-Studio-Code":"vscode-copilot","copilot-cli":"copilot-cli","GitHub Copilot CLI":"copilot-cli","github-copilot-cli":"copilot-cli","JetBrains Client":"jetbrains-copilot","IntelliJ IDEA":"jetbrains-copilot",PyCharm:"jetbrains-copilot",Codex:"codex","codex-mcp-client":"codex","Kilo Code":"kilo","Kiro CLI":"kiro","Pi CLI":"pi","Pi Coding Agent":"pi","omp-coding-agent":"omp",Zed:"zed",zed:"zed","qwen-code":"qwen-code","qwen-cli-mcp-client":"qwen-code","kimi-code":"kimi",kimi:"kimi","Kimi Code":"kimi"}});import{existsSync as f,readFileSync as G}from"node:fs";import{resolve as d}from"node:path";import{homedir as v}from"node:os";function $(){if(g!==null)return g!=="miss"&&g.hasCM;try{let t=d(v(),".claude","plugins","installed_plugins.json"),n=G(t,"utf-8"),e=JSON.parse(n),r=[...Object.keys(e.plugins??{}),...Object.keys(e.enabledPlugins??{})].some(i=>i.includes("context-mode"));return g={hasCM:r},r}catch{return g="miss",!1}}function S(t){switch(t){case"claude-code":return[".claude"];case"gemini-cli":return[".gemini"];case"antigravity":return[".gemini"];case"antigravity-cli":return[".gemini"];case"openclaw":return[".openclaw"];case"codex":return[".codex"];case"cursor":return[".cursor"];case"vscode-copilot":return[".vscode"];case"copilot-cli":return[".copilot"];case"kiro":return[".kiro"];case"pi":return[".pi"];case"omp":return[".omp"];case"qwen-code":return[".qwen"];case"kimi":return[".kimi-code"];case"kilo":return[".config","kilo"];case"opencode":return[".config","opencode"];case"zed":return[".config","zed"];case"jetbrains-copilot":return[".config","JetBrains"];default:return null}}function b(t){if(t?.name){let i=A[t.name];if(i)return{platform:i,confidence:"high",reason:`MCP clientInfo.name="${t.name}"`};if(t.name.startsWith("qwen-cli-mcp-client"))return{platform:"qwen-code",confidence:"high",reason:`MCP clientInfo.name="${t.name}" (qwen-cli pattern)`}}let n=process.env.CONTEXT_MODE_PLATFORM;if(n&&["claude-code","gemini-cli","kilo","opencode","codex","vscode-copilot","jetbrains-copilot","copilot-cli","cursor","antigravity","antigravity-cli","kiro","pi","omp","zed","qwen-code","kimi"].includes(n))return{platform:n,confidence:"high",reason:`CONTEXT_MODE_PLATFORM=${n} override`};for(let[i,s]of J)if(s.some(a=>a.detect!==!1&&process.env[a.name]))return i==="vscode-copilot"&&$()?{platform:"claude-code",confidence:"high",reason:"VSCODE_PID set but ~/.claude/plugins/installed_plugins.json lists context-mode (issue #539 fallback)"}:{platform:i,confidence:"high",reason:`${s.filter(a=>a.detect!==!1).map(a=>a.name).join(" or ")} env var set`};let e=v(),o=(()=>{let i=process.env.COPILOT_HOME;return i&&i.trim()!==""?i.startsWith("~")?d(e,i.replace(/^~[/\\]?/,"")):d(i):d(e,".copilot")})(),r=f(d(o,"mcp-config.json"))||f(d(o,"hooks","context-mode.json"));return process.env.COPILOT_HOME?.trim()&&r?{platform:"copilot-cli",confidence:"medium",reason:"context-mode config in explicit COPILOT_HOME exists (mcp-config.json or hooks/context-mode.json)"}:f(d(e,".local","bin","agy"))||f(d(e,".gemini","antigravity-cli"))||f(d(e,".gemini","config","mcp_config.json"))?{platform:"antigravity-cli",confidence:"medium",reason:"Antigravity CLI marker exists (~/.local/bin/agy, ~/.gemini/antigravity-cli, or ~/.gemini/config/mcp_config.json)"}:r?{platform:"copilot-cli",confidence:"medium",reason:"context-mode config in Copilot CLI home exists (mcp-config.json or hooks/context-mode.json; honors COPILOT_HOME)"}:f(d(e,".claude"))?{platform:"claude-code",confidence:"medium",reason:"~/.claude/ directory exists"}:f(d(e,".gemini"))?{platform:"gemini-cli",confidence:"medium",reason:"~/.gemini/ directory exists"}:f(d(e,".codex"))?{platform:"codex",confidence:"medium",reason:"~/.codex/ directory exists"}:f(d(e,".kiro"))?{platform:"kiro",confidence:"medium",reason:"~/.kiro/ directory exists"}:f(d(e,".omp"))?{platform:"omp",confidence:"medium",reason:"~/.omp/ directory exists"}:f(d(e,".pi"))?{platform:"pi",confidence:"medium",reason:"~/.pi/ directory exists"}:f(d(e,".qwen"))?{platform:"qwen-code",confidence:"medium",reason:"~/.qwen/ directory exists"}:f(d(e,".kimi-code"))?{platform:"kimi",confidence:"medium",reason:"~/.kimi-code/ directory exists"}:f(d(e,".openclaw"))?{platform:"openclaw",confidence:"medium",reason:"~/.openclaw/ directory exists"}:f(d(e,".cursor"))?{platform:"cursor",confidence:"medium",reason:"~/.cursor/ directory exists"}:f(d(e,".config","kilo"))?{platform:"kilo",confidence:"medium",reason:"~/.config/kilo/ directory exists"}:f(d(e,".config","JetBrains"))?{platform:"jetbrains-copilot",confidence:"medium",reason:"~/.config/JetBrains/ directory exists"}:f(d(e,".config","opencode"))?{platform:"opencode",confidence:"medium",reason:"~/.config/opencode/ directory exists"}:f(d(e,".config","zed"))?{platform:"zed",confidence:"medium",reason:"~/.config/zed/ directory exists"}:{platform:"claude-code",confidence:"low",reason:"No platform detected, defaulting to Claude Code"}}var g,F,J,D=P(()=>{"use strict";O();g=null;F=[["claude-code",[{name:"CLAUDE_CODE_ENTRYPOINT",role:"identification"},{name:"CLAUDE_PLUGIN_ROOT",role:"identification"},{name:"CLAUDE_PROJECT_DIR",role:"workspace"},{name:"CLAUDE_SESSION_ID",role:"identification"}]],["antigravity",[{name:"ANTIGRAVITY_CLI_ALIAS",role:"identification"}]],["cursor",[{name:"CURSOR_CWD",role:"workspace"},{name:"CURSOR_TRACE_ID",role:"identification"},{name:"CURSOR_CLI",role:"identification"}]],["kilo",[{name:"KILO",role:"identification"},{name:"KILO_PID",role:"identification"}]],["opencode",[{name:"OPENCODE_PROJECT_DIR",role:"workspace"},{name:"OPENCODE_CLIENT",role:"identification"},{name:"OPENCODE_TERMINAL",role:"identification"},{name:"OPENCODE",role:"identification"},{name:"OPENCODE_PID",role:"identification"}]],["zed",[{name:"ZED_SESSION_ID",role:"identification"},{name:"ZED_TERM",role:"identification"}]],["codex",[{name:"CODEX_THREAD_ID",role:"identification"},{name:"CODEX_CI",role:"identification"}]],["gemini-cli",[{name:"GEMINI_PROJECT_DIR",role:"workspace"},{name:"GEMINI_CLI",role:"identification"}]],["vscode-copilot",[{name:"VSCODE_CWD",role:"workspace"},{name:"VSCODE_PID",role:"identification"}]],["jetbrains-copilot",[{name:"IDEA_INITIAL_DIRECTORY",role:"workspace"}]],["qwen-code",[{name:"QWEN_PROJECT_DIR",role:"workspace"}]],["omp",[{name:"PI_CODING_AGENT_DIR",role:"workspace"}]],["pi",[{name:"PI_WORKSPACE_DIR",role:"workspace",detect:!1},{name:"PI_PROJECT_DIR",role:"workspace",detect:!1},{name:"PI_CONFIG_DIR",role:"identification"},{name:"PI_SESSION_FILE",role:"identification"},{name:"PI_COMPILED",role:"identification"},{name:"PI_CODING_AGENT",role:"identification"}]]],J=new Map(F)});import{resolve as h}from"node:path";import{homedir as x}from"node:os";function q(t=process.env){let n=t.CLAUDE_CONFIG_DIR;return n&&n.trim()!==""?n.startsWith("~")?h(x(),n.replace(/^~[/\\]?/,"")):h(n):h(x(),".claude")}function V(t=process.env){return h(q(t),"settings.json")}function E(t=process.env){let n=[],e=b();if(e.platform!=="claude-code"){let r=S(e.platform);r&&r.length>0&&n.push(h(x(),...r,"settings.json"))}let o=V(t);return n.includes(o)||n.push(o),n}var R=P(()=>{"use strict";D()});R();import{readFileSync as N,realpathSync as I}from"node:fs";import{relative as z,resolve as m,sep as H}from"node:path";function L(t){let n=t.match(/^Bash\((.+)\)$/);return n?n[1]:null}function W(t){let n=t.match(/^(\w+)\((.+)\)$/);return n?{tool:n[1],glob:n[2]}:null}function B(t){return t.replace(/[.*+?^${}()|[\]\\\/\-]/g,"\\$&")}function T(t){return t.replace(/[.+?^${}()|[\]\\\/\-]/g,"\\$&").replace(/\*/g,".*")}function U(t,n=!1){let e,o=t.indexOf(":");if(o!==-1){let r=t.slice(0,o),i=t.slice(o+1),s=B(r),a=T(i);e=`^${s}(\\s${a})?$`}else e=`^${T(t)}$`;return new RegExp(e,n?"i":"")}function K(t,n=!1){let e="",o=0;for(;o<t.length;)t[o]==="*"&&t[o+1]==="*"?o+2<t.length&&t[o+2]==="/"?(e+="(.*/)?",o+=3):(e+=".*",o+=2):t[o]==="*"?(e+="[^/]*",o++):t[o]==="?"?(e+="[^/]",o++):(e+=t[o].replace(/[.+^${}()|[\]\\\/\-]/g,"\\$&"),o++);return new RegExp(`^${e}$`,n?"i":"")}function w(t,n,e=!1){for(let o of n){let r=L(o);if(r&&U(r,e).test(t))return o}return null}function M(t,n){let e=0;for(let o=n-1;o>=0&&t[o]==="\\";o--)e++;return e%2===1}function X(t){let n=[],e="",o=!1,r=!1,i=!1,s=0;for(let a=0;a<t.length;a++){let c=t[a],l=M(t,a);c==="'"&&!r&&!i&&!l?(o=!o,e+=c):c==='"'&&!o&&!i&&!l?(r=!r,e+=c):c==="`"&&!o&&!r&&!l?(i=!i,e+=c):!o&&!r&&!i?c==="$"&&t[a+1]==="("&&!l?(s++,e+=c+t[a+1],a++):s>0&&c==="("&&!l?(s++,e+=c):c===")"&&s>0&&!l?(s--,e+=c):s===0&&(c===";"||c===`
|
|
2
|
+
`||c==="\r")&&!l?(n.push(e.trim()),e=""):s===0&&c==="|"&&t[a+1]==="|"||s===0&&c==="&"&&t[a+1]==="&"?(n.push(e.trim()),e="",a++):s===0&&c==="&"&&!l||s===0&&c==="|"?(n.push(e.trim()),e=""):e+=c:e+=c}return e.trim()&&n.push(e.trim()),n.filter(a=>a.length>0)}function j(t){let n=[],e=!1,o=!1,r=-1,i=[],s=[],a=0;for(let c=0;c<t.length;c++){let l=t[c],u=M(t,c);if(l==="'"&&!o&&r===-1&&!u)e=!e;else if(l==='"'&&!e&&r===-1&&!u)o=!o;else if(l==="`"&&!e&&!o&&!u)if(r===-1)r=c+1;else{let p=t.slice(r,c);n.push(p),n.push(...j(p)),r=-1}else if(!e&&r===-1){if(l==="$"&&t[c+1]==="("&&!u)t[c+2]==="("?(a+=2,c+=2):(i.push(c+2),s.push(a),a++,c++);else if(l==="("&&!u)a++;else if(l===")"&&!u&&(a>0&&a--,s.length>0&&a===s[s.length-1])){s.pop();let p=i.pop(),y=t.slice(p,c);n.push(y)}}}return n}function k(t){let n=[],e=X(t);for(let o of e){n.push(o);for(let r of j(o))n.push(...k(r))}return n}function _(t){let n;try{n=N(t,"utf-8")}catch{return null}let e;try{e=JSON.parse(n)}catch{return null}let o=e?.permissions;if(!o||typeof o!="object")return null;let r=i=>Array.isArray(i)?i.filter(s=>typeof s=="string"&&L(s)!==null):[];return{allow:r(o.allow),deny:r(o.deny),ask:r(o.ask)}}function ye(t,n){let e=[];if(t){let r=m(t,".claude","settings.local.json"),i=_(r);i&&e.push(i);let s=m(t,".claude","settings.json"),a=_(s);a&&e.push(a)}let o=n!==void 0?[n]:E();for(let r of o){let i=_(r);i&&e.push(i)}return e}function we(t,n,e){return Z(t,"deny",n,e)}function Z(t,n,e,o){let r=[],i=a=>{let c;try{c=N(a,"utf-8")}catch{return null}let l;try{l=JSON.parse(c)}catch{return null}let u=l?.permissions?.[n];if(!Array.isArray(u))return[];let p=[];for(let y of u){if(typeof y!="string")continue;let C=W(y);C&&C.tool===t&&p.push(C.glob)}return p};if(e){let a=i(m(e,".claude","settings.local.json"));a!==null&&r.push(a);let c=i(m(e,".claude","settings.json"));c!==null&&r.push(c)}let s=o!==void 0?[o]:E();for(let a of s){let c=i(a);c!==null&&r.push(c)}return r}function Ce(t,n,e=process.platform==="win32"||process.platform==="darwin"){let o=k(t);for(let r of o)for(let i of n){let s=w(r,i.deny,e);if(s)return{decision:"deny",matchedPattern:s}}for(let r of n){let i=!0,s=!1,a,c;for(let l of o){let u=w(l,r.ask,e);if(u){s=!0,a=u;break}let p=w(l,r.allow,e);p?c=p:i=!1}if(s)return{decision:"ask",matchedPattern:a};if(i&&o.length>0)return{decision:"allow",matchedPattern:c}}return{decision:"ask"}}function Pe(t,n,e=process.platform==="win32"||process.platform==="darwin"){let o=k(t);for(let r of o)for(let i of n){let s=w(r,i.deny,e);if(s)return{decision:"deny",matchedPattern:s}}return{decision:"allow"}}function Y(t,n,e=process.platform==="win32"||process.platform==="darwin",o){let r=s=>s.replace(/\\/g,"/"),i=new Set;if(i.add(r(t)),o){let s=m(o,t);i.add(r(s));try{i.add(r(I(s)))}catch{}}for(let s of n)for(let a of s){let c=K(r(a),e);for(let l of i)if(c.test(l))return{denied:!0,matchedPattern:a}}return{denied:!1}}function Q(t,n,e=process.platform==="win32"||process.platform==="darwin"){if(!n)return!0;let o=m(n),r=m(n,t),i=(s,a)=>{let c=s,l=a;if(e&&(c=c.toLowerCase(),l=l.toLowerCase()),c===l)return!0;let u=z(c,l);return u===""?!0:!(u===".."||u.startsWith(".."+H)||ee(u))};if(!i(o,r))return!1;try{let s=I(o),a=I(r);if(!i(s,a))return!1}catch{}return!0}function ee(t){if(t.startsWith("/"))return!0;if(t.length>=3&&t[1]===":"&&(t[2]==="\\"||t[2]==="/")){let n=t.charCodeAt(0);return n>=65&&n<=90||n>=97&&n<=122}return!1}function xe(t,n,e=[],o=process.platform==="win32"||process.platform==="darwin"){return Q(t,n,o)?{allowed:!0,reason:"inside"}:e.some(r=>r.length>0)&&Y(t,e,o,n).denied?{allowed:!0,reason:"allow-rule"}:{allowed:!1,reason:"outside"}}var te={python:[/os\.system\(\s*(['"])(.*?)\1\s*\)/g,/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*(['"])(.*?)\1/g],javascript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],typescript:[/exec(?:Sync|File|FileSync)?\(\s*(['"`])(.*?)\1/g,/spawn(?:Sync)?\(\s*(['"`])(.*?)\1/g],ruby:[/system\(\s*(['"])(.*?)\1/g,/`(.*?)`/g],go:[/exec\.Command\(\s*(['"`])(.*?)\1/g],php:[/shell_exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])exec\(\s*(['"`])(.*?)\1/g,/(?:^|[^.])system\(\s*(['"`])(.*?)\1/g,/passthru\(\s*(['"`])(.*?)\1/g,/proc_open\(\s*(['"`])(.*?)\1/g],rust:[/Command::new\(\s*(['"`])(.*?)\1/g]};function ne(t){let n=[],e=/subprocess\.(?:run|call|Popen|check_output|check_call)\(\s*\[([^\]]+)\]/g,o;for(;(o=e.exec(t))!==null;){let i=[...o[1].matchAll(/(['"])(.*?)\1/g)].map(s=>s[2]);i.length>0&&n.push(i.join(" "))}return n}function Ee(t,n){let e=te[n];if(!e&&n!=="python")return[];let o=[];if(e)for(let r of e){r.lastIndex=0;let i;for(;(i=r.exec(t))!==null;){let s=i[i.length-1];s&&o.push(s)}}return n==="python"&&o.push(...ne(t)),o}export{Ce as evaluateCommand,Pe as evaluateCommandDenyOnly,Y as evaluateFilePath,xe as evaluateProjectContainment,Ee as extractShellCommands,j as extractSubshellCommands,K as fileGlobToRegex,U as globToRegex,Q as isPathInsideProject,w as matchesAnyPattern,L as parseBashPattern,W as parseToolPattern,ye as readBashPolicies,we as readToolDenyPatterns,Z as readToolPermissionPatterns,X as splitChainedCommands};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{dirname as d,isAbsolute as a,normalize as g,resolve as j,sep as y}from"node:path";function e(r){let n=g(r).replace(/\\/g,"/");return n.length<=1?n:n.replace(/\/+$/,"")}function u(r,n){return!r||!n?!1:r===n?!0:r.startsWith(`${n}/`)}function D(r){if(!r||r.length===0)return[];let n=r.filter(i=>typeof i=="string"&&i.trim().length>0).map(i=>e(i));return Array.from(new Set(n)).sort((i,s)=>s.length-i.length)}function m(r){let n=" in ",t=r.lastIndexOf(n);if(t<0)return null;let i=r.slice(t+n.length).trim();return i.length>0?i:null}function w(r){return r?r.includes("/")||r.includes("\\")||r.startsWith(".")||/^[A-Za-z]:[\\/]/.test(r):!1}function P(r){if(r.type==="cwd")return{rawPath:r.data,fromCwdEvent:!0};if(r.type==="file_search"){let t=m(r.data);if(t)return{rawPath:t,fromCwdEvent:!1}}return new Set(["file_read","file_write","file_edit","file_glob","rule"]).has(r.type)&&w(r.data)?{rawPath:r.data,fromCwdEvent:!1}:null}function b(r,n){if(!r||r.includes("*")&&!a(r)&&!/^[A-Za-z]:[\\/]/.test(r))return null;if(a(r)||/^[A-Za-z]:[\\/]/.test(r))return e(r);let t=n.lastKnownProjectDir||n.inputProjectDir||n.sessionOriginDir||null;return t?e(j(t,r)):null}function h(r,n,t){let i=D(t.workspaceRoots),s=t.sessionOriginDir?e(t.sessionOriginDir):"",o=t.inputProjectDir?e(t.inputProjectDir):"",c=t.lastKnownProjectDir?e(t.lastKnownProjectDir):"",f=i.find(p=>u(r,p));return f?{projectDir:f,source:"workspace_root",confidence:.98}:o&&u(r,o)?{projectDir:o,source:"input_cwd",confidence:.88}:s&&u(r,s)?{projectDir:s,source:"session_origin",confidence:.82}:c&&u(r,c)?{projectDir:c,source:"last_seen",confidence:.76}:n.type==="cwd"?{projectDir:r,source:"cwd_event",confidence:.9}:{projectDir:new Set(["file_read","file_write","file_edit","rule"]).has(n.type)?e(d(r)):r,source:"event_path",confidence:.7}}function l(r){return r.inputProjectDir?{projectDir:e(r.inputProjectDir),source:"input_cwd",confidence:.45}:r.lastKnownProjectDir?{projectDir:e(r.lastKnownProjectDir),source:"last_seen",confidence:.4}:r.sessionOriginDir?{projectDir:e(r.sessionOriginDir),source:"session_origin",confidence:.35}:{projectDir:"",source:"unknown",confidence:0}}function A(r,n){try{let t=P(r);if(!t)return l(n);let i=b(t.rawPath,n);return i?h(i,r,n):l(n)}catch{return l(n)}}function z(r,n){let t=[],i=n.lastKnownProjectDir?e(n.lastKnownProjectDir):"";for(let s of r){let o=A(s,{...n,lastKnownProjectDir:i||n.lastKnownProjectDir||null});t.push(o),o.projectDir&&o.confidence>=.55&&(i=o.projectDir)}return t}function C(r){let n=Math.max(0,Math.min(1,r));return Math.round(n*100)}function E(r){return r>=.8}function K(r){return e(r)}var O=1;export{O as PROJECT_ATTRIBUTION_VERSION,C as confidenceToPercent,E as isHighConfidenceAttribution,K as normalizeProjectDir,A as resolveProjectAttribution,z as resolveProjectAttributions};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import{createRequire as le}from"node:module";import{existsSync as ge,unlinkSync as H,renameSync as Ee}from"node:fs";import{tmpdir as me}from"node:os";import{join as _e}from"node:path";var I=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){let t="",r=null;for(let i=0;i<e.length;i++){let a=e[i];if(r)t+=a,a===r&&(r=null);else if(a==="'"||a==='"')t+=a,r=a;else if(a===";"){let u=t.trim();u&&this.#e.prepare(u).run(),t=""}else t+=a}let s=t.trim();return s&&this.#e.prepare(s).run(),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>{let s=t.get(...r);return s===null?void 0:s},all:(...r)=>t.all(...r),iterate:(...r)=>t.iterate(...r)}}transaction(e){return this.#e.transaction(e)}close(){this.#e.close()}},M=class{#e;constructor(e){this.#e=e}pragma(e){let r=this.#e.prepare(`PRAGMA ${e}`).all();if(!r||r.length===0)return;if(r.length>1)return r;let s=Object.values(r[0]);return s.length===1?s[0]:r[0]}exec(e){return this.#e.exec(e),this}prepare(e){let t=this.#e.prepare(e);return{run:(...r)=>t.run(...r),get:(...r)=>t.get(...r),all:(...r)=>t.all(...r),iterate:(...r)=>typeof t.iterate=="function"?t.iterate(...r):t.all(...r)[Symbol.iterator]()}}transaction(e){return(...t)=>{this.#e.exec("BEGIN");try{let r=e(...t);return this.#e.exec("COMMIT"),r}catch(r){throw this.#e.exec("ROLLBACK"),r}}}close(){this.#e.close()}},y=null;function pe(n){let e=null;try{return e=new n(":memory:"),e.exec("CREATE VIRTUAL TABLE __fts5_probe USING fts5(x)"),!0}catch{return!1}finally{try{e?.close()}catch{}}}function Se(n,e){let t=e!==void 0?e:globalThis.Bun;if(typeof t<"u"&&t!==null)return!0;let r=n??process.versions,[s,i]=(r.node??"0.0.0").split("."),a=Number(s),u=Number(i);return!Number.isFinite(a)||!Number.isFinite(u)?!1:a>22||a===22&&u>=5}function fe(){if(!y){let n=le(import.meta.url);if(globalThis.Bun){let e=n(["bun","sqlite"].join(":")).Database;y=function(r,s){let i=new e(r,{readonly:s?.readonly,create:!0}),a=new I(i);return s?.timeout&&a.pragma(`busy_timeout = ${s.timeout}`),a}}else if(Se()){let e=null;try{({DatabaseSync:e}=n(["node","sqlite"].join(":")))}catch{e=null}e&&pe(e)?y=function(r,s){let i=new e(r,{readOnly:s?.readonly??!1}),a=new M(i);return s?.timeout&&a.pragma(`busy_timeout = ${s.timeout}`),a}:y=n("better-sqlite3")}else y=n("better-sqlite3")}return y}function F(n,e=process.env){n.pragma("journal_mode = WAL"),n.pragma("synchronous = NORMAL");let t=ye(e);if(t!==null)try{n.pragma(`mmap_size = ${t}`)}catch{}}function ye(n=process.env){let e=n.CONTEXT_MODE_DB_MMAP_SIZE;if(e==null)return null;let t=String(e).trim();if(t==="")return null;let r=Number(t);return!Number.isFinite(r)||!Number.isInteger(r)||r<0?null:r}function B(n){if(!ge(n))for(let e of["-wal","-shm"])try{H(n+e)}catch{}}function he(n){for(let e of["","-wal","-shm"])try{H(n+e)}catch{}}function U(n){try{n.close()}catch{}}function $(n="context-mode"){return _e(me(),`${n}-${process.pid}.db`)}function W(n){if(n instanceof Error){let e=n.code;return typeof e=="string"?`${e} ${n.message}`:n.message}if(typeof n=="string")return n;if(n!==null&&typeof n=="object"){let e=n.code,t=n.message,r=[typeof e=="string"?e:"",typeof t=="string"?t:""].filter(Boolean);return r.length>0?r.join(" "):String(n)}return String(n)}function Te(n){let e=W(n);return e.includes("SQLITE_BUSY")||e.includes("database is locked")||e.includes("SQLITE_IOERR")||/disk i\/o error/i.test(e)}function ve(n,e=[100,500,2e3]){let t;for(let r=0;r<=e.length;r++)try{return n()}catch(s){if(!Te(s))throw s;if(t=s instanceof Error?s:new Error(W(s)),r<e.length){let i=e[r],a=Date.now();for(;Date.now()-a<i;);}}throw new Error(`SQLITE_BUSY/SQLITE_IOERR: transient SQLite error after ${e.length} retries. Original error: ${t?.message}`)}function Re(n){return n.includes("SQLITE_CORRUPT")||n.includes("SQLITE_NOTADB")||n.includes("database disk image is malformed")||n.includes("file is not a database")}function be(n){let e=Date.now();for(let t of["","-wal","-shm"])try{Ee(n+t,`${n}${t}.corrupt-${e}`)}catch{}}var De="[context-mode:db]",j=3e4,V=256,m=new Map;function Le(n){if(n instanceof Error){let e=n.code;return typeof e=="string"?e:""}if(n!==null&&typeof n=="object"){let e=n.code;return typeof e=="string"?e:""}return""}function Ne(n){if(n instanceof Error)return n.message;if(typeof n=="string")return n;if(n!==null&&typeof n=="object"){let e=n.message;if(typeof e=="string"&&e)return e}return String(n)}function l(n,e,t,r=Date.now()){try{let s=Le(e),i=Ne(e),a=t?` (${t})`:"",u=`${n}|${s}|${i}${a}`,d=m.get(u);if(d!==void 0&&r-d<j)return;if(m.set(u,r),m.size>V){for(let[E,L]of m)r-L>=j&&m.delete(E);for(;m.size>V;){let E=m.keys().next().value;if(E===void 0)break;m.delete(E)}}let c=process.env.OPENCODE_DEBUG,p=c!==void 0&&c!==""&&c!=="0"&&c!=="false"&&e instanceof Error&&e.stack?`
|
|
2
|
+
${e.stack}`:"",f=s?` [${s}]`:"";console.error(`${De} ${n}${f}: ${i}${a}${p}`)}catch{}}var R=Symbol.for("__context_mode_live_dbs_v3__"),x=(()=>{let n=globalThis;return n[R]||(n[R]=new Set,process.on("exit",()=>{for(let e of n[R])U(e);n[R].clear()})),n[R]})(),N=class{#e;#t;constructor(e){let t=fe();this.#e=e,B(e);let r;try{r=new t(e,{timeout:3e4}),F(r)}catch(s){let i=s instanceof Error?s.message:String(s);if(Re(i)){l("SQLiteBase.open",s,e),be(e),B(e);try{r=new t(e,{timeout:3e4}),F(r)}catch(a){throw new Error(`Failed to create fresh DB after renaming corrupt file: ${a instanceof Error?a.message:String(a)}`)}}else throw s}this.#t=r,x.add(this.#t),this.initSchema(),this.prepareStatements()}get db(){return this.#t}get dbPath(){return this.#e}close(){x.delete(this.#t),U(this.#t)}withRetry(e){return ve(e)}cleanup(){x.delete(this.#t),U(this.#t),he(this.#e)}};import{createHash as b}from"node:crypto";import{execFileSync as Ce}from"node:child_process";import{accessSync as Oe,constants as Ae,existsSync as A,mkdirSync as we,realpathSync as xe,renameSync as k}from"node:fs";import{homedir as Q}from"node:os";import{dirname as Ie,isAbsolute as J,join as _,resolve as T}from"node:path";var g="CONTEXT_MODE_DIR",Z="sessions",X="content",D=class extends Error{kind;path;overrideEnvVar;ignoredEnvVar;ignoredReason;constructor(e,t,r=g,s,i,a={}){super(i??Fe(e,t,a),{cause:s}),this.name="StorageDirectoryError",this.kind=e,this.path=t,this.overrideEnvVar=r,this.ignoredEnvVar=a.ignoredEnvVar,this.ignoredReason=a.ignoredReason}},O=new Map;function rt(n){let e=n.env??process.env,t=n.legacySessionDirEnv,r=t?e[t]?.trim():void 0;return r&&t?(n.onLegacySessionDir?.(t,r),r):_(Me(n.configDir,n.configDirEnv,e),"context-mode","sessions")}function Me(n,e,t){let r=e?t[e]:void 0;return r&&r.trim()!==""?q(r.trim()):q(n,Q())}function q(n,e){return n.startsWith("~")?T(Q(),n.replace(/^~[/\\]?/,"")):J(n)?T(n):e?T(e,n):T(n)}function Ue(n,e,t){return new D(n,e,g,void 0,[`Invalid ${g} for context-mode ${n} directory: ${t}`,re()].join(`
|
|
3
|
+
`))}function ee(n){let e=process.env[g];if(e===void 0)return{kind:"unset"};let t=e.trim();if(!t)return{kind:"ignored-empty",ignoredEnvVar:g,ignoredReason:"empty"};if(!J(t))throw Ue(n,t,`${g} must be an absolute path.`);return{kind:"override",root:T(t)}}function ke(n){return n.kind==="ignored-empty"?{ignoredEnvVar:n.ignoredEnvVar,ignoredReason:n.ignoredReason}:{}}function te(n,e){let t=ee(n);return t.kind!=="override"?null:{kind:n,path:_(t.root,e),envVar:g,source:"override"}}function Pe(n,e,t){return{kind:n,path:T(e()),envVar:null,source:"default",...t}}function ne(n){let e=ee("session");return e.kind==="override"?{kind:"session",path:_(e.root,Z),envVar:g,source:"override"}:Pe("session",n,ke(e))}function st(n){let e=te("content",X);if(e)return e;let t=ne(n);return{kind:"content",path:_(Ie(t.path),X),envVar:t.envVar,source:t.source,ignoredEnvVar:t.ignoredEnvVar,ignoredReason:t.ignoredReason}}function ot(n){let e=te("stats",Z);if(e)return e;let t=ne(n);return{kind:"stats",path:t.path,envVar:t.envVar,source:t.source,ignoredEnvVar:t.ignoredEnvVar,ignoredReason:t.ignoredReason}}function it(n){return n.message}function at(n){return n.source==="override"&&n.envVar?`via ${n.envVar}`:n.ignoredEnvVar&&n.ignoredReason==="empty"?`default; ignored empty ${n.ignoredEnvVar}`:"default"}function ct(){O.clear()}function ut(n){let e=[n.kind,n.path,n.source,n.envVar??"",n.ignoredEnvVar??"",n.ignoredReason??""].join("\0"),t=O.get(e);if(t instanceof D)throw t;if(t===n.path)return t;try{return we(n.path,{recursive:!0}),Oe(n.path,Ae.W_OK),O.set(e,n.path),n.path}catch(r){let s=new D(n.kind,je(r)??n.path,g,r,void 0,{ignoredEnvVar:n.ignoredEnvVar,ignoredReason:n.ignoredReason});throw O.set(e,s),s}}function Fe(n,e,t={}){return[`context-mode ${n} directory is not writable: ${e}`,Be(t),re()].filter(Boolean).join(`
|
|
4
|
+
`)}function Be(n){return n.ignoredEnvVar&&n.ignoredReason==="empty"?`Ignored empty ${n.ignoredEnvVar}; using adapter default.`:null}function re(){return`Set ${g} to a writable absolute path.`}function je(n){if(!n||typeof n!="object")return null;let e=n.path;return typeof e=="string"&&e.length>0?e:null}var h;function S(n){let e=n.replace(/\\/g,"/");return/^\/+$/.test(e)?"/":/^[A-Za-z]:\/+$/.test(e)?`${e.slice(0,2)}/`:e.replace(/\/+$/,"")}function G(n){let e=n;try{e=xe.native(n)}catch{}let t=S(e);return process.platform==="win32"||process.platform==="darwin"?t.toLowerCase():t}function se(n,e){return Ce("git",["-C",n,...e],{encoding:"utf-8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim()}function Ve(n){let e=se(n,["rev-parse","--show-toplevel"]);return e.length>0?S(e):null}function He(n){let e=se(n,["worktree","list","--porcelain"]).split(/\r?\n/).find(t=>t.startsWith("worktree "))?.replace("worktree ","")?.trim();return e?S(e):null}function $e(n=process.cwd()){let e=process.env.CONTEXT_MODE_SESSION_SUFFIX;if(h&&h.projectDir===n&&h.envSuffix===e)return h.suffix;let t="";if(e!==void 0)t=e?`__${e}`:"";else try{let r=Ve(n),s=He(n);if(r&&s){let i=G(r),a=G(s);i!==a&&(t=`__${b("sha256").update(i).digest("hex").slice(0,8)}`)}}catch{}return h={projectDir:n,envSuffix:e,suffix:t},t}function dt(){h=void 0}function oe(n){return b("sha256").update(S(n)).digest("hex").slice(0,16)}function ie(n){let e=S(n),t=process.platform==="darwin"||process.platform==="win32"?e.toLowerCase():e;return b("sha256").update(t).digest("hex").slice(0,16)}function lt(n){let{projectDir:e,contentDir:t}=n,r=ie(e),s=_(t,`${r}.db`);if(A(s))return s;let i=oe(e);if(i===r)return s;let a=_(t,`${i}.db`);if(A(a))try{k(a,s);for(let u of["-wal","-shm"])try{k(a+u,s+u)}catch{}}catch{}return s}function gt(n){return We({...n,ext:".db"})}function We(n){let{projectDir:e,sessionsDir:t,ext:r}=n,s=n.suffix??$e(e),i=ie(e),a=_(t,`${i}${s}${r}`);if(A(a))return a;let u=oe(e);if(u===i)return a;let d=_(t,`${u}${s}${r}`);if(A(d))try{k(d,a)}catch{}return a}var Y=1e3,K=5;function C(n){let e=Number(n);return!Number.isFinite(e)||e<=0?0:Math.floor(e)}var o={insertEvent:"insertEvent",getEvents:"getEvents",getEventsByType:"getEventsByType",getEventsByPriority:"getEventsByPriority",getEventsByTypeAndPriority:"getEventsByTypeAndPriority",getEventCount:"getEventCount",getLatestAttributedProject:"getLatestAttributedProject",checkDuplicate:"checkDuplicate",evictLowestPriority:"evictLowestPriority",updateMetaLastEvent:"updateMetaLastEvent",ensureSession:"ensureSession",getSessionStats:"getSessionStats",getSessionRollup:"getSessionRollup",getMaxFileEdits:"getMaxFileEdits",getLatestCommitMessage:"getLatestCommitMessage",incrementCompactCount:"incrementCompactCount",getUsageCursor:"getUsageCursor",setUsageCursor:"setUsageCursor",upsertResume:"upsertResume",getResume:"getResume",markResumeConsumed:"markResumeConsumed",claimLatestUnconsumedResume:"claimLatestUnconsumedResume",deleteEvents:"deleteEvents",deleteMeta:"deleteMeta",deleteResume:"deleteResume",getOldSessions:"getOldSessions",searchEvents:"searchEvents",incrementToolCall:"incrementToolCall",getToolCallTotals:"getToolCallTotals",getToolCallByTool:"getToolCallByTool",getEventBytesSummary:"getEventBytesSummary"},Xe=[["project_dir","TEXT NOT NULL DEFAULT ''"],["attribution_source","TEXT NOT NULL DEFAULT 'unknown'"],["attribution_confidence","REAL NOT NULL DEFAULT 0"],["bytes_avoided","INTEGER NOT NULL DEFAULT 0"],["bytes_returned","INTEGER NOT NULL DEFAULT 0"]];function ae(n){let e=n.pragma("table_xinfo(session_events)"),t=new Set(e.map(s=>s.name)),r=!1;for(let[s,i]of Xe)t.has(s)||(n.exec(`ALTER TABLE session_events ADD COLUMN ${s} ${i}`),r=!0);return r&&n.exec("CREATE INDEX IF NOT EXISTS idx_session_events_project ON session_events(session_id, project_dir)"),r}function Et(n,e){let t=null;try{t=new e(n),ae(t)}catch(r){l("ensureSessionEventsSchema",r,n)}finally{try{t?.close()}catch{}}}var z=class extends N{constructor(e){super(e?.dbPath??$("session"))}stmt(e){return this.stmts.get(e)}initSchema(){try{let t=(this.db.pragma("table_xinfo(session_events)")??[]).find(r=>r.name==="data_hash");t&&t.hidden!==0&&this.db.exec("DROP TABLE session_events")}catch(e){l("SessionDB.initSchema.dataHashMigration",e,this.dbPath)}this.db.exec(`
|
|
5
|
+
CREATE TABLE IF NOT EXISTS session_events (
|
|
6
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
7
|
+
session_id TEXT NOT NULL,
|
|
8
|
+
type TEXT NOT NULL,
|
|
9
|
+
category TEXT NOT NULL,
|
|
10
|
+
priority INTEGER NOT NULL DEFAULT 2,
|
|
11
|
+
data TEXT NOT NULL,
|
|
12
|
+
project_dir TEXT NOT NULL DEFAULT '',
|
|
13
|
+
attribution_source TEXT NOT NULL DEFAULT 'unknown',
|
|
14
|
+
attribution_confidence REAL NOT NULL DEFAULT 0,
|
|
15
|
+
bytes_avoided INTEGER NOT NULL DEFAULT 0,
|
|
16
|
+
bytes_returned INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
source_hook TEXT NOT NULL,
|
|
18
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
19
|
+
data_hash TEXT NOT NULL DEFAULT ''
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_session ON session_events(session_id);
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_type ON session_events(session_id, type);
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_session_events_priority ON session_events(session_id, priority);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS session_meta (
|
|
27
|
+
session_id TEXT PRIMARY KEY,
|
|
28
|
+
project_dir TEXT NOT NULL,
|
|
29
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
30
|
+
last_event_at TEXT,
|
|
31
|
+
event_count INTEGER NOT NULL DEFAULT 0,
|
|
32
|
+
compact_count INTEGER NOT NULL DEFAULT 0
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE TABLE IF NOT EXISTS session_resume (
|
|
36
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
37
|
+
session_id TEXT NOT NULL UNIQUE,
|
|
38
|
+
snapshot TEXT NOT NULL,
|
|
39
|
+
event_count INTEGER NOT NULL,
|
|
40
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
41
|
+
consumed INTEGER NOT NULL DEFAULT 0
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
45
|
+
session_id TEXT NOT NULL,
|
|
46
|
+
tool TEXT NOT NULL,
|
|
47
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
48
|
+
bytes_returned INTEGER NOT NULL DEFAULT 0,
|
|
49
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
50
|
+
PRIMARY KEY (session_id, tool)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id);
|
|
54
|
+
`);try{ae(this.db)}catch(e){l("SessionDB.initSchema.migrateColumns",e,this.dbPath)}try{(this.db.pragma("table_xinfo(session_meta)")??[]).some(t=>t.name==="usage_cursor")||this.db.exec("ALTER TABLE session_meta ADD COLUMN usage_cursor TEXT")}catch(e){l("SessionDB.initSchema.usageCursorMigration",e,this.dbPath)}}prepareStatements(){this.stmts=new Map;let e=(t,r)=>{this.stmts.set(t,this.db.prepare(r))};e(o.insertEvent,`INSERT INTO session_events (
|
|
55
|
+
session_id, type, category, priority, data,
|
|
56
|
+
project_dir, attribution_source, attribution_confidence,
|
|
57
|
+
bytes_avoided, bytes_returned,
|
|
58
|
+
source_hook, data_hash
|
|
59
|
+
)
|
|
60
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),e(o.getEvents,`SELECT id, session_id, type, category, priority, data,
|
|
61
|
+
project_dir, attribution_source, attribution_confidence,
|
|
62
|
+
bytes_avoided, bytes_returned,
|
|
63
|
+
source_hook, created_at, data_hash
|
|
64
|
+
FROM session_events WHERE session_id = ? ORDER BY id ASC LIMIT ?`),e(o.getEventsByType,`SELECT id, session_id, type, category, priority, data,
|
|
65
|
+
project_dir, attribution_source, attribution_confidence,
|
|
66
|
+
bytes_avoided, bytes_returned,
|
|
67
|
+
source_hook, created_at, data_hash
|
|
68
|
+
FROM session_events WHERE session_id = ? AND type = ? ORDER BY id ASC LIMIT ?`),e(o.getEventsByPriority,`SELECT id, session_id, type, category, priority, data,
|
|
69
|
+
project_dir, attribution_source, attribution_confidence,
|
|
70
|
+
bytes_avoided, bytes_returned,
|
|
71
|
+
source_hook, created_at, data_hash
|
|
72
|
+
FROM session_events WHERE session_id = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),e(o.getEventsByTypeAndPriority,`SELECT id, session_id, type, category, priority, data,
|
|
73
|
+
project_dir, attribution_source, attribution_confidence,
|
|
74
|
+
bytes_avoided, bytes_returned,
|
|
75
|
+
source_hook, created_at, data_hash
|
|
76
|
+
FROM session_events WHERE session_id = ? AND type = ? AND priority >= ? ORDER BY id ASC LIMIT ?`),e(o.getEventCount,"SELECT COUNT(*) AS cnt FROM session_events WHERE session_id = ?"),e(o.getLatestAttributedProject,`SELECT project_dir
|
|
77
|
+
FROM session_events
|
|
78
|
+
WHERE session_id = ? AND project_dir != ''
|
|
79
|
+
ORDER BY id DESC
|
|
80
|
+
LIMIT 1`),e(o.checkDuplicate,`SELECT 1 FROM (
|
|
81
|
+
SELECT type, data_hash FROM session_events
|
|
82
|
+
WHERE session_id = ? ORDER BY id DESC LIMIT ?
|
|
83
|
+
) AS recent
|
|
84
|
+
WHERE recent.type = ? AND recent.data_hash = ?
|
|
85
|
+
LIMIT 1`),e(o.evictLowestPriority,`DELETE FROM session_events WHERE id = (
|
|
86
|
+
SELECT id FROM session_events WHERE session_id = ?
|
|
87
|
+
ORDER BY priority ASC, id ASC LIMIT 1
|
|
88
|
+
)`),e(o.updateMetaLastEvent,`UPDATE session_meta
|
|
89
|
+
SET last_event_at = datetime('now'), event_count = event_count + 1
|
|
90
|
+
WHERE session_id = ?`),e(o.ensureSession,"INSERT OR IGNORE INTO session_meta (session_id, project_dir) VALUES (?, ?)"),e(o.getSessionStats,`SELECT session_id, project_dir, started_at, last_event_at, event_count, compact_count
|
|
91
|
+
FROM session_meta WHERE session_id = ?`),e(o.getSessionRollup,`SELECT
|
|
92
|
+
COUNT(*) AS tool_calls,
|
|
93
|
+
COALESCE(SUM(CASE WHEN category = 'error' THEN 1 ELSE 0 END), 0) AS errors,
|
|
94
|
+
COUNT(DISTINCT type) AS unique_tools,
|
|
95
|
+
COUNT(DISTINCT CASE WHEN category = 'file' THEN data END) AS unique_files,
|
|
96
|
+
CASE WHEN SUM(CASE WHEN type = 'git_commit' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END AS has_commit,
|
|
97
|
+
CAST(COALESCE((MAX(strftime('%s', created_at)) - MIN(strftime('%s', created_at))) / 60.0, 0) AS INTEGER) AS duration_min,
|
|
98
|
+
COALESCE(SUM(CASE WHEN type = 'external_ref' THEN 1 ELSE 0 END), 0) AS sources_indexed,
|
|
99
|
+
CAST(COALESCE(SUM(bytes_avoided) / 1024.0, 0) AS INTEGER) AS total_chunks,
|
|
100
|
+
COALESCE(SUM(CASE WHEN type IN ('file_search', 'file_glob') THEN 1 ELSE 0 END), 0) AS search_queries
|
|
101
|
+
FROM session_events
|
|
102
|
+
WHERE session_id = ?`),e(o.getMaxFileEdits,`SELECT COALESCE(MAX(c), 0) AS max_file_edits
|
|
103
|
+
FROM (
|
|
104
|
+
SELECT COUNT(*) AS c
|
|
105
|
+
FROM session_events
|
|
106
|
+
WHERE session_id = ? AND category = 'file' AND type IN ('file_edit', 'file_write')
|
|
107
|
+
GROUP BY data
|
|
108
|
+
)`),e(o.getLatestCommitMessage,`SELECT data
|
|
109
|
+
FROM session_events
|
|
110
|
+
WHERE session_id = ? AND type = 'git_commit'
|
|
111
|
+
ORDER BY id DESC
|
|
112
|
+
LIMIT 1`),e(o.incrementCompactCount,"UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?"),e(o.getUsageCursor,"SELECT usage_cursor FROM session_meta WHERE session_id = ?"),e(o.setUsageCursor,"UPDATE session_meta SET usage_cursor = ? WHERE session_id = ?"),e(o.upsertResume,`INSERT INTO session_resume (session_id, snapshot, event_count)
|
|
113
|
+
VALUES (?, ?, ?)
|
|
114
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
115
|
+
snapshot = excluded.snapshot,
|
|
116
|
+
event_count = excluded.event_count,
|
|
117
|
+
created_at = datetime('now'),
|
|
118
|
+
consumed = 0`),e(o.getResume,"SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?"),e(o.markResumeConsumed,"UPDATE session_resume SET consumed = 1 WHERE session_id = ?"),e(o.claimLatestUnconsumedResume,`UPDATE session_resume
|
|
119
|
+
SET consumed = 1
|
|
120
|
+
WHERE id = (
|
|
121
|
+
SELECT id FROM session_resume
|
|
122
|
+
WHERE consumed = 0
|
|
123
|
+
AND session_id != ?
|
|
124
|
+
ORDER BY created_at DESC, id DESC
|
|
125
|
+
LIMIT 1
|
|
126
|
+
)
|
|
127
|
+
RETURNING session_id, snapshot`),e(o.deleteEvents,"DELETE FROM session_events WHERE session_id = ?"),e(o.deleteMeta,"DELETE FROM session_meta WHERE session_id = ?"),e(o.deleteResume,"DELETE FROM session_resume WHERE session_id = ?"),e(o.searchEvents,`SELECT id, session_id, category, type, data, created_at
|
|
128
|
+
FROM session_events
|
|
129
|
+
WHERE (project_dir = ? OR project_dir = '')
|
|
130
|
+
AND (data LIKE '%' || ? || '%' ESCAPE '\\' OR category LIKE '%' || ? || '%' ESCAPE '\\')
|
|
131
|
+
AND (? IS NULL OR category = ?)
|
|
132
|
+
ORDER BY id ASC
|
|
133
|
+
LIMIT ?`),e(o.getOldSessions,"SELECT session_id FROM session_meta WHERE started_at < datetime('now', ? || ' days')"),e(o.incrementToolCall,`INSERT INTO tool_calls (session_id, tool, calls, bytes_returned)
|
|
134
|
+
VALUES (?, ?, 1, ?)
|
|
135
|
+
ON CONFLICT(session_id, tool) DO UPDATE SET
|
|
136
|
+
calls = calls + 1,
|
|
137
|
+
bytes_returned = bytes_returned + excluded.bytes_returned,
|
|
138
|
+
updated_at = datetime('now')`),e(o.getToolCallTotals,`SELECT COALESCE(SUM(calls), 0) AS calls,
|
|
139
|
+
COALESCE(SUM(bytes_returned), 0) AS bytes_returned
|
|
140
|
+
FROM tool_calls WHERE session_id = ?`),e(o.getToolCallByTool,`SELECT tool, calls, bytes_returned
|
|
141
|
+
FROM tool_calls WHERE session_id = ? ORDER BY calls DESC`),e(o.getEventBytesSummary,`SELECT COALESCE(SUM(bytes_avoided), 0) AS bytes_avoided,
|
|
142
|
+
COALESCE(SUM(bytes_returned), 0) AS bytes_returned
|
|
143
|
+
FROM session_events WHERE session_id = ?`)}insertEvent(e,t,r="PostToolUse",s,i){let a=b("sha256").update(t.data).digest("hex").slice(0,16).toUpperCase(),u=String(s?.projectDir??t.project_dir??this._getSessionProjectDir(e)).trim(),d=String(s?.source??t.attribution_source??"unknown"),c=Number(s?.confidence??t.attribution_confidence??0),v=Number.isFinite(c)?Math.max(0,Math.min(1,c)):0,p=C(i?.bytesAvoided),f=C(i?.bytesReturned),E=this.db.transaction(()=>{if(this.stmt(o.checkDuplicate).get(e,K,t.type,a))return;this.stmt(o.getEventCount).get(e).cnt>=Y&&this.stmt(o.evictLowestPriority).run(e),this.stmt(o.insertEvent).run(e,t.type,t.category,t.priority,t.data,u,d,v,p,f,r,a),this.stmt(o.updateMetaLastEvent).run(e)});this.withRetry(()=>E())}bulkInsertEvents(e,t,r="PostToolUse",s,i){if(!t||t.length===0)return;if(t.length===1){this.insertEvent(e,t[0],r,s?.[0],i?.[0]);return}let a=t.map((d,c)=>{let v=b("sha256").update(d.data).digest("hex").slice(0,16).toUpperCase(),p=s?.[c],f=String(p?.projectDir??d.project_dir??this._getSessionProjectDir(e)??"").trim(),E=f===""?"":S(f),L=String(p?.source??d.attribution_source??"unknown"),w=Number(p?.confidence??d.attribution_confidence??0),ce=Number.isFinite(w)?Math.max(0,Math.min(1,w)):0,P=i?.[c],ue=C(P?.bytesAvoided),de=C(P?.bytesReturned);return{event:d,dataHash:v,projectDir:E,attributionSource:L,attributionConfidence:ce,bytesAvoided:ue,bytesReturned:de}}),u=this.db.transaction(()=>{let d=this.stmt(o.getEventCount).get(e).cnt;for(let c of a)this.stmt(o.checkDuplicate).get(e,K,c.event.type,c.dataHash)||(d>=Y?this.stmt(o.evictLowestPriority).run(e):d++,this.stmt(o.insertEvent).run(e,c.event.type,c.event.category,c.event.priority,c.event.data,c.projectDir,c.attributionSource,c.attributionConfidence,c.bytesAvoided,c.bytesReturned,r,c.dataHash));this.stmt(o.updateMetaLastEvent).run(e)});this.withRetry(()=>u())}getEvents(e,t){let r=t?.limit??1e3,s=t?.type,i=t?.minPriority;return s&&i!==void 0?this.stmt(o.getEventsByTypeAndPriority).all(e,s,i,r):s?this.stmt(o.getEventsByType).all(e,s,r):i!==void 0?this.stmt(o.getEventsByPriority).all(e,i,r):this.stmt(o.getEvents).all(e,r)}getEventCount(e){return this.stmt(o.getEventCount).get(e).cnt}getEventBytesSummary(e){let t=this.stmt(o.getEventBytesSummary).get(e);return{bytesAvoided:Number(t?.bytes_avoided??0),bytesReturned:Number(t?.bytes_returned??0)}}getLatestAttributedProjectDir(e){return this.stmt(o.getLatestAttributedProject).get(e)?.project_dir||null}_getSessionProjectDir(e){try{return this.db.prepare("SELECT project_dir FROM session_meta WHERE session_id = ?").get(e)?.project_dir||""}catch(t){return l("SessionDB.getSessionProjectDir",t,this.dbPath),""}}searchEvents(e,t,r,s){try{let i=e.replace(/[%_]/g,u=>"\\"+u),a=s??null;return this.stmt(o.searchEvents).all(r,i,i,a,a,t)}catch(i){return l("SessionDB.searchEvents",i,this.dbPath),[]}}getSessionIdsForProject(e){try{let t=S(e);return this.db.prepare(`SELECT DISTINCT session_id
|
|
144
|
+
FROM session_events
|
|
145
|
+
WHERE RTRIM(REPLACE(project_dir, '\\', '/'), '/') = ?`).all(t).map(s=>s.session_id)}catch(t){return l("SessionDB.getSessionIdsForProject",t,this.dbPath),[]}}ensureSession(e,t){this.stmt(o.ensureSession).run(e,t)}getSessionStats(e){return this.stmt(o.getSessionStats).get(e)??null}getSessionRollup(e){let t=this.stmt(o.getSessionRollup).get(e),r=this.stmt(o.getMaxFileEdits).get(e),s=this.stmt(o.getLatestCommitMessage).get(e),i=this.getSessionStats(e),a=(t?.tool_calls??0)>0?t?.unique_files??0:0,u=t?.errors??0,d=Math.min(a,u);return{tool_calls:t?.tool_calls??0,errors:t?.errors??0,unique_tools:t?.unique_tools??0,unique_files:t?.unique_files??0,max_file_edits:r?.max_file_edits??0,has_commit:t?.has_commit??0,commit_message:s?.data??"",edit_test_cycles:d,duration_min:t?.duration_min??0,compact_count:i?.compact_count??0,sources_indexed:t?.sources_indexed??0,total_chunks:t?.total_chunks??0,search_queries:t?.search_queries??0}}incrementCompactCount(e){this.stmt(o.incrementCompactCount).run(e)}getUsageCursor(e){return this.stmt(o.getUsageCursor).get(e)?.usage_cursor??null}setUsageCursor(e,t){this.stmt(o.setUsageCursor).run(t,e)}upsertResume(e,t,r){this.stmt(o.upsertResume).run(e,t,r??0)}getResume(e){return this.stmt(o.getResume).get(e)??null}markResumeConsumed(e){this.stmt(o.markResumeConsumed).run(e)}claimLatestUnconsumedResume(e){let t=this.stmt(o.claimLatestUnconsumedResume).get(e);return t?{sessionId:t.session_id,snapshot:t.snapshot}:null}getLatestSessionId(){try{return this.db.prepare("SELECT session_id FROM session_meta ORDER BY started_at DESC LIMIT 1").get()?.session_id??null}catch(e){return l("SessionDB.getLatestSessionId",e,this.dbPath),null}}incrementToolCall(e,t,r=0){let s=Number.isFinite(r)&&r>0?Math.round(r):0;try{this.stmt(o.incrementToolCall).run(e,t,s)}catch(i){l("SessionDB.incrementToolCall",i,this.dbPath)}}getToolCallStats(e){try{let t=this.stmt(o.getToolCallTotals).get(e),r=this.stmt(o.getToolCallByTool).all(e),s={};for(let i of r)s[i.tool]={calls:i.calls,bytesReturned:i.bytes_returned};return{totalCalls:t?.calls??0,totalBytesReturned:t?.bytes_returned??0,byTool:s}}catch(t){return l("SessionDB.getToolCallStats",t,this.dbPath),{totalCalls:0,totalBytesReturned:0,byTool:{}}}}deleteSession(e){this.db.transaction(()=>{this.stmt(o.deleteEvents).run(e),this.stmt(o.deleteResume).run(e),this.stmt(o.deleteMeta).run(e)})()}cleanupOldSessions(e=7){let t=`-${e}`,r=this.stmt(o.getOldSessions).all(t);for(let{session_id:s}of r)this.deleteSession(s);return r.length}pruneOrphanedEvents(){let e=this.db.prepare("DELETE FROM session_events WHERE session_id NOT IN (SELECT session_id FROM session_meta)").run();return Number(e.changes??0)}};export{z as SessionDB,D as StorageDirectoryError,dt as _resetWorktreeSuffixCacheForTests,ae as applyMissingSessionEventsColumns,ct as clearStorageDirectoryCheckCacheForTests,at as describeStorageDirectorySource,Et as ensureSessionEventsSchema,ut as ensureWritableStorageDir,it as formatStorageDirectoryError,$e as getWorktreeSuffix,ie as hashProjectDirCanonical,oe as hashProjectDirLegacy,S as normalizeWorktreePath,st as resolveContentStorageDir,lt as resolveContentStorePath,rt as resolveDefaultSessionDir,gt as resolveSessionDbPath,We as resolveSessionPath,ne as resolveSessionStorageDir,ot as resolveStatsStorageDir};
|