kritya 0.8.52-beta → 0.8.54-beta
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/dist/config/config.js +47 -1
- package/dist/tools/grep.js +10 -0
- package/dist/tools/read.js +10 -2
- package/dist/ui/App.js +7 -1
- package/package.json +1 -1
package/dist/config/config.js
CHANGED
|
@@ -101,6 +101,44 @@ export function sanitizeMcpServersRecord(servers, label = "mcpServers") {
|
|
|
101
101
|
}
|
|
102
102
|
return out;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Top-level keys `~/.kritya/config.json` understands — keep in sync with
|
|
106
|
+
* CliConfig. Anything else in the file is silently ignored (the parsed
|
|
107
|
+
* object is only ever consumed through the typed interface), so a typo like
|
|
108
|
+
* `maxStep` or `contextwindow` looks like "my config isn't taking effect"
|
|
109
|
+
* with no clue why. loadConfig warns about those instead.
|
|
110
|
+
*/
|
|
111
|
+
const KNOWN_CONFIG_KEYS = new Set([
|
|
112
|
+
"apiKey",
|
|
113
|
+
"model",
|
|
114
|
+
"baseUrl",
|
|
115
|
+
"provider",
|
|
116
|
+
"providers",
|
|
117
|
+
"customModels",
|
|
118
|
+
"pricing",
|
|
119
|
+
"contextWindow",
|
|
120
|
+
"maxSteps",
|
|
121
|
+
"tokenBudget",
|
|
122
|
+
"toolTimeoutSeconds",
|
|
123
|
+
"subagentConcurrency",
|
|
124
|
+
"subagentTimeoutSeconds",
|
|
125
|
+
"subagentMaxSteps",
|
|
126
|
+
"subagentWriteMaxSteps",
|
|
127
|
+
"mcpServers",
|
|
128
|
+
"sandboxExec",
|
|
129
|
+
"retentionDays",
|
|
130
|
+
"audit",
|
|
131
|
+
"otel",
|
|
132
|
+
"commitAttribution",
|
|
133
|
+
"privacyMode",
|
|
134
|
+
]);
|
|
135
|
+
/**
|
|
136
|
+
* Keys in a parsed config object that CliConfig doesn't know — all ignored
|
|
137
|
+
* by the typed code that consumes it. Exported for tests.
|
|
138
|
+
*/
|
|
139
|
+
export function unknownConfigKeys(parsed) {
|
|
140
|
+
return Object.keys(parsed).filter((key) => !KNOWN_CONFIG_KEYS.has(key));
|
|
141
|
+
}
|
|
104
142
|
function sanitizeCliConfig(config) {
|
|
105
143
|
if (!config.mcpServers)
|
|
106
144
|
return config;
|
|
@@ -218,7 +256,15 @@ export function loadConfig() {
|
|
|
218
256
|
}
|
|
219
257
|
try {
|
|
220
258
|
assertJsonWithinLimits(raw, "config.json");
|
|
221
|
-
|
|
259
|
+
const parsed = JSON.parse(raw);
|
|
260
|
+
const unknown = unknownConfigKeys(parsed);
|
|
261
|
+
if (unknown.length > 0) {
|
|
262
|
+
// warnUser dedupes per context, so this prints once per process even
|
|
263
|
+
// though loadConfig runs often.
|
|
264
|
+
warnUser(`loadConfig(${CONFIG_FILE})`, new Error(`unknown key(s) ${unknown.map((key) => `"${key}"`).join(", ")} ignored — ` +
|
|
265
|
+
`check the spelling against the documented config keys`));
|
|
266
|
+
}
|
|
267
|
+
return sanitizeCliConfig(parsed);
|
|
222
268
|
}
|
|
223
269
|
catch (err) {
|
|
224
270
|
// Malformed or oversized — worth being able to see when someone reports
|
package/dist/tools/grep.js
CHANGED
|
@@ -5,6 +5,14 @@ import { isPathSafe, resolveSafe, safeCompileRegex, truncateResult, meaningfulLi
|
|
|
5
5
|
import { loadIgnorePatterns } from "./ignore.js";
|
|
6
6
|
const MAX_MATCHES = 200;
|
|
7
7
|
const MAX_FILE_BYTES = 1024 * 1024;
|
|
8
|
+
/**
|
|
9
|
+
* Longest line regex-tested. A minified file is a single ~1MB line, and
|
|
10
|
+
* safeCompileRegex only blocks nested-quantifier shapes — any other
|
|
11
|
+
* ReDoS-shaped pattern tested against that one line spins the event loop,
|
|
12
|
+
* where the tool timeout can't fire. Skipping the line closes it; a real
|
|
13
|
+
* match on a line this long is unreadable in the result anyway.
|
|
14
|
+
*/
|
|
15
|
+
const MAX_LINE_CHARS = 10_000;
|
|
8
16
|
export const grepTool = {
|
|
9
17
|
name: "grep",
|
|
10
18
|
description: "Search file contents with a regular expression. Returns 'file:line: text' matches. " +
|
|
@@ -74,6 +82,8 @@ export const grepTool = {
|
|
|
74
82
|
const rel = path.relative(ctx.workspace, abs);
|
|
75
83
|
const lines = content.split("\n");
|
|
76
84
|
for (let i = 0; i < lines.length && matches.length < MAX_MATCHES; i++) {
|
|
85
|
+
if (lines[i].length > MAX_LINE_CHARS)
|
|
86
|
+
continue;
|
|
77
87
|
if (regex.test(lines[i])) {
|
|
78
88
|
matches.push(`${rel}:${i + 1}: ${lines[i].slice(0, 250)}`);
|
|
79
89
|
}
|
package/dist/tools/read.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveSafe, truncateResult, countLines } from "./common.js";
|
|
|
3
3
|
export const readFileTool = {
|
|
4
4
|
name: "read_file",
|
|
5
5
|
description: "Read a text file from the workspace. Returns the content with line numbers. " +
|
|
6
|
-
"Use offset/limit for large files.",
|
|
6
|
+
"Use offset/limit for large files. Refuses binary files — use read_document for .pdf/.docx/.xlsx/.pptx.",
|
|
7
7
|
parameters: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -18,7 +18,15 @@ export const readFileTool = {
|
|
|
18
18
|
resultSummary: (output) => countLines(output, "line", "lines"),
|
|
19
19
|
async execute(args, ctx) {
|
|
20
20
|
const abs = resolveSafe(ctx.workspace, String(args.path));
|
|
21
|
-
const
|
|
21
|
+
const buffer = await fs.readFile(abs);
|
|
22
|
+
// Binary sniff, same as grep: a NUL byte in the first 8KB means this is
|
|
23
|
+
// not text. Decoding it as UTF-8 anyway would dump garbage into the
|
|
24
|
+
// context and burn tokens on bytes no one can read.
|
|
25
|
+
if (buffer.subarray(0, 8000).includes(0)) {
|
|
26
|
+
throw new Error(`"${args.path}" looks like a binary file — read_file only reads text. ` +
|
|
27
|
+
`For .pdf, .docx, .xlsx, or .pptx use read_document instead.`);
|
|
28
|
+
}
|
|
29
|
+
const content = buffer.toString("utf8");
|
|
22
30
|
const lines = content.split("\n");
|
|
23
31
|
const offset = Math.max(1, Number(args.offset) || 1);
|
|
24
32
|
const limit = Number(args.limit) || 2000;
|
package/dist/ui/App.js
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import { glob } from "tinyglobby";
|
|
6
6
|
import stringWidth from "string-width";
|
|
7
7
|
import { gitDiffStat } from "../git/git.js";
|
|
8
|
+
import { debugLog } from "../config/debug.js";
|
|
8
9
|
import { modelDisplaySlug } from "../config/models.js";
|
|
9
10
|
import { setStderrSink } from "../stderr.js";
|
|
10
11
|
import { openInEditor } from "./openInEditor.js";
|
|
@@ -145,7 +146,12 @@ export function App({ agent, workspace, modelRef, providerRef, config, resumedCo
|
|
|
145
146
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**", ...loadIgnorePatterns(workspace)],
|
|
146
147
|
})
|
|
147
148
|
.then((files) => setFileList(files.sort().slice(0, 2000)))
|
|
148
|
-
.catch(() => {
|
|
149
|
+
.catch((err) => {
|
|
150
|
+
// A failed refresh must not leave the previous workspace's files
|
|
151
|
+
// behind for @-mentions: clear, and leave a trail in the debug log.
|
|
152
|
+
debugLog("refreshFileList", err);
|
|
153
|
+
setFileList([]);
|
|
154
|
+
});
|
|
149
155
|
}, [workspace]);
|
|
150
156
|
useEffect(refreshFileList, [refreshFileList]);
|
|
151
157
|
const { items, addItem, clearItems, phase, setPhase, stream, thinking, activity, setActivity, runningPhase, setRunningPhase, setChained, workflow, refreshWorkflow, prefill, setPrefill, permission, elicitation, inFlight, model, provider, servedModel, totalUsage, totalCost, tasks, setTasks, ctxPct, setCtxPct, tokenBudget, budgetPct, budgetUsed, budgetStopped, resetBudget, setBudgetLimit, branch, planMode, setPlanMode, dryRunMode, killed, killReason, engageKill, releaseKill, acceptEdits, setAcceptEdits, bypassMode, setBypassMode, autoApprovedCount, cycleMode, onAcceptEditsConfirm, onBypassModeConfirm, abortRef, setModelEverywhere, setProviderEverywhere, costReport, runAgent, runWebSearch, onPermissionDecision, onElicitationDecision, onResumeSelect, requestPermission, requestElicitation, persistenceWarningCount, } = useAgent({
|