context-mode 1.0.52 → 1.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.openclaw-plugin/openclaw.plugin.json +1 -1
- package/.openclaw-plugin/package.json +1 -1
- package/README.md +87 -29
- package/build/adapters/claude-code/hooks.d.ts +18 -0
- package/build/adapters/claude-code/hooks.js +23 -0
- package/build/adapters/claude-code/index.js +34 -2
- package/build/adapters/client-map.js +1 -0
- package/build/adapters/detect.d.ts +1 -0
- package/build/adapters/detect.js +18 -2
- package/build/adapters/opencode/index.d.ts +7 -2
- package/build/adapters/opencode/index.js +37 -24
- package/build/adapters/types.d.ts +1 -1
- package/build/cli.js +12 -28
- package/build/executor.js +3 -3
- package/build/openclaw-plugin.js +41 -33
- package/build/opencode-plugin.js +5 -2
- package/build/runtime.js +29 -11
- package/build/server.d.ts +2 -0
- package/build/server.js +35 -44
- package/build/store.d.ts +4 -3
- package/build/store.js +101 -34
- package/cli.bundle.mjs +185 -131
- package/configs/codex/AGENTS.md +19 -0
- package/configs/kilo/AGENTS.md +58 -0
- package/configs/kilo/kilo.json +10 -0
- package/hooks/core/tool-naming.mjs +1 -0
- package/hooks/pretooluse.mjs +25 -20
- package/hooks/sessionstart.mjs +25 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +156 -102
package/build/cli.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
15
15
|
import color from "picocolors";
|
|
16
|
-
import {
|
|
17
|
-
import { readFileSync, writeFileSync, cpSync, accessSync, existsSync,
|
|
16
|
+
import { execFileSync } from "node:child_process";
|
|
17
|
+
import { readFileSync, writeFileSync, cpSync, accessSync, existsSync, rmSync, closeSync, openSync, chmodSync, constants } from "node:fs";
|
|
18
18
|
import { request as httpsRequest } from "node:https";
|
|
19
19
|
import { resolve, dirname, join } from "node:path";
|
|
20
20
|
import { tmpdir, devNull } from "node:os";
|
|
@@ -351,7 +351,7 @@ async function upgrade() {
|
|
|
351
351
|
const tmpDir = join(tmpdir(), `context-mode-upgrade-${Date.now()}`);
|
|
352
352
|
s.start("Cloning mksglu/context-mode");
|
|
353
353
|
try {
|
|
354
|
-
|
|
354
|
+
execFileSync("git", ["clone", "--depth", "1", "https://github.com/mksglu/context-mode.git", tmpDir], { stdio: "pipe", timeout: 30000 });
|
|
355
355
|
s.stop("Downloaded");
|
|
356
356
|
const srcDir = tmpDir;
|
|
357
357
|
const newPkg = JSON.parse(readFileSync(resolve(srcDir, "package.json"), "utf-8"));
|
|
@@ -364,12 +364,12 @@ async function upgrade() {
|
|
|
364
364
|
}
|
|
365
365
|
// Step 2: Install dependencies + build
|
|
366
366
|
s.start("Installing dependencies & building");
|
|
367
|
-
|
|
367
|
+
execFileSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
368
368
|
cwd: srcDir,
|
|
369
369
|
stdio: "pipe",
|
|
370
370
|
timeout: 120000,
|
|
371
371
|
});
|
|
372
|
-
|
|
372
|
+
execFileSync("npm", ["run", "build"], {
|
|
373
373
|
cwd: srcDir,
|
|
374
374
|
stdio: "pipe",
|
|
375
375
|
timeout: 60000,
|
|
@@ -377,24 +377,8 @@ async function upgrade() {
|
|
|
377
377
|
s.stop("Built successfully");
|
|
378
378
|
// Step 3: Update in-place
|
|
379
379
|
s.start("Updating files in-place");
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
const cacheParent = cacheParentMatch[1];
|
|
383
|
-
const myDir = pluginRoot.replace(cacheParent, "").replace(/[\\/]/g, "");
|
|
384
|
-
try {
|
|
385
|
-
const oldDirs = readdirSync(cacheParent).filter(d => d !== myDir);
|
|
386
|
-
for (const d of oldDirs) {
|
|
387
|
-
try {
|
|
388
|
-
rmSync(resolve(cacheParent, d), { recursive: true, force: true });
|
|
389
|
-
}
|
|
390
|
-
catch { /* skip */ }
|
|
391
|
-
}
|
|
392
|
-
if (oldDirs.length > 0) {
|
|
393
|
-
p.log.info(color.dim(` Cleaned ${oldDirs.length} stale cache dir(s)`));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
catch { /* parent may not exist */ }
|
|
397
|
-
}
|
|
380
|
+
// Old version dirs are cleaned lazily by sessionstart.mjs (age-gated >1h)
|
|
381
|
+
// to avoid breaking active sessions that still reference them (#181).
|
|
398
382
|
const items = [
|
|
399
383
|
"build", "src", "hooks", "skills", "scripts", ".claude-plugin",
|
|
400
384
|
"start.mjs", "server.bundle.mjs", "cli.bundle.mjs", "package.json",
|
|
@@ -422,7 +406,7 @@ async function upgrade() {
|
|
|
422
406
|
p.log.info(color.dim(" Registry synced to " + pluginRoot));
|
|
423
407
|
// Install production deps
|
|
424
408
|
s.start("Installing production dependencies");
|
|
425
|
-
|
|
409
|
+
execFileSync("npm", ["install", "--production", "--no-audit", "--no-fund"], {
|
|
426
410
|
cwd: pluginRoot,
|
|
427
411
|
stdio: "pipe",
|
|
428
412
|
timeout: 60000,
|
|
@@ -431,7 +415,7 @@ async function upgrade() {
|
|
|
431
415
|
// Rebuild native addons for current Node.js ABI (fixes #131)
|
|
432
416
|
s.start("Rebuilding native addons");
|
|
433
417
|
try {
|
|
434
|
-
|
|
418
|
+
execFileSync("npm", ["rebuild", "better-sqlite3"], {
|
|
435
419
|
cwd: pluginRoot,
|
|
436
420
|
stdio: "pipe",
|
|
437
421
|
timeout: 60000,
|
|
@@ -449,7 +433,7 @@ async function upgrade() {
|
|
|
449
433
|
// Update global npm
|
|
450
434
|
s.start("Updating npm global package");
|
|
451
435
|
try {
|
|
452
|
-
|
|
436
|
+
execFileSync("npm", ["install", "-g", pluginRoot, "--no-audit", "--no-fund"], {
|
|
453
437
|
stdio: "pipe",
|
|
454
438
|
timeout: 30000,
|
|
455
439
|
});
|
|
@@ -507,7 +491,7 @@ async function upgrade() {
|
|
|
507
491
|
const binPath = resolve(pluginRoot, bin);
|
|
508
492
|
try {
|
|
509
493
|
accessSync(binPath, constants.F_OK);
|
|
510
|
-
|
|
494
|
+
chmodSync(binPath, 0o755);
|
|
511
495
|
permSet.push(binPath);
|
|
512
496
|
}
|
|
513
497
|
catch { /* not found — skip */ }
|
|
@@ -535,7 +519,7 @@ async function upgrade() {
|
|
|
535
519
|
const cliBundlePath = resolve(pluginRoot, "cli.bundle.mjs");
|
|
536
520
|
const cliBuildPath = resolve(pluginRoot, "build", "cli.js");
|
|
537
521
|
const cliPath = existsSync(cliBundlePath) ? cliBundlePath : cliBuildPath;
|
|
538
|
-
|
|
522
|
+
execFileSync("node", [cliPath, "doctor"], {
|
|
539
523
|
stdio: "inherit",
|
|
540
524
|
timeout: 30000,
|
|
541
525
|
cwd: pluginRoot,
|
package/build/executor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawn, execSync } from "node:child_process";
|
|
1
|
+
import { spawn, execSync, execFileSync } from "node:child_process";
|
|
2
2
|
import { mkdtempSync, writeFileSync, rmSync, existsSync } from "node:fs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
@@ -50,7 +50,7 @@ export class PolyglotExecutor {
|
|
|
50
50
|
}
|
|
51
51
|
async execute(opts) {
|
|
52
52
|
const { language, code, timeout = 30_000, background = false } = opts;
|
|
53
|
-
const tmpDir = mkdtempSync(join(tmpdir(), "ctx-mode-"));
|
|
53
|
+
const tmpDir = mkdtempSync(join(tmpdir(), ".ctx-mode-"));
|
|
54
54
|
try {
|
|
55
55
|
const filePath = this.#writeScript(tmpDir, code, language);
|
|
56
56
|
const cmd = buildCommand(this.#runtimes, language, filePath);
|
|
@@ -127,7 +127,7 @@ export class PolyglotExecutor {
|
|
|
127
127
|
const binPath = srcPath.replace(/\.rs$/, "") + binSuffix;
|
|
128
128
|
// Compile
|
|
129
129
|
try {
|
|
130
|
-
|
|
130
|
+
execFileSync("rustc", [srcPath, "-o", binPath], {
|
|
131
131
|
cwd,
|
|
132
132
|
timeout: Math.min(timeout, 60_000),
|
|
133
133
|
encoding: "utf-8",
|
package/build/openclaw-plugin.js
CHANGED
|
@@ -130,17 +130,17 @@ export default {
|
|
|
130
130
|
catch {
|
|
131
131
|
// best effort
|
|
132
132
|
}
|
|
133
|
-
// Async init: load routing module
|
|
133
|
+
// Async init: load routing module. Hooks await this.
|
|
134
|
+
// NOTE: writeRoutingInstructions is intentionally NOT called here.
|
|
135
|
+
// process.cwd() at plugin load time is the gateway's working directory, not
|
|
136
|
+
// the agent's workspace. Writing AGENTS.md to cwd() caused the file to be
|
|
137
|
+
// created in arbitrary directories (repo roots, config dirs, $HOME, etc.).
|
|
138
|
+
// The write is now deferred to session_start where the real workspace path
|
|
139
|
+
// is known via the sessionKey → workspace mapping.
|
|
134
140
|
const initPromise = (async () => {
|
|
135
141
|
const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
|
|
136
142
|
const routing = await import(pathToFileURL(routingPath).href);
|
|
137
143
|
await routing.initSecurity(buildDir);
|
|
138
|
-
try {
|
|
139
|
-
new OpenClawAdapter().writeRoutingInstructions(projectDir, pluginRoot);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// best effort — never break plugin init
|
|
143
|
-
}
|
|
144
144
|
return { routing };
|
|
145
145
|
})();
|
|
146
146
|
// ── 1. tool_call:before — Routing enforcement ──────────
|
|
@@ -315,6 +315,33 @@ export default {
|
|
|
315
315
|
workspaceRouter.registerSession(key, sessionId);
|
|
316
316
|
}
|
|
317
317
|
resumeInjected = false;
|
|
318
|
+
// Write routing instructions (AGENTS.md) now that we know the real
|
|
319
|
+
// workspace. Derive the workspace directory from the sessionKey so we
|
|
320
|
+
// only write into recognised /.openclaw/workspace* paths, never into
|
|
321
|
+
// the gateway's cwd or any other arbitrary directory.
|
|
322
|
+
if (key) {
|
|
323
|
+
try {
|
|
324
|
+
const adapter = new OpenClawAdapter();
|
|
325
|
+
const openclawBase = resolve(homedir(), ".openclaw");
|
|
326
|
+
// Resolve workspace dir from sessionKey (pattern: agent:<name>:*)
|
|
327
|
+
// Restrict agent name to safe characters to prevent path traversal (#183)
|
|
328
|
+
const wsMatch = key.match(/^agent:([a-zA-Z0-9_-]+):/);
|
|
329
|
+
let wsDir;
|
|
330
|
+
if (wsMatch) {
|
|
331
|
+
wsDir = resolve(openclawBase, `workspace-${wsMatch[1]}`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
wsDir = resolve(openclawBase, "workspace");
|
|
335
|
+
}
|
|
336
|
+
// Containment check: never write outside ~/.openclaw/
|
|
337
|
+
if (wsDir.startsWith(openclawBase)) {
|
|
338
|
+
adapter.writeRoutingInstructions(wsDir, pluginRoot);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
// best effort — never break session start
|
|
343
|
+
}
|
|
344
|
+
}
|
|
318
345
|
}
|
|
319
346
|
catch {
|
|
320
347
|
// best effort — never break session start
|
|
@@ -402,7 +429,7 @@ export default {
|
|
|
402
429
|
info: {
|
|
403
430
|
id: "context-mode",
|
|
404
431
|
name: "Context Mode",
|
|
405
|
-
ownsCompaction:
|
|
432
|
+
ownsCompaction: false,
|
|
406
433
|
},
|
|
407
434
|
async ingest() {
|
|
408
435
|
return { ingested: true };
|
|
@@ -410,31 +437,12 @@ export default {
|
|
|
410
437
|
async assemble({ messages }) {
|
|
411
438
|
return { messages, estimatedTokens: 0 };
|
|
412
439
|
},
|
|
413
|
-
async compact(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const stats = db.getSessionStats(sid);
|
|
420
|
-
const compactCount = (stats?.compact_count ?? 0) + 1;
|
|
421
|
-
const snapshot = buildResumeSnapshot(events, { compactCount });
|
|
422
|
-
db.upsertResume(sid, snapshot, events.length);
|
|
423
|
-
db.incrementCompactCount(sid);
|
|
424
|
-
return {
|
|
425
|
-
ok: true,
|
|
426
|
-
compacted: true,
|
|
427
|
-
result: {
|
|
428
|
-
summary: snapshot,
|
|
429
|
-
firstKeptEntryId: "", // clear all history before this compaction
|
|
430
|
-
tokensBefore: currentTokenCount ?? 0,
|
|
431
|
-
tokensAfter: 0,
|
|
432
|
-
},
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
catch {
|
|
436
|
-
return { ok: false, compacted: false };
|
|
437
|
-
}
|
|
440
|
+
async compact() {
|
|
441
|
+
// No-op: session continuity is handled by before_compaction / after_compaction hooks.
|
|
442
|
+
// Returning ownsCompaction: false + compacted: false lets the host platform (OpenClaw)
|
|
443
|
+
// manage conversation truncation, preserving Anthropic thinking/redacted_thinking blocks.
|
|
444
|
+
// See: https://github.com/mksglu/context-mode/issues/191
|
|
445
|
+
return { ok: true, compacted: false };
|
|
438
446
|
},
|
|
439
447
|
}));
|
|
440
448
|
// ── 10. Auto-reply commands — ctx slash commands ──────
|
package/build/opencode-plugin.js
CHANGED
|
@@ -23,8 +23,11 @@ import { extractEvents } from "./session/extract.js";
|
|
|
23
23
|
import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
24
24
|
import { OpenCodeAdapter } from "./adapters/opencode/index.js";
|
|
25
25
|
// ── Helpers ───────────────────────────────────────────────
|
|
26
|
+
function getPlatform() {
|
|
27
|
+
return process.env.KILO ? "kilo" : "opencode";
|
|
28
|
+
}
|
|
26
29
|
function getSessionDir() {
|
|
27
|
-
const dir = join(homedir(), ".config",
|
|
30
|
+
const dir = join(homedir(), ".config", getPlatform(), "context-mode", "sessions");
|
|
28
31
|
mkdirSync(dir, { recursive: true });
|
|
29
32
|
return dir;
|
|
30
33
|
}
|
|
@@ -54,7 +57,7 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
54
57
|
db.ensureSession(sessionId, projectDir);
|
|
55
58
|
// Auto-write AGENTS.md on startup for OpenCode projects
|
|
56
59
|
try {
|
|
57
|
-
new OpenCodeAdapter().writeRoutingInstructions(projectDir, resolve(buildDir, ".."));
|
|
60
|
+
new OpenCodeAdapter(getPlatform()).writeRoutingInstructions(projectDir, resolve(buildDir, ".."));
|
|
58
61
|
}
|
|
59
62
|
catch {
|
|
60
63
|
// best effort — never break plugin init
|
package/build/runtime.js
CHANGED
|
@@ -11,6 +11,23 @@ function commandExists(cmd) {
|
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
function bunExists() {
|
|
15
|
+
if (commandExists("bun"))
|
|
16
|
+
return true;
|
|
17
|
+
// Bun installs to ~/.bun/bin which may not be in PATH in MCP server environments
|
|
18
|
+
if (!isWindows) {
|
|
19
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
20
|
+
if (home && existsSync(`${home}/.bun/bin/bun`))
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function bunCommand() {
|
|
26
|
+
if (commandExists("bun"))
|
|
27
|
+
return "bun";
|
|
28
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
29
|
+
return `${home}/.bun/bin/bun`;
|
|
30
|
+
}
|
|
14
31
|
/**
|
|
15
32
|
* On Windows, resolve the first non-WSL bash in PATH.
|
|
16
33
|
* WSL bash (C:\Windows\System32\bash.exe) cannot handle Windows paths,
|
|
@@ -59,11 +76,12 @@ function getVersion(cmd) {
|
|
|
59
76
|
}
|
|
60
77
|
}
|
|
61
78
|
export function detectRuntimes() {
|
|
62
|
-
const hasBun =
|
|
79
|
+
const hasBun = bunExists();
|
|
80
|
+
const bun = hasBun ? bunCommand() : null;
|
|
63
81
|
return {
|
|
64
|
-
javascript:
|
|
65
|
-
typescript:
|
|
66
|
-
?
|
|
82
|
+
javascript: bun ?? process.execPath,
|
|
83
|
+
typescript: bun
|
|
84
|
+
? bun
|
|
67
85
|
: commandExists("tsx")
|
|
68
86
|
? "tsx"
|
|
69
87
|
: commandExists("ts-node")
|
|
@@ -91,11 +109,11 @@ export function detectRuntimes() {
|
|
|
91
109
|
};
|
|
92
110
|
}
|
|
93
111
|
export function hasBunRuntime() {
|
|
94
|
-
return
|
|
112
|
+
return bunExists();
|
|
95
113
|
}
|
|
96
114
|
export function getRuntimeSummary(runtimes) {
|
|
97
115
|
const lines = [];
|
|
98
|
-
const bunPreferred = runtimes.javascript
|
|
116
|
+
const bunPreferred = runtimes.javascript?.endsWith("bun") ?? false;
|
|
99
117
|
lines.push(` JavaScript: ${runtimes.javascript} (${getVersion(runtimes.javascript)})${bunPreferred ? " ⚡" : ""}`);
|
|
100
118
|
if (runtimes.typescript) {
|
|
101
119
|
lines.push(` TypeScript: ${runtimes.typescript} (${getVersion(runtimes.typescript)})`);
|
|
@@ -156,15 +174,15 @@ export function getAvailableLanguages(runtimes) {
|
|
|
156
174
|
export function buildCommand(runtimes, language, filePath) {
|
|
157
175
|
switch (language) {
|
|
158
176
|
case "javascript":
|
|
159
|
-
return runtimes.javascript
|
|
160
|
-
? [
|
|
161
|
-
: [
|
|
177
|
+
return runtimes.javascript.endsWith("bun")
|
|
178
|
+
? [runtimes.javascript, "run", filePath]
|
|
179
|
+
: [runtimes.javascript, filePath];
|
|
162
180
|
case "typescript":
|
|
163
181
|
if (!runtimes.typescript) {
|
|
164
182
|
throw new Error("No TypeScript runtime available. Install one of: bun (recommended), tsx (npm i -g tsx), or ts-node.");
|
|
165
183
|
}
|
|
166
|
-
if (runtimes.typescript
|
|
167
|
-
return [
|
|
184
|
+
if (runtimes.typescript?.endsWith("bun"))
|
|
185
|
+
return [runtimes.typescript, "run", filePath];
|
|
168
186
|
if (runtimes.typescript === "tsx")
|
|
169
187
|
return ["tsx", filePath];
|
|
170
188
|
return ["ts-node", filePath];
|
package/build/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { ContentStore } from "./store.js";
|
|
2
3
|
/**
|
|
3
4
|
* Parse FTS5 highlight markers to find match positions in the
|
|
4
5
|
* original (marker-free) text. Returns character offsets into the
|
|
@@ -6,3 +7,4 @@
|
|
|
6
7
|
*/
|
|
7
8
|
export declare function positionsFromHighlight(highlighted: string): number[];
|
|
8
9
|
export declare function extractSnippet(content: string, query: string, maxLen?: number, highlighted?: string): string;
|
|
10
|
+
export declare function formatBatchQueryResults(store: ContentStore, queries: string[], source: string, maxOutput?: number): string[];
|
package/build/server.js
CHANGED
|
@@ -321,6 +321,33 @@ export function extractSnippet(content, query, maxLen = 1500, highlighted) {
|
|
|
321
321
|
}
|
|
322
322
|
return parts.join("\n\n");
|
|
323
323
|
}
|
|
324
|
+
export function formatBatchQueryResults(store, queries, source, maxOutput = 80 * 1024) {
|
|
325
|
+
const sections = [];
|
|
326
|
+
let outputSize = 0;
|
|
327
|
+
for (const query of queries) {
|
|
328
|
+
if (outputSize > maxOutput) {
|
|
329
|
+
sections.push(`## ${query}\n(output cap reached — use search(queries: ["${query}"]) for details)\n`);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const results = store.searchWithFallback(query, 3, source, undefined, "exact");
|
|
333
|
+
sections.push(`## ${query}`);
|
|
334
|
+
sections.push("");
|
|
335
|
+
if (results.length > 0) {
|
|
336
|
+
for (const result of results) {
|
|
337
|
+
const snippet = extractSnippet(result.content, query, 3000, result.highlighted);
|
|
338
|
+
sections.push(`### ${result.title}`);
|
|
339
|
+
sections.push(snippet);
|
|
340
|
+
sections.push("");
|
|
341
|
+
outputSize += snippet.length + result.title.length;
|
|
342
|
+
}
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
sections.push("No matching sections found.");
|
|
346
|
+
sections.push("");
|
|
347
|
+
}
|
|
348
|
+
sections.push(`\n> **Tip:** Results are scoped to this batch only. To search across all indexed sources, use \`ctx_search(queries: [...])\`.`);
|
|
349
|
+
return sections;
|
|
350
|
+
}
|
|
324
351
|
// ─────────────────────────────────────────────────────────
|
|
325
352
|
// Tool: execute
|
|
326
353
|
// ─────────────────────────────────────────────────────────
|
|
@@ -1272,45 +1299,9 @@ server.registerTool("ctx_batch_execute", {
|
|
|
1272
1299
|
inventory.push(`- ${s.title} (${(bytes / 1024).toFixed(1)}KB)`);
|
|
1273
1300
|
sectionTitles.push(s.title);
|
|
1274
1301
|
}
|
|
1275
|
-
// Run all search queries —
|
|
1276
|
-
//
|
|
1277
|
-
const
|
|
1278
|
-
const queryResults = [];
|
|
1279
|
-
let outputSize = 0;
|
|
1280
|
-
for (const query of queries) {
|
|
1281
|
-
if (outputSize > MAX_OUTPUT) {
|
|
1282
|
-
queryResults.push(`## ${query}\n(output cap reached — use search(queries: ["${query}"]) for details)\n`);
|
|
1283
|
-
continue;
|
|
1284
|
-
}
|
|
1285
|
-
// Tier 1: scoped search with fallback (porter → trigram → fuzzy)
|
|
1286
|
-
let results = store.searchWithFallback(query, 3, source);
|
|
1287
|
-
let crossSource = false;
|
|
1288
|
-
// Tier 2: global fallback (no source filter) — warn about cross-source (Issue #61)
|
|
1289
|
-
if (results.length === 0) {
|
|
1290
|
-
results = store.searchWithFallback(query, 3);
|
|
1291
|
-
crossSource = results.length > 0;
|
|
1292
|
-
}
|
|
1293
|
-
queryResults.push(`## ${query}`);
|
|
1294
|
-
if (crossSource) {
|
|
1295
|
-
queryResults.push(`> **Note:** No results in current batch output. Showing results from previously indexed content.`);
|
|
1296
|
-
}
|
|
1297
|
-
queryResults.push("");
|
|
1298
|
-
if (results.length > 0) {
|
|
1299
|
-
for (const r of results) {
|
|
1300
|
-
// Use larger snippet (3KB) for batch_execute to reduce tiny-fragment issue (Issue #61)
|
|
1301
|
-
const snippet = extractSnippet(r.content, query, 3000, r.highlighted);
|
|
1302
|
-
const sourceTag = crossSource ? ` _(source: ${r.source})_` : "";
|
|
1303
|
-
queryResults.push(`### ${r.title}${sourceTag}`);
|
|
1304
|
-
queryResults.push(snippet);
|
|
1305
|
-
queryResults.push("");
|
|
1306
|
-
outputSize += snippet.length + r.title.length;
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
else {
|
|
1310
|
-
queryResults.push("No matching sections found.");
|
|
1311
|
-
queryResults.push("");
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1302
|
+
// Run all search queries — source scoped only.
|
|
1303
|
+
// Cross-source search remains available via explicit search().
|
|
1304
|
+
const queryResults = formatBatchQueryResults(store, queries, source);
|
|
1314
1305
|
// Get searchable terms for edge cases where follow-up is needed
|
|
1315
1306
|
const distinctiveTerms = store.getDistinctiveTerms
|
|
1316
1307
|
? store.getDistinctiveTerms(indexed.sourceId)
|
|
@@ -1608,7 +1599,7 @@ server.registerTool("ctx_upgrade", {
|
|
|
1608
1599
|
// Write inline script to a temp .mjs file — avoids quote-escaping issues
|
|
1609
1600
|
// across cmd.exe, PowerShell, and bash (node -e '...' breaks on Windows).
|
|
1610
1601
|
const scriptLines = [
|
|
1611
|
-
`import{
|
|
1602
|
+
`import{execFileSync}from"node:child_process";`,
|
|
1612
1603
|
`import{cpSync,rmSync,existsSync,mkdtempSync}from"node:fs";`,
|
|
1613
1604
|
`import{join}from"node:path";`,
|
|
1614
1605
|
`import{tmpdir}from"node:os";`,
|
|
@@ -1616,15 +1607,15 @@ server.registerTool("ctx_upgrade", {
|
|
|
1616
1607
|
`const T=mkdtempSync(join(tmpdir(),"ctx-upgrade-"));`,
|
|
1617
1608
|
`try{`,
|
|
1618
1609
|
`console.log("- [x] Starting inline upgrade (no CLI found)");`,
|
|
1619
|
-
`
|
|
1610
|
+
`execFileSync("git",["clone","--depth","1","${repoUrl}",T],{stdio:"inherit"});`,
|
|
1620
1611
|
`console.log("- [x] Cloned latest source");`,
|
|
1621
|
-
`
|
|
1622
|
-
`
|
|
1612
|
+
`execFileSync("npm",["install"],{cwd:T,stdio:"inherit"});`,
|
|
1613
|
+
`execFileSync("npm",["run","build"],{cwd:T,stdio:"inherit"});`,
|
|
1623
1614
|
`console.log("- [x] Built from source");`,
|
|
1624
1615
|
...copyDirs.map((d) => `if(existsSync(join(T,${JSON.stringify(d)})))cpSync(join(T,${JSON.stringify(d)}),join(P,${JSON.stringify(d)}),{recursive:true,force:true});`),
|
|
1625
1616
|
...copyFiles.map((f) => `if(existsSync(join(T,${JSON.stringify(f)})))cpSync(join(T,${JSON.stringify(f)}),join(P,${JSON.stringify(f)}),{force:true});`),
|
|
1626
1617
|
`console.log("- [x] Copied build artifacts");`,
|
|
1627
|
-
`
|
|
1618
|
+
`execFileSync("npm",["install","--production"],{cwd:P,stdio:"inherit"});`,
|
|
1628
1619
|
`console.log("- [x] Installed production dependencies");`,
|
|
1629
1620
|
`console.log("## context-mode upgrade complete");`,
|
|
1630
1621
|
`}catch(e){`,
|
package/build/store.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Use for documentation, API references, and any content where
|
|
8
8
|
* you need EXACT text later — not summaries.
|
|
9
9
|
*/
|
|
10
|
+
type SourceMatchMode = "like" | "exact";
|
|
10
11
|
import type { IndexResult, SearchResult, StoreStats } from "./types.js";
|
|
11
12
|
export type { IndexResult, SearchResult, StoreStats } from "./types.js";
|
|
12
13
|
/**
|
|
@@ -42,10 +43,10 @@ export declare class ContentStore {
|
|
|
42
43
|
* Falls back to `indexPlainText` if the content is not valid JSON.
|
|
43
44
|
*/
|
|
44
45
|
indexJSON(content: string, source: string, maxChunkBytes?: number): IndexResult;
|
|
45
|
-
search(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose"): SearchResult[];
|
|
46
|
-
searchTrigram(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose"): SearchResult[];
|
|
46
|
+
search(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
47
|
+
searchTrigram(query: string, limit?: number, source?: string, mode?: "AND" | "OR", contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
47
48
|
fuzzyCorrect(query: string): string | null;
|
|
48
|
-
searchWithFallback(query: string, limit?: number, source?: string, contentType?: "code" | "prose"): SearchResult[];
|
|
49
|
+
searchWithFallback(query: string, limit?: number, source?: string, contentType?: "code" | "prose", sourceMatchMode?: SourceMatchMode): SearchResult[];
|
|
49
50
|
getSourceMeta(label: string): {
|
|
50
51
|
label: string;
|
|
51
52
|
chunkCount: number;
|