@sayknow-cli/coding-agent 0.5.6 → 0.5.9
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/CHANGELOG.md +16 -1
- package/dist/types/config/settings-schema.d.ts +64 -0
- package/dist/types/system-prompt.d.ts +5 -0
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +7 -7
- package/scripts/compile-args.ts +8 -4
- package/src/config/model-profiles.ts +15 -15
- package/src/config/settings-schema.ts +70 -0
- package/src/prompts/system/system-prompt.md +8 -0
- package/src/sdk/session.ts +2 -0
- package/src/session/agent-session.ts +66 -1
- package/src/system-prompt.ts +7 -0
- package/src/tools/fetch.ts +7 -2
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +35 -0
- package/vendor/mupdf/mupdf-wasm.wasm +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
-
## [0.5.
|
|
5
|
+
## [0.5.9] - 2026-09-10
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed published-install startup crash from the 0.5.8 PDF wasm embed. The asset was imported from `../../../../node_modules/mupdf/dist/mupdf-wasm.wasm`, which only exists in the monorepo checkout — after `bun install -g` / `skc update` the same relative path resolved outside the package and aborted before the TUI opened. The wasm is now vendored under `packages/coding-agent/vendor/mupdf/` and imported package-locally so source installs and compiled binaries both resolve it.
|
|
10
|
+
|
|
11
|
+
## [0.5.7] - 2026-09-10
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added opt-in `reasoningLanguage: english`. Model reasoning quality on technical work is generally better in English, but that previously meant answering in English too. With the setting on, the system prompt gains a `<reasoning-language>` block asking the agent to reason in English while keeping user-facing answers in the language the user used; correctness, safety, and communication requirements are explicitly unchanged. The default `off` adds nothing to the prompt.
|
|
16
|
+
- Added opt-in `compaction.adaptive.*` settings — `enabled`, `baseThresholdPercent`, `aggression`, `turnWindow`, and `minThresholdPercent` — and wired a per-session call-rate tracker into the auto-compaction threshold decision. Adaptive mode is disabled by default and the existing fixed `compaction.thresholdTokens` / `compaction.thresholdPercent` precedence is preserved. `getGroup("compaction")` flattens dotted keys, so the nested options object is assembled explicitly at the decision site rather than inferred, and a completed compaction resets the tracker in exactly one place.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Restored PDF conversion in standalone binaries (ported from upstream #5433). Compiled releases kept mupdf `--external`, but compiled-Bun module resolution cannot satisfy markit's bare `import("mupdf")`, and mupdf's Emscripten loader cannot find `mupdf-wasm.wasm` inside the bunfs — so every standalone PDF read fell back to raw bytes behind a generic "PDF support requires 'mupdf'" note while the real cause was discarded. mupdf is now bundled: the wasm is embedded via `with { type: "file" }` and routed to the loader through a `locateFile` hook seeded before the first mupdf import, markit-ai's bundler-hostile `require("mupdf")` image-render call is patched to a dynamic import (`patches/markit-ai@0.5.3.patch`), and conversion failures re-run the import so diagnostics carry the actual error. The fetch tool also accepts any non-empty markit conversion instead of discarding short documents (e.g. the 14-character W3C dummy.pdf) into a raw-bytes fallback.
|
|
6
21
|
|
|
7
22
|
## [0.5.4] - 2026-08-28
|
|
8
23
|
|
|
@@ -523,6 +523,25 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
523
523
|
}];
|
|
524
524
|
};
|
|
525
525
|
};
|
|
526
|
+
readonly reasoningLanguage: {
|
|
527
|
+
readonly type: "enum";
|
|
528
|
+
readonly values: readonly ["off", "english"];
|
|
529
|
+
readonly default: "off";
|
|
530
|
+
readonly ui: {
|
|
531
|
+
readonly tab: "appearance";
|
|
532
|
+
readonly label: "Reasoning Language";
|
|
533
|
+
readonly description: "Reason through technical work in English while answering in your own language";
|
|
534
|
+
readonly options: readonly [{
|
|
535
|
+
readonly value: "off";
|
|
536
|
+
readonly label: "Off";
|
|
537
|
+
readonly description: "Do not add reasoning-language guidance";
|
|
538
|
+
}, {
|
|
539
|
+
readonly value: "english";
|
|
540
|
+
readonly label: "English";
|
|
541
|
+
readonly description: "Reason in English and answer in the language you used";
|
|
542
|
+
}];
|
|
543
|
+
};
|
|
544
|
+
};
|
|
526
545
|
readonly colorBlindMode: {
|
|
527
546
|
readonly type: "boolean";
|
|
528
547
|
readonly default: false;
|
|
@@ -1958,6 +1977,51 @@ export declare const SETTINGS_SCHEMA: {
|
|
|
1958
1977
|
}];
|
|
1959
1978
|
};
|
|
1960
1979
|
};
|
|
1980
|
+
readonly "compaction.adaptive.enabled": {
|
|
1981
|
+
readonly type: "boolean";
|
|
1982
|
+
readonly default: false;
|
|
1983
|
+
readonly ui: {
|
|
1984
|
+
readonly tab: "context";
|
|
1985
|
+
readonly label: "Adaptive Compaction";
|
|
1986
|
+
readonly description: "Adjust compaction frequency by context size and call rate";
|
|
1987
|
+
};
|
|
1988
|
+
};
|
|
1989
|
+
readonly "compaction.adaptive.baseThresholdPercent": {
|
|
1990
|
+
readonly type: "number";
|
|
1991
|
+
readonly default: 85;
|
|
1992
|
+
readonly ui: {
|
|
1993
|
+
readonly tab: "context";
|
|
1994
|
+
readonly label: "Adaptive Base Threshold %";
|
|
1995
|
+
readonly description: "Base context percentage used when adaptive compaction is enabled";
|
|
1996
|
+
};
|
|
1997
|
+
};
|
|
1998
|
+
readonly "compaction.adaptive.aggression": {
|
|
1999
|
+
readonly type: "number";
|
|
2000
|
+
readonly default: 0.15;
|
|
2001
|
+
readonly ui: {
|
|
2002
|
+
readonly tab: "context";
|
|
2003
|
+
readonly label: "Adaptive Aggression";
|
|
2004
|
+
readonly description: "How strongly call rate lowers the compaction threshold; use 0 to 1";
|
|
2005
|
+
};
|
|
2006
|
+
};
|
|
2007
|
+
readonly "compaction.adaptive.turnWindow": {
|
|
2008
|
+
readonly type: "number";
|
|
2009
|
+
readonly default: 15;
|
|
2010
|
+
readonly ui: {
|
|
2011
|
+
readonly tab: "context";
|
|
2012
|
+
readonly label: "Call-Rate Window";
|
|
2013
|
+
readonly description: "Minutes of recent calls considered by adaptive compaction";
|
|
2014
|
+
};
|
|
2015
|
+
};
|
|
2016
|
+
readonly "compaction.adaptive.minThresholdPercent": {
|
|
2017
|
+
readonly type: "number";
|
|
2018
|
+
readonly default: 50;
|
|
2019
|
+
readonly ui: {
|
|
2020
|
+
readonly tab: "context";
|
|
2021
|
+
readonly label: "Adaptive Floor %";
|
|
2022
|
+
readonly description: "Lowest context percentage adaptive compaction may lower the threshold to";
|
|
2023
|
+
};
|
|
2024
|
+
};
|
|
1961
2025
|
readonly "compaction.handoffSaveToDisk": {
|
|
1962
2026
|
readonly type: "boolean";
|
|
1963
2027
|
readonly default: false;
|
|
@@ -83,6 +83,11 @@ export interface BuildSystemPromptOptions {
|
|
|
83
83
|
alwaysApplyRules?: AlwaysApplyRule[];
|
|
84
84
|
/** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
|
|
85
85
|
secretsEnabled?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Reason through technical work in English while answering in the language the
|
|
88
|
+
* user used. Affects prompt guidance only. Default: "off".
|
|
89
|
+
*/
|
|
90
|
+
reasoningLanguage?: "off" | "english";
|
|
86
91
|
/** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
87
92
|
workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
|
|
88
93
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureMupdfWasmResolution(): void;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@sayknow-cli/coding-agent",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.9",
|
|
5
5
|
"description": "Sayknow-CLI CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://sayknow-cli.com",
|
|
7
7
|
"author": "jaybeyond",
|
|
@@ -54,12 +54,12 @@
|
|
|
54
54
|
"@agentclientprotocol/sdk": "1.3.0",
|
|
55
55
|
"@babel/parser": "^7.29.3",
|
|
56
56
|
"@mozilla/readability": "^0.6.0",
|
|
57
|
-
"@sayknow-cli/stats": "0.5.
|
|
58
|
-
"@sayknow-cli/agent-core": "0.5.
|
|
59
|
-
"@sayknow-cli/ai": "0.5.
|
|
60
|
-
"@sayknow-cli/natives": "0.5.
|
|
61
|
-
"@sayknow-cli/tui": "0.5.
|
|
62
|
-
"@sayknow-cli/utils": "0.5.
|
|
57
|
+
"@sayknow-cli/stats": "0.5.9",
|
|
58
|
+
"@sayknow-cli/agent-core": "0.5.9",
|
|
59
|
+
"@sayknow-cli/ai": "0.5.9",
|
|
60
|
+
"@sayknow-cli/natives": "0.5.9",
|
|
61
|
+
"@sayknow-cli/tui": "0.5.9",
|
|
62
|
+
"@sayknow-cli/utils": "0.5.9",
|
|
63
63
|
"@puppeteer/browsers": "^2.13.0",
|
|
64
64
|
"@types/turndown": "5.0.6",
|
|
65
65
|
"@xterm/headless": "^6.0.0",
|
package/scripts/compile-args.ts
CHANGED
|
@@ -18,7 +18,14 @@ const compiledDefineFlags = ['process.env.PI_COMPILED="true"'];
|
|
|
18
18
|
const releaseDefineFlags = [...compiledDefineFlags, 'process.env.SKC_BUILD_CHANNEL="release"'];
|
|
19
19
|
const devDefineFlags = [...compiledDefineFlags, 'process.env.SKC_BUILD_CHANNEL="dev"'];
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// NOTE: mupdf must NOT be marked --external here. Compiled binaries cannot
|
|
22
|
+
// resolve bare "mupdf" imports (and its Emscripten loader cannot find the
|
|
23
|
+
// wasm asset inside the bunfs), which broke markit PDF conversion in every
|
|
24
|
+
// standalone release (upstream #5433). mupdf is bundled instead: its wasm is
|
|
25
|
+
// embedded via `with { type: "file" }` and routed to the loader through the
|
|
26
|
+
// hook in packages/coding-agent/src/utils/mupdf-wasm.ts, and the bundled
|
|
27
|
+
// `require("mupdf")` inside markit-ai is replaced by the patch in
|
|
28
|
+
// patches/markit-ai@0.5.3.patch.
|
|
22
29
|
|
|
23
30
|
export const releaseEntrypoints = [
|
|
24
31
|
"./packages/coding-agent/src/cli.ts",
|
|
@@ -57,8 +64,6 @@ export function buildReleaseCompileArgs(target: string, outfile: string): string
|
|
|
57
64
|
outfile,
|
|
58
65
|
target,
|
|
59
66
|
defines: releaseDefineFlags,
|
|
60
|
-
|
|
61
|
-
externals: compiledExternalPackages,
|
|
62
67
|
});
|
|
63
68
|
}
|
|
64
69
|
|
|
@@ -68,7 +73,6 @@ export function buildDevCompileArgs(outfile = "dist/skc"): string[] {
|
|
|
68
73
|
entrypoints: devEntrypoints,
|
|
69
74
|
outfile,
|
|
70
75
|
defines: devDefineFlags,
|
|
71
|
-
externals: compiledExternalPackages,
|
|
72
76
|
});
|
|
73
77
|
}
|
|
74
78
|
|
|
@@ -149,25 +149,25 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
149
149
|
architect: "anthropic/claude-fable-5:xhigh",
|
|
150
150
|
}),
|
|
151
151
|
profile("glm-eco", ["zai"], {
|
|
152
|
-
default: "zai/glm-5.
|
|
153
|
-
executor: "zai/glm-5.
|
|
154
|
-
planner: "zai/glm-5.
|
|
155
|
-
critic: "zai/glm-5.
|
|
156
|
-
architect: "zai/glm-5.
|
|
152
|
+
default: "zai/glm-5.3:low",
|
|
153
|
+
executor: "zai/glm-5.3:minimal",
|
|
154
|
+
planner: "zai/glm-5.3:low",
|
|
155
|
+
critic: "zai/glm-5.3:medium",
|
|
156
|
+
architect: "zai/glm-5.3:high",
|
|
157
157
|
}),
|
|
158
158
|
profile("glm-medium", ["zai"], {
|
|
159
|
-
default: "zai/glm-5.
|
|
160
|
-
executor: "zai/glm-5.
|
|
161
|
-
planner: "zai/glm-5.
|
|
162
|
-
critic: "zai/glm-5.
|
|
163
|
-
architect: "zai/glm-5.
|
|
159
|
+
default: "zai/glm-5.3:medium",
|
|
160
|
+
executor: "zai/glm-5.3:low",
|
|
161
|
+
planner: "zai/glm-5.3:medium",
|
|
162
|
+
critic: "zai/glm-5.3:high",
|
|
163
|
+
architect: "zai/glm-5.3:xhigh",
|
|
164
164
|
}),
|
|
165
165
|
profile("glm-pro", ["zai"], {
|
|
166
|
-
default: "zai/glm-5.
|
|
167
|
-
executor: "zai/glm-5.
|
|
168
|
-
planner: "zai/glm-5.
|
|
169
|
-
critic: "zai/glm-5.
|
|
170
|
-
architect: "zai/glm-5.
|
|
166
|
+
default: "zai/glm-5.3:xhigh",
|
|
167
|
+
executor: "zai/glm-5.3:medium",
|
|
168
|
+
planner: "zai/glm-5.3:high",
|
|
169
|
+
critic: "zai/glm-5.3:xhigh",
|
|
170
|
+
architect: "zai/glm-5.3:xhigh",
|
|
171
171
|
}),
|
|
172
172
|
profile("kimi-coding-plan-eco", ["kimi-code"], {
|
|
173
173
|
default: "kimi-code/k3:low",
|
|
@@ -581,6 +581,25 @@ export const SETTINGS_SCHEMA = {
|
|
|
581
581
|
},
|
|
582
582
|
},
|
|
583
583
|
|
|
584
|
+
reasoningLanguage: {
|
|
585
|
+
type: "enum",
|
|
586
|
+
values: ["off", "english"] as const,
|
|
587
|
+
default: "off",
|
|
588
|
+
ui: {
|
|
589
|
+
tab: "appearance",
|
|
590
|
+
label: "Reasoning Language",
|
|
591
|
+
description: "Reason through technical work in English while answering in your own language",
|
|
592
|
+
options: [
|
|
593
|
+
{ value: "off", label: "Off", description: "Do not add reasoning-language guidance" },
|
|
594
|
+
{
|
|
595
|
+
value: "english",
|
|
596
|
+
label: "English",
|
|
597
|
+
description: "Reason in English and answer in the language you used",
|
|
598
|
+
},
|
|
599
|
+
],
|
|
600
|
+
},
|
|
601
|
+
},
|
|
602
|
+
|
|
584
603
|
colorBlindMode: {
|
|
585
604
|
type: "boolean",
|
|
586
605
|
default: false,
|
|
@@ -1625,6 +1644,57 @@ export const SETTINGS_SCHEMA = {
|
|
|
1625
1644
|
},
|
|
1626
1645
|
},
|
|
1627
1646
|
|
|
1647
|
+
// Adaptive compaction. Opt-in; the fixed thresholds above keep precedence.
|
|
1648
|
+
"compaction.adaptive.enabled": {
|
|
1649
|
+
type: "boolean",
|
|
1650
|
+
default: false,
|
|
1651
|
+
ui: {
|
|
1652
|
+
tab: "context",
|
|
1653
|
+
label: "Adaptive Compaction",
|
|
1654
|
+
description: "Adjust compaction frequency by context size and call rate",
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
|
|
1658
|
+
"compaction.adaptive.baseThresholdPercent": {
|
|
1659
|
+
type: "number",
|
|
1660
|
+
default: 85,
|
|
1661
|
+
ui: {
|
|
1662
|
+
tab: "context",
|
|
1663
|
+
label: "Adaptive Base Threshold %",
|
|
1664
|
+
description: "Base context percentage used when adaptive compaction is enabled",
|
|
1665
|
+
},
|
|
1666
|
+
},
|
|
1667
|
+
|
|
1668
|
+
"compaction.adaptive.aggression": {
|
|
1669
|
+
type: "number",
|
|
1670
|
+
default: 0.15,
|
|
1671
|
+
ui: {
|
|
1672
|
+
tab: "context",
|
|
1673
|
+
label: "Adaptive Aggression",
|
|
1674
|
+
description: "How strongly call rate lowers the compaction threshold; use 0 to 1",
|
|
1675
|
+
},
|
|
1676
|
+
},
|
|
1677
|
+
|
|
1678
|
+
"compaction.adaptive.turnWindow": {
|
|
1679
|
+
type: "number",
|
|
1680
|
+
default: 15,
|
|
1681
|
+
ui: {
|
|
1682
|
+
tab: "context",
|
|
1683
|
+
label: "Call-Rate Window",
|
|
1684
|
+
description: "Minutes of recent calls considered by adaptive compaction",
|
|
1685
|
+
},
|
|
1686
|
+
},
|
|
1687
|
+
|
|
1688
|
+
"compaction.adaptive.minThresholdPercent": {
|
|
1689
|
+
type: "number",
|
|
1690
|
+
default: 50,
|
|
1691
|
+
ui: {
|
|
1692
|
+
tab: "context",
|
|
1693
|
+
label: "Adaptive Floor %",
|
|
1694
|
+
description: "Lowest context percentage adaptive compaction may lower the threshold to",
|
|
1695
|
+
},
|
|
1696
|
+
},
|
|
1697
|
+
|
|
1628
1698
|
"compaction.handoffSaveToDisk": {
|
|
1629
1699
|
type: "boolean",
|
|
1630
1700
|
default: false,
|
|
@@ -42,6 +42,14 @@ Optimize for correctness first, maintainability second, and brevity third. Prefe
|
|
|
42
42
|
- Do not defer actionable work. Underpromise and overdeliver: report only what is done or in progress, never announce remaining work instead of doing it.
|
|
43
43
|
</communication>
|
|
44
44
|
|
|
45
|
+
{{#if reasoningLanguageEnglish}}
|
|
46
|
+
<reasoning-language>
|
|
47
|
+
- Reason through development and technical problem-solving in English.
|
|
48
|
+
- Keep user-facing answers in the language the user requested or used.
|
|
49
|
+
- This changes reasoning language only; it does not relax correctness, safety, or communication requirements.
|
|
50
|
+
</reasoning-language>
|
|
51
|
+
{{/if}}
|
|
52
|
+
|
|
45
53
|
<completion-contract>
|
|
46
54
|
- Never present partial work as complete.
|
|
47
55
|
- Never suppress tests or warnings to make code pass.
|
package/src/sdk/session.ts
CHANGED
|
@@ -2197,6 +2197,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2197
2197
|
|
|
2198
2198
|
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
2199
2199
|
const eagerTasks = settings.get("task.eager");
|
|
2200
|
+
const reasoningLanguage = settings.get("reasoningLanguage");
|
|
2200
2201
|
const intentTracingEnabled = resolveIntentTracingEnabled(
|
|
2201
2202
|
settings.get("tools.intentTracing"),
|
|
2202
2203
|
options.hasUI ?? false,
|
|
@@ -2257,6 +2258,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2257
2258
|
toolDiscoveryActive: effectiveDiscoveryMode === "all" || mcpDiscoveryEnabled,
|
|
2258
2259
|
eagerTasks,
|
|
2259
2260
|
secretsEnabled,
|
|
2261
|
+
reasoningLanguage,
|
|
2260
2262
|
workspaceTree: workspaceTreePromise,
|
|
2261
2263
|
subagent: options.parentTaskPrefix !== undefined,
|
|
2262
2264
|
});
|
|
@@ -42,10 +42,13 @@ import {
|
|
|
42
42
|
} from "@sayknow-cli/agent-core";
|
|
43
43
|
import { normalizeMessagesForProvider } from "@sayknow-cli/agent-core/agent-loop";
|
|
44
44
|
import {
|
|
45
|
+
type AdaptiveCompactionOptions,
|
|
46
|
+
AdaptiveCompactionTracker,
|
|
45
47
|
AUTO_HANDOFF_THRESHOLD_FOCUS,
|
|
46
48
|
CompactionCancelledError,
|
|
47
49
|
type CompactionPreparation,
|
|
48
50
|
type CompactionResult,
|
|
51
|
+
type CompactionSettings as CoreCompactionSettings,
|
|
49
52
|
calculateContextTokens,
|
|
50
53
|
calculatePromptTokens,
|
|
51
54
|
collectEntriesForBranchSummary,
|
|
@@ -10770,6 +10773,43 @@ export class AgentSession {
|
|
|
10770
10773
|
return 0;
|
|
10771
10774
|
}
|
|
10772
10775
|
|
|
10776
|
+
/**
|
|
10777
|
+
* Call-rate tracker backing opt-in adaptive compaction. Always maintained so
|
|
10778
|
+
* enabling the setting mid-session has immediate, honest state; the recorded
|
|
10779
|
+
* rate only reaches a threshold decision while `compaction.adaptive.enabled`
|
|
10780
|
+
* is true.
|
|
10781
|
+
*/
|
|
10782
|
+
#adaptiveCompaction = new AdaptiveCompactionTracker();
|
|
10783
|
+
|
|
10784
|
+
#adaptiveCompactionOptions(): AdaptiveCompactionOptions {
|
|
10785
|
+
return {
|
|
10786
|
+
enabled: this.settings.get("compaction.adaptive.enabled"),
|
|
10787
|
+
baseThresholdPercent: this.settings.get("compaction.adaptive.baseThresholdPercent"),
|
|
10788
|
+
aggression: this.settings.get("compaction.adaptive.aggression"),
|
|
10789
|
+
turnWindow: this.settings.get("compaction.adaptive.turnWindow"),
|
|
10790
|
+
minThresholdPercent: this.settings.get("compaction.adaptive.minThresholdPercent"),
|
|
10791
|
+
};
|
|
10792
|
+
}
|
|
10793
|
+
|
|
10794
|
+
/**
|
|
10795
|
+
* Compaction settings for a threshold decision.
|
|
10796
|
+
*
|
|
10797
|
+
* `getGroup("compaction")` flattens `compaction.adaptive.*` into dotted keys,
|
|
10798
|
+
* so the nested options object is assembled explicitly here. The tracker
|
|
10799
|
+
* window follows the configured minutes so a settings change takes effect
|
|
10800
|
+
* without restarting the session.
|
|
10801
|
+
*/
|
|
10802
|
+
#compactionSettingsWithAdaptive(): CoreCompactionSettings {
|
|
10803
|
+
const adaptive = this.#adaptiveCompactionOptions();
|
|
10804
|
+
const windowMinutes = Number.isFinite(adaptive.turnWindow) ? Math.max(1, adaptive.turnWindow) : 15;
|
|
10805
|
+
this.#adaptiveCompaction.setWindowMs(windowMinutes * 60_000);
|
|
10806
|
+
return {
|
|
10807
|
+
...this.settings.getGroup("compaction"),
|
|
10808
|
+
adaptive,
|
|
10809
|
+
adaptiveState: this.#adaptiveCompaction.decisionState(),
|
|
10810
|
+
};
|
|
10811
|
+
}
|
|
10812
|
+
|
|
10773
10813
|
/**
|
|
10774
10814
|
* Evidence-gated below-threshold maintenance pruning (Finding 13). Runs
|
|
10775
10815
|
* #pruneToolOutputs ONCE, below the compaction threshold, only when it is
|
|
@@ -11436,13 +11476,15 @@ export class AgentSession {
|
|
|
11436
11476
|
}
|
|
11437
11477
|
return this.#scheduleOverflowRetryContinuation(generation);
|
|
11438
11478
|
}
|
|
11439
|
-
const compactionSettings = this
|
|
11479
|
+
const compactionSettings = this.#compactionSettingsWithAdaptive();
|
|
11440
11480
|
if (!compactionSettings.enabled || compactionSettings.strategy === "off") return false;
|
|
11441
11481
|
|
|
11442
11482
|
// Case 2: Threshold - turn succeeded but context is getting large
|
|
11443
11483
|
// Skip if this was an error (non-overflow errors don't have usage data)
|
|
11444
11484
|
if (assistantMessage.stopReason === "error") return false;
|
|
11445
11485
|
let contextTokens = calculateContextTokens(assistantMessage.usage);
|
|
11486
|
+
this.#adaptiveCompaction.recordCall(contextTokens);
|
|
11487
|
+
compactionSettings.adaptiveState = this.#adaptiveCompaction.decisionState();
|
|
11446
11488
|
// Model maxTokens is a capability ceiling, not a per-turn reservation.
|
|
11447
11489
|
// Auto maintenance should track actual context fullness.
|
|
11448
11490
|
const autoCompactionOutputReserveTokens = 0;
|
|
@@ -12747,6 +12789,11 @@ export class AgentSession {
|
|
|
12747
12789
|
|
|
12748
12790
|
/**
|
|
12749
12791
|
* Internal: Run auto-compaction with events.
|
|
12792
|
+
*
|
|
12793
|
+
* Wraps the run so a completed compaction resets the adaptive call-rate
|
|
12794
|
+
* tracker in exactly one place. The post-compaction context size is reported
|
|
12795
|
+
* as 0 because the next recorded call carries the real provider number; the
|
|
12796
|
+
* tracker only uses this value as a fallback for callers that pass none.
|
|
12750
12797
|
*/
|
|
12751
12798
|
async #runAutoCompaction(
|
|
12752
12799
|
reason: "overflow" | "threshold" | "idle",
|
|
@@ -12760,6 +12807,24 @@ export class AgentSession {
|
|
|
12760
12807
|
beforeTerminalOverflowNoop?: () => void;
|
|
12761
12808
|
suppressNextPostTurnMaintenanceAfterHandoff?: boolean;
|
|
12762
12809
|
},
|
|
12810
|
+
): Promise<AutoCompactionTerminalStatus> {
|
|
12811
|
+
const status = await this.#runAutoCompactionInner(reason, willRetry, deferred, options);
|
|
12812
|
+
if (status.kind === "compacted") this.#adaptiveCompaction.recordCompact(0);
|
|
12813
|
+
return status;
|
|
12814
|
+
}
|
|
12815
|
+
|
|
12816
|
+
async #runAutoCompactionInner(
|
|
12817
|
+
reason: "overflow" | "threshold" | "idle",
|
|
12818
|
+
willRetry: boolean,
|
|
12819
|
+
deferred = false,
|
|
12820
|
+
options?: {
|
|
12821
|
+
continueAfterMaintenance?: boolean;
|
|
12822
|
+
deferHandoffMaintenance?: boolean;
|
|
12823
|
+
force?: boolean;
|
|
12824
|
+
signal?: AbortSignal;
|
|
12825
|
+
beforeTerminalOverflowNoop?: () => void;
|
|
12826
|
+
suppressNextPostTurnMaintenanceAfterHandoff?: boolean;
|
|
12827
|
+
},
|
|
12763
12828
|
): Promise<AutoCompactionTerminalStatus> {
|
|
12764
12829
|
const compactionSettings = this.settings.getGroup("compaction");
|
|
12765
12830
|
// `force` is the non-disableable emergency floor (F6): it bypasses the user's
|
package/src/system-prompt.ts
CHANGED
|
@@ -375,6 +375,11 @@ export interface BuildSystemPromptOptions {
|
|
|
375
375
|
alwaysApplyRules?: AlwaysApplyRule[];
|
|
376
376
|
/** Whether secret obfuscation is active. When true, explains the redaction format in the prompt. */
|
|
377
377
|
secretsEnabled?: boolean;
|
|
378
|
+
/**
|
|
379
|
+
* Reason through technical work in English while answering in the language the
|
|
380
|
+
* user used. Affects prompt guidance only. Default: "off".
|
|
381
|
+
*/
|
|
382
|
+
reasoningLanguage?: "off" | "english";
|
|
378
383
|
/** Pre-loaded workspace tree (skips discovery if provided). May be a Promise to allow early kick-off. */
|
|
379
384
|
workspaceTree?: WorkspaceTree | Promise<WorkspaceTree>;
|
|
380
385
|
/**
|
|
@@ -439,6 +444,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
439
444
|
toolDiscoveryActive = false,
|
|
440
445
|
eagerTasks = false,
|
|
441
446
|
secretsEnabled = false,
|
|
447
|
+
reasoningLanguage = "off",
|
|
442
448
|
workspaceTree: providedWorkspaceTree,
|
|
443
449
|
subagent = false,
|
|
444
450
|
} = options;
|
|
@@ -613,6 +619,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
613
619
|
toolDiscoveryActive: toolDiscoveryActive && hasHiddenToolDiscoveryTool,
|
|
614
620
|
eagerTasks,
|
|
615
621
|
secretsEnabled,
|
|
622
|
+
reasoningLanguageEnglish: reasoningLanguage === "english",
|
|
616
623
|
subagent,
|
|
617
624
|
};
|
|
618
625
|
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
package/src/tools/fetch.ts
CHANGED
|
@@ -751,7 +751,9 @@ async function renderUrl(
|
|
|
751
751
|
let convertedText: string | null = null;
|
|
752
752
|
const converted = await convertWithMarkit(binary.buffer, conversionExtension, timeout, signal);
|
|
753
753
|
if (converted.ok) {
|
|
754
|
-
|
|
754
|
+
// See the non-empty conversion note near the document path
|
|
755
|
+
// below (upstream #5433): short conversions still beat raw fallback.
|
|
756
|
+
if (converted.content.trim().length > 0) {
|
|
755
757
|
notes.push("Converted with markit");
|
|
756
758
|
convertedText = converted.content;
|
|
757
759
|
} else {
|
|
@@ -859,7 +861,10 @@ async function renderUrl(
|
|
|
859
861
|
const ext = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
|
|
860
862
|
const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
|
|
861
863
|
if (converted.ok) {
|
|
862
|
-
|
|
864
|
+
// Any non-empty markit conversion is preferable to a raw-bytes
|
|
865
|
+
// fallback (upstream #5433): short documents like the W3C dummy.pdf
|
|
866
|
+
// ("Dummy PDF file", 14 chars) are legitimately converted text.
|
|
867
|
+
if (converted.content.trim().length > 0) {
|
|
863
868
|
notes.push("Converted with markit");
|
|
864
869
|
const output = finalizeOutput(converted.content);
|
|
865
870
|
return {
|
package/src/utils/markit.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { untilAborted } from "@sayknow-cli/utils";
|
|
2
2
|
import type { Markit, StreamInfo } from "markit-ai";
|
|
3
3
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
|
+
import { ensureMupdfWasmResolution } from "./mupdf-wasm";
|
|
4
5
|
|
|
5
6
|
export interface MarkitConversionResult {
|
|
6
7
|
content: string;
|
|
@@ -18,6 +19,11 @@ let markit: () => Markit | Promise<Markit> = async () => {
|
|
|
18
19
|
return promise;
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
// Upstream #5433: mupdf's wasm asset is not reachable at the Emscripten
|
|
23
|
+
// loader's default bunfs path in compiled binaries. Seed the embedded-asset
|
|
24
|
+
// hook before any markit conversion (and therefore before any mupdf import).
|
|
25
|
+
ensureMupdfWasmResolution();
|
|
26
|
+
|
|
21
27
|
function normalizeExtension(extension: string): string {
|
|
22
28
|
const trimmed = extension.trim().toLowerCase();
|
|
23
29
|
if (!trimmed) return ".bin";
|
|
@@ -31,6 +37,22 @@ function normalizeError(error: unknown): string {
|
|
|
31
37
|
return "Conversion failed";
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* markit-ai's PDF converter swallows the real mupdf import failure behind a
|
|
42
|
+
* generic "Install it" message (upstream #5433). Re-run the import here so
|
|
43
|
+
* release diagnostics carry the actual module/asset/initialization error.
|
|
44
|
+
*/
|
|
45
|
+
async function describeConversionError(error: unknown): Promise<string> {
|
|
46
|
+
const base = normalizeError(error);
|
|
47
|
+
if (!base.includes("PDF support requires 'mupdf'")) return base;
|
|
48
|
+
try {
|
|
49
|
+
await import("mupdf");
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
return `${base} (mupdf import failed: ${normalizeError(cause)})`;
|
|
52
|
+
}
|
|
53
|
+
return base;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
async function runMarkitConversion<T>(task: (markit: Markit) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
35
57
|
try {
|
|
36
58
|
const instance = await markit();
|
|
@@ -62,7 +84,7 @@ export async function convertFileWithMarkit(filePath: string, signal?: AbortSign
|
|
|
62
84
|
if (error instanceof ToolAbortError) {
|
|
63
85
|
throw error;
|
|
64
86
|
}
|
|
65
|
-
return { content: "", ok: false, error:
|
|
87
|
+
return { content: "", ok: false, error: await describeConversionError(error) };
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
|
|
@@ -84,6 +106,6 @@ export async function convertBufferWithMarkit(
|
|
|
84
106
|
if (error instanceof ToolAbortError) {
|
|
85
107
|
throw error;
|
|
86
108
|
}
|
|
87
|
-
return { content: "", ok: false, error:
|
|
109
|
+
return { content: "", ok: false, error: await describeConversionError(error) };
|
|
88
110
|
}
|
|
89
111
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MuPDF wasm asset resolution inside compiled binaries (upstream #5433).
|
|
3
|
+
*
|
|
4
|
+
* `mupdf`'s Emscripten loader resolves `mupdf-wasm.wasm` relative to
|
|
5
|
+
* `import.meta.url` (or `scriptDirectory`). Inside a `bun build --compile`
|
|
6
|
+
* bunfs that path points at the bunfs root where the asset does not exist,
|
|
7
|
+
* so every mupdf import aborted with
|
|
8
|
+
* `ENOENT: ... /$bunfs/root/mupdf-wasm.wasm`.
|
|
9
|
+
*
|
|
10
|
+
* The wasm is embedded via `with { type: "file" }`, which lands it at a
|
|
11
|
+
* hashed bunfs path. MuPDF's top-level factory call reads
|
|
12
|
+
* `globalThis["$libmupdf_wasm_Module"]`, so seeding that config with a
|
|
13
|
+
* `locateFile` hook that returns the embedded asset path makes the loader
|
|
14
|
+
* read the wasm from the bunfs directly — no disk sidecar needed.
|
|
15
|
+
*
|
|
16
|
+
* The asset MUST live under this package (`vendor/mupdf/…`), not under the
|
|
17
|
+
* monorepo `node_modules/` tree. A `../../../../node_modules/mupdf/…` import
|
|
18
|
+
* resolves in a workspace checkout but breaks every published install
|
|
19
|
+
* (`bun install -g` / npm) where `@sayknow-cli/coding-agent` sits several
|
|
20
|
+
* directories deeper and mupdf is hoisted elsewhere — that was the 0.5.8
|
|
21
|
+
* startup crash. Refresh the vendored bytes from
|
|
22
|
+
* `node_modules/mupdf/dist/mupdf-wasm.wasm` whenever markit-ai's mupdf
|
|
23
|
+
* dependency moves.
|
|
24
|
+
*
|
|
25
|
+
* This must run before the first `import("mupdf")` anywhere in the process.
|
|
26
|
+
*/
|
|
27
|
+
import mupdfWasmPath from "../../vendor/mupdf/mupdf-wasm.wasm" with { type: "file" };
|
|
28
|
+
|
|
29
|
+
const MODULE_CONFIG_KEY = "$libmupdf_wasm_Module";
|
|
30
|
+
|
|
31
|
+
export function ensureMupdfWasmResolution(): void {
|
|
32
|
+
const globalScope = globalThis as typeof globalThis & Record<string, unknown>;
|
|
33
|
+
if (globalScope[MODULE_CONFIG_KEY] !== undefined) return;
|
|
34
|
+
globalScope[MODULE_CONFIG_KEY] = { locateFile: () => String(mupdfWasmPath) };
|
|
35
|
+
}
|
|
Binary file
|