diff --git a/README.md b/README.md index d2c068c8..771fce67 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ LLM providers cache prompts based on exact prefix matching. When DCP prunes cont **No impact for:** -- **Request-based billing** — Providers like GitHub Copilot that charge per request, not tokens. +- **Request-based billing** — Some providers charge per request, not tokens. - **Uniform token pricing** — Providers like Cerebras that bill cached and uncached tokens at the same rate. ## License diff --git a/dcp.schema.json b/dcp.schema.json index 39f2df53..474d5bab 100644 --- a/dcp.schema.json +++ b/dcp.schema.json @@ -5,6 +5,7 @@ "description": "Configuration schema for the OpenCode Dynamic Context Pruning plugin", "type": "object", "additionalProperties": false, + "allowTrailingCommas": true, "properties": { "$schema": { "type": "string", diff --git a/lib/compress/pipeline.ts b/lib/compress/pipeline.ts index 30f1b33b..cddb2be4 100644 --- a/lib/compress/pipeline.ts +++ b/lib/compress/pipeline.ts @@ -85,7 +85,15 @@ export async function finalizeSession( entries: NotificationEntry[], batchTopic: string | undefined, ): Promise { - ctx.state.manualMode = ctx.state.manualMode ? "active" : false + if (ctx.state.manualMode === "compress-pending") { + ctx.state.manualMode = false + await refreshManualMode( + ctx.state, + toolCtx.sessionID, + ctx.logger, + ctx.config.manualMode.enabled, + ) + } applyPendingCompressionDurations(ctx.state) await saveSessionState(ctx.state, ctx.logger) diff --git a/lib/hooks.ts b/lib/hooks.ts index 67030f1c..6d6f3862 100644 --- a/lib/hooks.ts +++ b/lib/hooks.ts @@ -46,6 +46,15 @@ const INTERNAL_AGENT_SIGNATURES = [ "Summarize what was done in this conversation", ] +function isInternalAgentCall(systemPrompts: string[]): boolean { + const primaryPrompt = systemPrompts[0] + if (typeof primaryPrompt !== "string" || primaryPrompt.length === 0) { + return false + } + + return INTERNAL_AGENT_SIGNATURES.some((signature) => primaryPrompt.includes(signature)) +} + export function createSystemPromptHandler( state: SessionState, logger: Logger, @@ -65,8 +74,7 @@ export function createSystemPromptHandler( return } - const systemText = output.system.join("\n") - if (INTERNAL_AGENT_SIGNATURES.some((sig) => systemText.includes(sig))) { + if (isInternalAgentCall(output.system)) { logger.info("Skipping DCP system prompt injection for internal agent") return } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index eae03327..65b3d591 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -7,6 +7,8 @@ const SUMMARY_ID_HASH_LENGTH = 16 const DCP_BLOCK_ID_TAG_REGEX = /(])[^>]*>)b\d+(<\/dcp-message-id>)/g const DCP_PAIRED_TAG_REGEX = /]*>[\s\S]*?<\/dcp[^>]*>/gi const DCP_UNPAIRED_TAG_REGEX = /<\/?dcp[^>]*>/gi +const INJECTED_MESSAGE_ID_SUFFIX_REGEX = /(?<=\n)]*>m\d+<\/dcp-message-id>\s*$/ +const HALLUCINATED_PARAMETER_SUFFIX_REGEX = /(?<=\n)m\d+<\/parameter>\s*$/ const generateStableId = (prefix: string, seed: string): string => { const hash = createHash("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH) @@ -163,7 +165,12 @@ export const replaceBlockIdsWithBlocked = (text: string): string => { } export const stripHallucinationsFromString = (text: string): string => { - return text.replace(DCP_PAIRED_TAG_REGEX, "").replace(DCP_UNPAIRED_TAG_REGEX, "") + const withoutKnownSuffixes = text + .replace(INJECTED_MESSAGE_ID_SUFFIX_REGEX, "") + .replace(HALLUCINATED_PARAMETER_SUFFIX_REGEX, "") + return withoutKnownSuffixes + .replace(DCP_PAIRED_TAG_REGEX, "") + .replace(DCP_UNPAIRED_TAG_REGEX, "") } export const stripHallucinations = (messages: WithParts[]): void => { diff --git a/lib/protected-patterns.ts b/lib/protected-patterns.ts index a59605bc..15d1535c 100644 --- a/lib/protected-patterns.ts +++ b/lib/protected-patterns.ts @@ -1,5 +1,9 @@ function normalizePath(input: string): string { - return input.replaceAll("\\\\", "/") + // A single backslash. In source, "\\" is the one-character string; the + // previous "\\\\" was a *two*-character string, so it only ever matched a + // doubled separator -- which a real Windows path does not contain. The + // normalisation was therefore a no-op on the only platform that needs it. + return input.replaceAll("\\", "/") } function escapeRegExpChar(ch: string): string { diff --git a/package-lock.json b/package-lock.json index c27ec250..d90568eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,31 @@ { "name": "@tarquinen/opencode-dcp", - "version": "3.1.14", + "version": "3.1.15", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@tarquinen/opencode-dcp", - "version": "3.1.14", + "version": "3.1.15", "license": "AGPL-3.0-or-later", "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.4.3", - "jsonc-parser": "^3.3.1" + "@opentui/core": "^0.4.5", + "@opentui/solid": "^0.4.5", + "jsonc-parser": "^3.3.1", + "solid-js": "^1.9.12" }, "devDependencies": { "@opencode-ai/plugin": "^1.4.3", - "@opentui/core": "^0.4.2", - "@opentui/solid": "^0.4.2", "@types/node": "^25.5.0", "prettier": "^3.8.1", - "solid-js": "^1.9.12", "tsup": "^8.5.1", "tsx": "^4.21.0", "typescript": "^6.0.2" }, "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/core": "^0.4.2", - "@opentui/solid": "^0.4.2", - "solid-js": "^1.9.12" + "@opencode-ai/plugin": ">=1.4.3" } }, "node_modules/@anthropic-ai/tokenizer": { @@ -60,7 +57,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -75,7 +71,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -85,7 +80,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -116,7 +110,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -133,7 +126,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -146,7 +138,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -163,7 +154,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -185,7 +175,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -195,7 +184,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -209,7 +197,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -223,7 +210,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -241,7 +227,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -254,7 +239,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -264,7 +248,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-member-expression-to-functions": "^7.29.7", @@ -282,7 +265,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -296,7 +278,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -306,7 +287,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -316,7 +296,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -326,7 +305,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -340,7 +318,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -356,7 +333,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -372,7 +348,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -388,7 +363,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.29.7", @@ -405,7 +379,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", @@ -425,7 +398,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", @@ -445,7 +417,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -460,7 +431,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -479,7 +449,6 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -935,7 +904,6 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -946,7 +914,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -957,7 +924,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -967,14 +933,12 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1024,40 +988,38 @@ } }, "node_modules/@opentui/core": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.4.2.tgz", - "integrity": "sha512-ulx6RMqftf2fm7Itf9e81GcCDMNY6NAhmnKYhllDOMYD+PxYXR+vomy2bxQNV5ow31RE7s8WQFnb7hWTRUbx2g==", - "dev": true, + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core/-/core-0.4.5.tgz", + "integrity": "sha512-JsgRTPkA6e+Vxmumxai6SElOSlRQkbzNKHlCfemlArRiLhfC1IZ9RXJo2QH4xSu+uBOWAM90uss73/pPlkdEig==", "license": "MIT", "dependencies": { - "bun-ffi-structs": "0.2.3", + "bun-ffi-structs": "0.2.4", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2" }, "optionalDependencies": { - "@opentui/core-darwin-arm64": "0.4.2", - "@opentui/core-darwin-x64": "0.4.2", - "@opentui/core-linux-arm64": "0.4.2", - "@opentui/core-linux-arm64-musl": "0.4.2", - "@opentui/core-linux-x64": "0.4.2", - "@opentui/core-linux-x64-musl": "0.4.2", - "@opentui/core-win32-arm64": "0.4.2", - "@opentui/core-win32-x64": "0.4.2" + "@opentui/core-darwin-arm64": "0.4.5", + "@opentui/core-darwin-x64": "0.4.5", + "@opentui/core-linux-arm64": "0.4.5", + "@opentui/core-linux-arm64-musl": "0.4.5", + "@opentui/core-linux-x64": "0.4.5", + "@opentui/core-linux-x64-musl": "0.4.5", + "@opentui/core-win32-arm64": "0.4.5", + "@opentui/core-win32-x64": "0.4.5" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "node_modules/@opentui/core-darwin-arm64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.4.2.tgz", - "integrity": "sha512-is+O+sS/l3E9cZXyM9pRF1WhqnE+hYSPYoZkbseR9CthJcaWPGi3R3jUJa1cLj325252jWgxVupnDqFUtKg36w==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-arm64/-/core-darwin-arm64-0.4.5.tgz", + "integrity": "sha512-8KUG0oRidnR+oW1RSZJ72/PhZLl+qRRMk5U/mieF4c0SJ5V3tYACpBZAKzQfHNd1f7QzD8FHZct1lPpQgtmkWg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1065,13 +1027,12 @@ ] }, "node_modules/@opentui/core-darwin-x64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.4.2.tgz", - "integrity": "sha512-ACi42h81DurSeybUAD1XyKT6xmXZcKeTxS54lZFi0CVZh46w0g99vNj8PlQzIFXvvFLT0e0IlRS//eWSWS2zGQ==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-darwin-x64/-/core-darwin-x64-0.4.5.tgz", + "integrity": "sha512-R2bocsg55gwjOqCp/MWFgFYzRmsduKegB6nzgFAPCvAD/L5Jf30xpWJWFlSg3x8vxe1L9WJ84dfqa4M7mZZ3wA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1079,13 +1040,12 @@ ] }, "node_modules/@opentui/core-linux-arm64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.4.2.tgz", - "integrity": "sha512-RjOx2HcjLRtGSy9WrAGSdr5M9SpJuPifPORpImx6Mciovw0ltnE0uoYjIyor82uf6/LExWC7YA2AcAl+YBxayA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64/-/core-linux-arm64-0.4.5.tgz", + "integrity": "sha512-R4MZ25a4CzOAGVjW9aj1hUfzQGVfCJwrwBDbNs2SXaIvzcZqkxCVtU4FoQ5LsaD0j/BdNQVg2CIfFkFsm1fDuQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1093,13 +1053,12 @@ ] }, "node_modules/@opentui/core-linux-arm64-musl": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.4.2.tgz", - "integrity": "sha512-heNciL2ngPU+kq1h01PHLsxn6Fr8iqTFtbxSdVbhaY3XihuIjkuXyEhFeuoa1lsXY7Bb2gpWnX5EQVWnZsAuDQ==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-arm64-musl/-/core-linux-arm64-musl-0.4.5.tgz", + "integrity": "sha512-ieqdyKI6EIYPalYAETB2wsdP83hr5Ifi+dFnBFUmdEEFHsoKwBmn2S7bsTOYlX7Bg03F4/YPIg+IvRpeC+cUJw==", "cpu": [ "arm64" ], - "dev": true, "libc": [ "musl" ], @@ -1110,13 +1069,12 @@ ] }, "node_modules/@opentui/core-linux-x64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.4.2.tgz", - "integrity": "sha512-9s0s/ooK+AhWP306By3gu+XhzcVEThC2sqKMPK1nQmGDujQhd+xOrtbtfCVcJSx62UzAovC2VNqypvP8vHByOg==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64/-/core-linux-x64-0.4.5.tgz", + "integrity": "sha512-SNyuQoxMKI1vuJhgxSSW96adWM6LqFl2SoS3GM4tGeneGOanVVG2Y06PvlytXvF4cKik97t0rqkVMRetmOs93w==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1124,13 +1082,12 @@ ] }, "node_modules/@opentui/core-linux-x64-musl": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.4.2.tgz", - "integrity": "sha512-Cjv6Bv7l3p/KLNJr5RyqCS0FmRlAGJnkA2IK3S+HkHhCOv/O02S1G+DBUY6POnyjp1eNy95vauustApobhdbig==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-linux-x64-musl/-/core-linux-x64-musl-0.4.5.tgz", + "integrity": "sha512-mKVKcIcPiSVVZZsdPSBoWwoa2/TCeQAaMDeHF7PFw2kt5bTXZPP7xxWfRQLCNIcA1eaGl59UuwUWHDR2Ve548Q==", "cpu": [ "x64" ], - "dev": true, "libc": [ "musl" ], @@ -1141,13 +1098,12 @@ ] }, "node_modules/@opentui/core-win32-arm64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.4.2.tgz", - "integrity": "sha512-mfJZrJ0TNPFRZUzXNsxAPe1YdiWsy/vbTl93+yeXGHPI1B8Qnk9V5hpzSxxEyBGhlTHSfGNtgiO+VrrdRC3kZA==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-arm64/-/core-win32-arm64-0.4.5.tgz", + "integrity": "sha512-GHTTsqeR45q2Iek9Rb7ty+x/hAKn2jZ1ujlCgPR8LBKyF7h0E1dNFryoZ7ehMc3kJndP1sKn836IemKFqxuDdQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1155,13 +1111,12 @@ ] }, "node_modules/@opentui/core-win32-x64": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.4.2.tgz", - "integrity": "sha512-P2oguG3ng3OMjAdasFSA3GhHaQXtzDUsIRDGbzWFOimpZ/zMemidp+JQ0V8V6XwK6Utk5G0aQ03oBaRCoLyYDw==", + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/core-win32-x64/-/core-win32-x64-0.4.5.tgz", + "integrity": "sha512-Y8T/yXCDGagRGiQrtmuB6AhRcPucKFs/Dre3v8kJwNYqDccI4FzUPKclZ7djfmRZNjl7JUqPhZZP/PwDpQocMg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1169,10 +1124,9 @@ ] }, "node_modules/@opentui/core/node_modules/bun-ffi-structs": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.2.3.tgz", - "integrity": "sha512-pgJiXP+hEgFo9qG51J6ItfY4ocs3vniwNzJ9WhoakB3QB2GdzQxX2EXssentPYlB2hOfJrTjO6iIQkWYzUodpg==", - "dev": true, + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/bun-ffi-structs/-/bun-ffi-structs-0.2.4.tgz", + "integrity": "sha512-AJzsqoVFs1KBbJbWHIYrVZLDC3NhTqqh25awRXqzoLzmBAKr5oqk6+CwuYHAekKx+VBCYVohBoKuRq40dV+TYg==", "license": "MIT", "peerDependencies": { "typescript": "^5" @@ -1182,7 +1136,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -1194,15 +1147,14 @@ } }, "node_modules/@opentui/solid": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.4.2.tgz", - "integrity": "sha512-zuYXsnrlsMtnXrS7QCYBdPzMtUSonG2LqnJikBR2NjEE2O4zEKvJd48n3eB1igcxjv96tiotTXRNCylYS0SNdQ==", - "dev": true, + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/@opentui/solid/-/solid-0.4.5.tgz", + "integrity": "sha512-B0RSkXnrtPVfEJOX+Hj+axjLJ3lzbG1BZw5I7Pvb9OPp48Vzg2cW2a3cSa86/q48ndLt647i/XwFPIw/jqnI5g==", "license": "MIT", "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", - "@opentui/core": "0.4.2", + "@opentui/core": "0.4.5", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", @@ -1304,9 +1256,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1321,9 +1270,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1338,9 +1284,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1355,9 +1298,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1372,9 +1312,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1389,9 +1326,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1406,9 +1340,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1423,9 +1354,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1440,9 +1368,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1457,9 +1382,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1474,9 +1396,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1491,9 +1410,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1508,9 +1424,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1635,7 +1548,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1655,7 +1567,6 @@ "version": "0.40.7", "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.7.tgz", "integrity": "sha512-/O6JWUmjv03OI9lL2ry9bUjpD5S3PclM55RRJEyCdcFZ5W2SEA/59d+l2hNsk3gI6kiWRdRPdOtqZmsQzFN1pQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "7.18.6", @@ -1672,7 +1583,6 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.18.6" @@ -1685,7 +1595,6 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/babel-plugin-module-resolver/-/babel-plugin-module-resolver-5.0.2.tgz", "integrity": "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg==", - "dev": true, "license": "MIT", "dependencies": { "find-babel-config": "^2.1.1", @@ -1699,7 +1608,6 @@ "version": "1.9.12", "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.12.tgz", "integrity": "sha512-LLqnuKVDlKpyBlMPcH6qEvs/wmS9a+NczppxJ3ryS/c0O5IiSFOIBQi9GzyiGDSbcJpx4Gr87jyFTos1MyEuWg==", - "dev": true, "license": "MIT", "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.6" @@ -1718,14 +1626,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.10.37", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -1735,10 +1641,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", - "dev": true, + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -1748,7 +1653,6 @@ "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1808,7 +1712,6 @@ "version": "1.0.30001799", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -1872,7 +1775,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -1893,14 +1795,12 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1918,7 +1818,6 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz", "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -1928,21 +1827,18 @@ "version": "1.5.373", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.373.tgz", "integrity": "sha512-G2Hym8JIf/QreuseqkDibgH8Ci8KfJzqGDKdakbhSx9UltwRBH2cBLAWU/lBX0sCdv0TlhyxQyDCnSfxgMWsjA==", - "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, "license": "MIT" }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -1955,7 +1851,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2007,7 +1902,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2035,7 +1929,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/find-babel-config/-/find-babel-config-2.1.2.tgz", "integrity": "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.3" @@ -2045,7 +1938,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "license": "MIT", "dependencies": { "locate-path": "^3.0.0" @@ -2070,7 +1962,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -2092,7 +1983,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2102,7 +1992,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2112,7 +2001,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2139,7 +2027,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -2158,7 +2045,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2171,14 +2057,12 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", - "dev": true, "license": "MIT" }, "node_modules/is-core-module": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.3" @@ -2210,14 +2094,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2230,7 +2112,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -2279,7 +2160,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "license": "MIT", "dependencies": { "p-locate": "^3.0.0", @@ -2293,7 +2173,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -2313,7 +2192,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", - "dev": true, "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -2326,7 +2204,6 @@ "version": "8.0.7", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.7.tgz", "integrity": "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -2342,7 +2219,6 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", - "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -2365,7 +2241,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/mz": { @@ -2384,7 +2259,6 @@ "version": "2.0.47", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2404,7 +2278,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -2420,7 +2293,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.0.0" @@ -2433,7 +2305,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2443,7 +2314,6 @@ "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -2456,7 +2326,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -2469,7 +2338,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2488,14 +2356,12 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -2512,14 +2378,12 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-scurry/node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -2536,7 +2400,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2578,7 +2441,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", - "dev": true, "license": "MIT", "dependencies": { "find-up": "^3.0.0" @@ -2664,14 +2526,12 @@ "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", - "dev": true, "license": "MIT" }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2758,14 +2618,12 @@ "version": "0.4.9", "resolved": "https://registry.npmjs.org/s-js/-/s-js-0.4.9.tgz", "integrity": "sha512-RtpOm+cM6O0sHg6IA70wH+UC3FZcND+rccBZpBAHzlUgNO2Bm5BN+FnM8+OBxzXdwpKWFwX11JGF0MFRkhSoIQ==", - "dev": true, "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -2775,7 +2633,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2785,7 +2642,6 @@ "version": "1.5.4", "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2819,7 +2675,6 @@ "version": "1.9.12", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.12.tgz", "integrity": "sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.1.0", @@ -2841,7 +2696,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -2859,7 +2713,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -2898,7 +2751,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3082,7 +2934,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -3113,7 +2964,6 @@ "version": "0.25.10", "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz", "integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==", - "dev": true, "license": "MIT", "peer": true, "peerDependencies": { @@ -3144,7 +2994,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, "license": "ISC" } } diff --git a/package.json b/package.json index e377f718..35326a14 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@tarquinen/opencode-dcp", - "version": "3.1.14", + "version": "3.1.15", "type": "module", "description": "OpenCode plugin that optimizes token usage by pruning obsolete tool outputs from conversation context", "main": "./dist/index.js", @@ -53,23 +53,20 @@ "author": "tarquinen", "license": "AGPL-3.0-or-later", "peerDependencies": { - "@opencode-ai/plugin": ">=1.4.3", - "@opentui/core": "^0.4.2", - "@opentui/solid": "^0.4.2", - "solid-js": "^1.9.12" + "@opencode-ai/plugin": ">=1.4.3" }, "dependencies": { "@anthropic-ai/tokenizer": "^0.0.4", "@opencode-ai/sdk": "^1.4.3", - "jsonc-parser": "^3.3.1" + "@opentui/core": "^0.4.5", + "@opentui/solid": "^0.4.5", + "jsonc-parser": "^3.3.1", + "solid-js": "^1.9.12" }, "devDependencies": { "@opencode-ai/plugin": "^1.4.3", - "@opentui/core": "^0.4.2", - "@opentui/solid": "^0.4.2", "@types/node": "^25.5.0", "prettier": "^3.8.1", - "solid-js": "^1.9.12", "tsup": "^8.5.1", "tsx": "^4.21.0", "typescript": "^6.0.2" diff --git a/tests/finalize-session.test.ts b/tests/finalize-session.test.ts new file mode 100644 index 00000000..6d5f70f7 --- /dev/null +++ b/tests/finalize-session.test.ts @@ -0,0 +1,120 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { finalizeSession } from "../lib/compress/pipeline" +import type { PluginConfig } from "../lib/config" +import { Logger } from "../lib/logger" +import { + createSessionState, + loadManualModeSetting, + saveManualModeSetting, + type WithParts, +} from "../lib/state" + +function buildConfig(manualMode = false): PluginConfig { + return { + enabled: true, + debug: false, + pruneNotification: "off", + pruneNotificationType: "chat", + commands: { enabled: true, protectedTools: [] }, + manualMode: { enabled: manualMode, automaticStrategies: true }, + turnProtection: { enabled: false, turns: 4 }, + experimental: { allowSubAgents: false, customPrompts: false }, + protectedFilePatterns: [], + compress: { + mode: "message", + permission: "allow", + showCompression: false, + maxContextLimit: 150000, + minContextLimit: 50000, + nudgeFrequency: 5, + iterationNudgeThreshold: 15, + nudgeForce: "soft", + protectedTools: ["task"], + protectTags: false, + protectUserMessages: false, + }, + strategies: { + deduplication: { enabled: true, protectedTools: [] }, + purgeErrors: { enabled: true, turns: 4, protectedTools: [] }, + }, + } as PluginConfig +} + +function buildToolContext(state: ReturnType, manualMode = false) { + return { + client: { session: { get: async () => ({}) } }, + state, + logger: new Logger(false), + config: buildConfig(manualMode), + prompts: { + reload() {}, + getRuntimePrompts() { + return {} as any + }, + }, + } +} + +test("finalizeSession resets compress-pending to auto mode", async () => { + const sessionId = `finalize-compress-pending-${Date.now()}` + const state = createSessionState() + state.sessionId = sessionId + state.manualMode = "compress-pending" + + await finalizeSession( + buildToolContext(state) as any, + { sessionID: sessionId, metadata: () => {}, ask: async () => {} }, + [] as WithParts[], + [], + undefined, + ) + + assert.equal(state.manualMode, false) + + const persisted = await loadManualModeSetting(sessionId, new Logger(false)) + assert.equal(persisted, false) +}) + +test("finalizeSession restores persisted manual mode after compression", async () => { + const sessionId = `finalize-persisted-manual-${Date.now()}` + const logger = new Logger(false) + await saveManualModeSetting(sessionId, true, logger) + + const state = createSessionState() + state.sessionId = sessionId + state.manualMode = "compress-pending" + + await finalizeSession( + buildToolContext(state) as any, + { sessionID: sessionId, metadata: () => {}, ask: async () => {} }, + [] as WithParts[], + [], + undefined, + ) + + assert.equal(state.manualMode, "active") + + const persisted = await loadManualModeSetting(sessionId, logger) + assert.equal(persisted, true) +}) + +test("finalizeSession restores configured manual mode after compression", async () => { + const sessionId = `finalize-configured-manual-${Date.now()}` + const state = createSessionState() + state.sessionId = sessionId + state.manualMode = "compress-pending" + + await finalizeSession( + buildToolContext(state, true) as any, + { sessionID: sessionId, metadata: () => {}, ask: async () => {} }, + [] as WithParts[], + [], + undefined, + ) + + assert.equal(state.manualMode, "active") + + const persisted = await loadManualModeSetting(sessionId, new Logger(false)) + assert.equal(persisted, true) +}) diff --git a/tests/hooks-permission.test.ts b/tests/hooks-permission.test.ts index 71be03a4..58d26c83 100644 --- a/tests/hooks-permission.test.ts +++ b/tests/hooks-permission.test.ts @@ -114,6 +114,69 @@ test("system prompt handler caches full model context for percentage thresholds" assert.equal(state.modelContextLimit, 200000) }) +function buildPromptStore() { + return { + reload() {}, + getRuntimePrompts() { + return { + system: "DCP-RUNTIME-PROMPT", + manualExtension: "", + subagentExtension: "", + } + }, + } as any +} + +test("system prompt handler injects nudges for main session with bundled internal prompts", async () => { + const state = createSessionState() + const handler = createSystemPromptHandler( + state, + new Logger(false), + buildConfig("allow"), + buildPromptStore(), + ) + const output = { + system: [ + "You are the primary coding assistant for this repository.", + "You are a title generator for short session names.", + ], + } + + await handler( + { + sessionID: "session-1", + model: { limit: { context: 200000 } }, + } as any, + output, + ) + + assert.match(output.system[output.system.length - 1], /DCP-RUNTIME-PROMPT/) +}) + +test("system prompt handler skips injection for internal agent calls", async () => { + const state = createSessionState() + const handler = createSystemPromptHandler( + state, + new Logger(false), + buildConfig("allow"), + buildPromptStore(), + ) + const output = { + system: ["You are a title generator. Return only a short title."], + } + + await handler( + { + sessionID: "session-1", + model: { limit: { context: 200000 } }, + } as any, + output, + ) + + assert.equal(output.system.length, 1) + assert.doesNotMatch(output.system[0], /DCP-RUNTIME-PROMPT/) +}) + test("chat message transform strips hallucinated tags even when compress is denied", async () => { const state = createSessionState() const logger = new Logger(false) diff --git a/tests/message-priority.test.ts b/tests/message-priority.test.ts index 1342ca1b..d6105eae 100644 --- a/tests/message-priority.test.ts +++ b/tests/message-priority.test.ts @@ -814,6 +814,37 @@ test("hallucination stripping does not affect non-dcp tags", async () => { ) }) +test("hallucination stripping preserves content when dcp-message-id is mentioned in text (issue #556)", () => { + const input = + "The tag called `` is used to track messages. " + + "This text should survive.\n\n" + + "m0369" + + assert.equal( + stripHallucinationsFromString(input), + "The tag called `` is used to track messages. This text should survive.\n\n", + ) +}) + +test("hallucination stripping handles priority on injected message-id suffixes", () => { + const input = + "The tag called `` is used to track messages. " + + "This text should survive.\n\n" + + 'm0370' + + assert.equal( + stripHallucinationsFromString(input), + "The tag called `` is used to track messages. This text should survive.\n\n", + ) +}) + +test("hallucination stripping removes trailing mXXXX artifact (issue #555)", () => { + assert.equal( + stripHallucinationsFromString("Total: maybe 20 lines changed.\n\nm0340\n\n"), + "Total: maybe 20 lines changed.\n\n", + ) +}) + test("injectMessageIds skips empty assistant messages to avoid prefill (issue #463)", () => { const sessionID = "ses_empty_assistant" const messages: WithParts[] = [ diff --git a/tests/protected-patterns.test.ts b/tests/protected-patterns.test.ts new file mode 100644 index 00000000..49c941a1 --- /dev/null +++ b/tests/protected-patterns.test.ts @@ -0,0 +1,104 @@ +import assert from "node:assert/strict" +import test from "node:test" +import { + getFilePathsFromParameters, + isFilePathProtected, + isToolNameProtected, + matchesGlob, +} from "../lib/protected-patterns" + +// A single backslash, built from its char code so the intent survives any later +// reformatting of this file. The bug being pinned here was precisely an escaping +// mistake in a string literal, so the tests avoid writing one by hand. +const BS = String.fromCharCode(92) +const winPath = (...segments: string[]) => segments.join(BS) + +test("matchesGlob treats a Windows separator as a path separator", () => { + // `protectedFilePatterns` is documented with forward slashes ("**/*.config.ts"), + // but on Windows the tool parameters carry backslashes. Both sides are + // normalised, so the same pattern must match either spelling of the same file. + const path = winPath("C:", "repo", "src", "config", "secrets.ts") + + assert.equal(matchesGlob(path, "**/secrets.ts"), true) + assert.equal(matchesGlob(path, "**/config/*.ts"), true) + assert.equal(matchesGlob(path, "C:/repo/src/**"), true) + assert.equal(matchesGlob(path, "**/*.ts"), true) +}) + +test("matchesGlob accepts a pattern written with Windows separators", () => { + // Normalisation applies to the pattern too, so a user who copies a path out of + // Explorer and uses it as a pattern gets the same result as the documented form. + const pattern = winPath("**", "config", "*.ts") + + assert.equal(matchesGlob("C:/repo/src/config/secrets.ts", pattern), true) + assert.equal(matchesGlob(winPath("C:", "repo", "src", "config", "secrets.ts"), pattern), true) +}) + +test("a single-segment wildcard still does not cross a Windows separator", () => { + // `*` is defined as "[^/]*". Normalisation must convert separators rather than + // erase them, or `*` would silently start matching across directories. + assert.equal(matchesGlob(winPath("src", "config", "secrets.ts"), "src/*"), false) + assert.equal(matchesGlob(winPath("src", "config", "secrets.ts"), "src/*/*.ts"), true) + assert.equal(matchesGlob(winPath("src", "secrets.ts"), "src/*"), true) +}) + +test("isFilePathProtected protects a Windows path the user configured", () => { + // The end-to-end shape: a `read` tool call on Windows, checked against the + // documented pattern style. This is the assertion that failed before the fix -- + // the file was protected on POSIX and unprotected on Windows. + const patterns = ["**/secrets.ts", "**/.env"] + + for (const path of [ + "C:/repo/src/config/secrets.ts", + winPath("C:", "repo", "src", "config", "secrets.ts"), + ]) { + const paths = getFilePathsFromParameters("read", { filePath: path }) + assert.equal(isFilePathProtected(paths, patterns), true, `not protected: ${path}`) + } +}) + +test("isFilePathProtected still returns false for a genuinely unmatched path", () => { + // The fix must widen matching only for separators, not for anything else. + const paths = getFilePathsFromParameters("read", { + filePath: winPath("C:", "repo", "src", "main.ts"), + }) + + assert.equal(isFilePathProtected(paths, ["**/secrets.ts"]), false) + assert.equal(isFilePathProtected(paths, []), false) + assert.equal(isFilePathProtected([], ["**/*.ts"]), false) +}) + +test("multiedit and apply_patch paths are protected on Windows too", () => { + // These two tools carry paths in shapes of their own, so they need their own + // coverage: a nested `edits` array and paths embedded in patch text. + const patterns = ["**/secrets.ts"] + + const multiedit = getFilePathsFromParameters("multiedit", { + filePath: winPath("src", "main.ts"), + edits: [{ filePath: winPath("src", "config", "secrets.ts") }], + }) + assert.equal(isFilePathProtected(multiedit, patterns), true) + + const patch = getFilePathsFromParameters("apply_patch", { + patchText: `*** Update File: ${winPath("src", "config", "secrets.ts")}\n@@\n-a\n+b\n`, + }) + assert.equal(isFilePathProtected(patch, patterns), true) +}) + +test("isToolNameProtected is unaffected by separator normalisation", () => { + // Tool names contain no separators; this pins that the shared helper does not + // change their behaviour. + assert.equal(isToolNameProtected("bash", ["bash"]), true) + assert.equal(isToolNameProtected("bash", ["ba*"]), true) + assert.equal(isToolNameProtected("bash", ["read"]), false) + assert.equal(isToolNameProtected("bash", []), false) + assert.equal(isToolNameProtected("", ["bash"]), false) +}) + +test("matchesGlob rejects an empty pattern and handles regex metacharacters", () => { + // Pattern text is interpolated into a RegExp, so characters that mean something + // there must be matched literally. + assert.equal(matchesGlob("a/b.ts", ""), false) + assert.equal(matchesGlob("src/a+b(1).ts", "src/a+b(1).ts"), true) + assert.equal(matchesGlob("src/axb1.ts", "src/a+b(1).ts"), false) +})