context-mode 1.0.53 → 1.0.56
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 +103 -32
- package/build/adapters/antigravity/index.d.ts +1 -3
- package/build/adapters/antigravity/index.js +0 -30
- 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.d.ts +1 -3
- package/build/adapters/claude-code/index.js +48 -35
- package/build/adapters/client-map.js +1 -0
- package/build/adapters/codex/index.d.ts +1 -3
- package/build/adapters/codex/index.js +1 -31
- package/build/adapters/cursor/index.d.ts +1 -3
- package/build/adapters/cursor/index.js +0 -11
- package/build/adapters/detect.d.ts +1 -0
- package/build/adapters/detect.js +18 -2
- package/build/adapters/gemini-cli/index.d.ts +1 -3
- package/build/adapters/gemini-cli/index.js +0 -30
- package/build/adapters/kiro/index.d.ts +1 -3
- package/build/adapters/kiro/index.js +0 -30
- package/build/adapters/openclaw/index.d.ts +1 -3
- package/build/adapters/openclaw/index.js +0 -38
- package/build/adapters/opencode/index.d.ts +5 -4
- package/build/adapters/opencode/index.js +37 -41
- package/build/adapters/types.d.ts +1 -14
- package/build/adapters/vscode-copilot/index.d.ts +1 -3
- package/build/adapters/vscode-copilot/index.js +0 -32
- package/build/adapters/zed/index.d.ts +1 -3
- package/build/adapters/zed/index.js +0 -30
- package/build/cli.js +12 -28
- package/build/executor.d.ts +0 -1
- package/build/executor.js +28 -16
- package/build/openclaw-plugin.js +12 -34
- package/build/opencode-plugin.d.ts +1 -0
- package/build/opencode-plugin.js +5 -9
- package/build/runtime.js +29 -11
- package/build/server.d.ts +2 -0
- package/build/server.js +69 -61
- package/build/store.d.ts +4 -3
- package/build/store.js +101 -34
- package/build/truncate.d.ts +4 -17
- package/build/truncate.js +4 -52
- package/cli.bundle.mjs +184 -157
- 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/ensure-deps.mjs +80 -2
- package/hooks/pretooluse.mjs +25 -20
- package/hooks/routing-block.mjs +10 -1
- package/hooks/session-snapshot.bundle.mjs +13 -13
- package/hooks/sessionstart.mjs +25 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +159 -129
- package/skills/context-mode-ops/SKILL.md +111 -0
- package/skills/context-mode-ops/agent-teams.md +198 -0
- package/skills/context-mode-ops/communication.md +224 -0
- package/skills/context-mode-ops/release.md +199 -0
- package/skills/context-mode-ops/review-pr.md +269 -0
- package/skills/context-mode-ops/tdd.md +329 -0
- package/skills/context-mode-ops/triage-issue.md +218 -0
- package/skills/context-mode-ops/validation.md +238 -0
- package/start.mjs +5 -52
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.d.ts
CHANGED
package/build/executor.js
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
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";
|
|
5
5
|
import { detectRuntimes, buildCommand, } from "./runtime.js";
|
|
6
|
-
import { smartTruncate } from "./truncate.js";
|
|
7
6
|
const isWin = process.platform === "win32";
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the real OS temp directory, bypassing any TMPDIR env override.
|
|
9
|
+
* os.tmpdir() reads TMPDIR from the environment, which some shells/tools
|
|
10
|
+
* set to the project root — causing temp files to pollute the working tree.
|
|
11
|
+
*/
|
|
12
|
+
const OS_TMPDIR = (() => {
|
|
13
|
+
if (isWin)
|
|
14
|
+
return process.env.TEMP ?? process.env.TMP ?? tmpdir();
|
|
15
|
+
try {
|
|
16
|
+
const result = execFileSync(process.platform === "darwin" ? "getconf" : "mktemp", process.platform === "darwin" ? ["DARWIN_USER_TEMP_DIR"] : ["-u", "-d"], { env: { ...process.env, TMPDIR: undefined }, encoding: "utf-8" }).trim();
|
|
17
|
+
const dir = process.platform === "darwin" ? result : resolve(result, "..");
|
|
18
|
+
if (dir && dir !== process.cwd())
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
catch { /* fall through */ }
|
|
22
|
+
return "/tmp";
|
|
23
|
+
})();
|
|
8
24
|
/** Kill process tree — on Windows uses taskkill /T; on Unix kills the process group. */
|
|
9
25
|
function killTree(proc) {
|
|
10
26
|
if (isWin && proc.pid) {
|
|
@@ -22,14 +38,12 @@ function killTree(proc) {
|
|
|
22
38
|
}
|
|
23
39
|
}
|
|
24
40
|
export class PolyglotExecutor {
|
|
25
|
-
#maxOutputBytes;
|
|
26
41
|
#hardCapBytes;
|
|
27
42
|
#projectRoot;
|
|
28
43
|
#runtimes;
|
|
29
44
|
/** PIDs of backgrounded processes — killed on cleanup to prevent zombies. */
|
|
30
45
|
#backgroundedPids = new Set();
|
|
31
46
|
constructor(opts) {
|
|
32
|
-
this.#maxOutputBytes = opts?.maxOutputBytes ?? 102_400;
|
|
33
47
|
this.#hardCapBytes = opts?.hardCapBytes ?? 100 * 1024 * 1024; // 100MB
|
|
34
48
|
this.#projectRoot = opts?.projectRoot ?? process.cwd();
|
|
35
49
|
this.#runtimes = opts?.runtimes ?? detectRuntimes();
|
|
@@ -50,7 +64,7 @@ export class PolyglotExecutor {
|
|
|
50
64
|
}
|
|
51
65
|
async execute(opts) {
|
|
52
66
|
const { language, code, timeout = 30_000, background = false } = opts;
|
|
53
|
-
const tmpDir = mkdtempSync(join(
|
|
67
|
+
const tmpDir = mkdtempSync(join(OS_TMPDIR, ".ctx-mode-"));
|
|
54
68
|
try {
|
|
55
69
|
const filePath = this.#writeScript(tmpDir, code, language);
|
|
56
70
|
const cmd = buildCommand(this.#runtimes, language, filePath);
|
|
@@ -62,7 +76,7 @@ export class PolyglotExecutor {
|
|
|
62
76
|
// and other project-aware tools work naturally. Non-shell languages
|
|
63
77
|
// run in the temp directory where their script file is written.
|
|
64
78
|
const cwd = language === "shell" ? this.#projectRoot : tmpDir;
|
|
65
|
-
const result = await this.#spawn(cmd, cwd, timeout, background);
|
|
79
|
+
const result = await this.#spawn(cmd, cwd, tmpDir, timeout, background);
|
|
66
80
|
// Skip tmpDir cleanup if process was backgrounded — it may still need files
|
|
67
81
|
if (!result.backgrounded) {
|
|
68
82
|
try {
|
|
@@ -127,7 +141,7 @@ export class PolyglotExecutor {
|
|
|
127
141
|
const binPath = srcPath.replace(/\.rs$/, "") + binSuffix;
|
|
128
142
|
// Compile
|
|
129
143
|
try {
|
|
130
|
-
|
|
144
|
+
execFileSync("rustc", [srcPath, "-o", binPath], {
|
|
131
145
|
cwd,
|
|
132
146
|
timeout: Math.min(timeout, 60_000),
|
|
133
147
|
encoding: "utf-8",
|
|
@@ -144,9 +158,9 @@ export class PolyglotExecutor {
|
|
|
144
158
|
};
|
|
145
159
|
}
|
|
146
160
|
// Run
|
|
147
|
-
return this.#spawn([binPath], cwd, timeout);
|
|
161
|
+
return this.#spawn([binPath], cwd, cwd, timeout);
|
|
148
162
|
}
|
|
149
|
-
async #spawn(cmd, cwd, timeout, background = false) {
|
|
163
|
+
async #spawn(cmd, cwd, sandboxTmpDir, timeout, background = false) {
|
|
150
164
|
return new Promise((res) => {
|
|
151
165
|
// Only .cmd/.bat shims need shell on Windows; real executables don't.
|
|
152
166
|
// Using shell: true globally causes process-tree kill issues with MSYS2/Git Bash.
|
|
@@ -168,7 +182,7 @@ export class PolyglotExecutor {
|
|
|
168
182
|
const proc = spawn(spawnCmd, spawnArgs, {
|
|
169
183
|
cwd,
|
|
170
184
|
stdio: ["ignore", "pipe", "pipe"],
|
|
171
|
-
env: this.#buildSafeEnv(
|
|
185
|
+
env: this.#buildSafeEnv(sandboxTmpDir),
|
|
172
186
|
shell: needsShell,
|
|
173
187
|
// On Unix, create a new process group so killTree can kill all children
|
|
174
188
|
detached: !isWin,
|
|
@@ -187,10 +201,9 @@ export class PolyglotExecutor {
|
|
|
187
201
|
proc.stderr.destroy();
|
|
188
202
|
const rawStdout = Buffer.concat(stdoutChunks).toString("utf-8");
|
|
189
203
|
const rawStderr = Buffer.concat(stderrChunks).toString("utf-8");
|
|
190
|
-
const max = this.#maxOutputBytes;
|
|
191
204
|
res({
|
|
192
|
-
stdout:
|
|
193
|
-
stderr:
|
|
205
|
+
stdout: rawStdout,
|
|
206
|
+
stderr: rawStderr,
|
|
194
207
|
exitCode: 0,
|
|
195
208
|
timedOut: true,
|
|
196
209
|
backgrounded: true,
|
|
@@ -237,9 +250,8 @@ export class PolyglotExecutor {
|
|
|
237
250
|
if (capExceeded) {
|
|
238
251
|
rawStderr += `\n[output capped at ${(this.#hardCapBytes / 1024 / 1024).toFixed(0)}MB — process killed]`;
|
|
239
252
|
}
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const stderr = smartTruncate(rawStderr, max);
|
|
253
|
+
const stdout = rawStdout;
|
|
254
|
+
const stderr = rawStderr;
|
|
243
255
|
res({
|
|
244
256
|
stdout,
|
|
245
257
|
stderr,
|
package/build/openclaw-plugin.js
CHANGED
|
@@ -36,7 +36,6 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
36
36
|
import { OpenClawSessionDB } from "./adapters/openclaw/session-db.js";
|
|
37
37
|
import { extractEvents, extractUserEvents } from "./session/extract.js";
|
|
38
38
|
import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
39
|
-
import { OpenClawAdapter } from "./adapters/openclaw/index.js";
|
|
40
39
|
import { WorkspaceRouter } from "./openclaw/workspace-router.js";
|
|
41
40
|
/** Plugin config schema for OpenClaw validation. */
|
|
42
41
|
const configSchema = {
|
|
@@ -130,17 +129,11 @@ export default {
|
|
|
130
129
|
catch {
|
|
131
130
|
// best effort
|
|
132
131
|
}
|
|
133
|
-
// Async init: load routing module
|
|
132
|
+
// Async init: load routing module. Hooks await this.
|
|
134
133
|
const initPromise = (async () => {
|
|
135
134
|
const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
|
|
136
135
|
const routing = await import(pathToFileURL(routingPath).href);
|
|
137
136
|
await routing.initSecurity(buildDir);
|
|
138
|
-
try {
|
|
139
|
-
new OpenClawAdapter().writeRoutingInstructions(projectDir, pluginRoot);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
// best effort — never break plugin init
|
|
143
|
-
}
|
|
144
137
|
return { routing };
|
|
145
138
|
})();
|
|
146
139
|
// ── 1. tool_call:before — Routing enforcement ──────────
|
|
@@ -315,6 +308,10 @@ export default {
|
|
|
315
308
|
workspaceRouter.registerSession(key, sessionId);
|
|
316
309
|
}
|
|
317
310
|
resumeInjected = false;
|
|
311
|
+
// Write routing instructions (AGENTS.md) now that we know the real
|
|
312
|
+
// workspace. Derive the workspace directory from the sessionKey so we
|
|
313
|
+
// only write into recognised /.openclaw/workspace* paths, never into
|
|
314
|
+
// the gateway's cwd or any other arbitrary directory.
|
|
318
315
|
}
|
|
319
316
|
catch {
|
|
320
317
|
// best effort — never break session start
|
|
@@ -402,7 +399,7 @@ export default {
|
|
|
402
399
|
info: {
|
|
403
400
|
id: "context-mode",
|
|
404
401
|
name: "Context Mode",
|
|
405
|
-
ownsCompaction:
|
|
402
|
+
ownsCompaction: false,
|
|
406
403
|
},
|
|
407
404
|
async ingest() {
|
|
408
405
|
return { ingested: true };
|
|
@@ -410,31 +407,12 @@ export default {
|
|
|
410
407
|
async assemble({ messages }) {
|
|
411
408
|
return { messages, estimatedTokens: 0 };
|
|
412
409
|
},
|
|
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
|
-
}
|
|
410
|
+
async compact() {
|
|
411
|
+
// No-op: session continuity is handled by before_compaction / after_compaction hooks.
|
|
412
|
+
// Returning ownsCompaction: false + compacted: false lets the host platform (OpenClaw)
|
|
413
|
+
// manage conversation truncation, preserving Anthropic thinking/redacted_thinking blocks.
|
|
414
|
+
// See: https://github.com/mksglu/context-mode/issues/191
|
|
415
|
+
return { ok: true, compacted: false };
|
|
438
416
|
},
|
|
439
417
|
}));
|
|
440
418
|
// ── 10. Auto-reply commands — ctx slash commands ──────
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Constraints:
|
|
12
12
|
* - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
|
|
13
13
|
* - No context injection (canInjectSessionContext: false)
|
|
14
|
+
* - No routing file auto-write (avoid dirtying project trees)
|
|
14
15
|
* - Session cleanup happens at plugin init (no SessionStart)
|
|
15
16
|
*/
|
|
16
17
|
/** OpenCode plugin context passed to the factory function. */
|
package/build/opencode-plugin.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Constraints:
|
|
12
12
|
* - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
|
|
13
13
|
* - No context injection (canInjectSessionContext: false)
|
|
14
|
+
* - No routing file auto-write (avoid dirtying project trees)
|
|
14
15
|
* - Session cleanup happens at plugin init (no SessionStart)
|
|
15
16
|
*/
|
|
16
17
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -21,10 +22,12 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
21
22
|
import { SessionDB } from "./session/db.js";
|
|
22
23
|
import { extractEvents } from "./session/extract.js";
|
|
23
24
|
import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
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
|
}
|
|
@@ -52,13 +55,6 @@ export const ContextModePlugin = async (ctx) => {
|
|
|
52
55
|
const db = new SessionDB({ dbPath: getDBPath(projectDir) });
|
|
53
56
|
const sessionId = randomUUID();
|
|
54
57
|
db.ensureSession(sessionId, projectDir);
|
|
55
|
-
// Auto-write AGENTS.md on startup for OpenCode projects
|
|
56
|
-
try {
|
|
57
|
-
new OpenCodeAdapter().writeRoutingInstructions(projectDir, resolve(buildDir, ".."));
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// best effort — never break plugin init
|
|
61
|
-
}
|
|
62
58
|
// Clean up old sessions on startup (replaces SessionStart hook)
|
|
63
59
|
db.cleanupOldSessions(0);
|
|
64
60
|
return {
|
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[];
|