context-mode 1.0.124 → 1.0.126
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 +3 -3
- package/build/adapters/claude-code/hooks.d.ts +22 -17
- package/build/adapters/claude-code/hooks.js +33 -24
- package/build/adapters/claude-code/index.d.ts +24 -1
- package/build/adapters/claude-code/index.js +67 -5
- package/build/adapters/codex/hooks.d.ts +13 -14
- package/build/adapters/codex/hooks.js +13 -14
- package/build/adapters/codex/index.js +19 -8
- package/build/adapters/types.d.ts +57 -0
- package/build/adapters/types.js +29 -0
- package/build/cli.js +38 -13
- package/build/db-base.d.ts +19 -2
- package/build/db-base.js +49 -15
- package/build/executor.js +40 -3
- package/build/runtime.d.ts +2 -1
- package/build/runtime.js +10 -0
- package/build/server.js +4 -2
- package/build/util/hook-config.d.ts +24 -1
- package/build/util/hook-config.js +39 -2
- package/build/util/plugin-cache-integrity.d.ts +37 -0
- package/build/util/plugin-cache-integrity.js +105 -0
- package/cli.bundle.mjs +141 -138
- package/configs/codex/hooks.json +1 -1
- package/hooks/core/routing.mjs +8 -4
- package/hooks/hooks.json +1 -1
- package/hooks/session-db.bundle.mjs +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/scripts/plugin-cache-integrity.mjs +168 -0
- package/server.bundle.mjs +97 -94
- package/start.mjs +37 -0
- package/skills/UPSTREAM-CREDITS.md +0 -51
- package/skills/diagnose/SKILL.md +0 -122
- package/skills/diagnose/scripts/hitl-loop.template.sh +0 -41
- package/skills/grill-me/SKILL.md +0 -15
- package/skills/grill-with-docs/ADR-FORMAT.md +0 -47
- package/skills/grill-with-docs/CONTEXT-FORMAT.md +0 -77
- package/skills/grill-with-docs/SKILL.md +0 -93
- package/skills/improve-codebase-architecture/DEEPENING.md +0 -37
- package/skills/improve-codebase-architecture/INTERFACE-DESIGN.md +0 -44
- package/skills/improve-codebase-architecture/LANGUAGE.md +0 -53
- package/skills/improve-codebase-architecture/SKILL.md +0 -76
- package/skills/tdd/SKILL.md +0 -114
- package/skills/tdd/deep-modules.md +0 -33
- package/skills/tdd/interface-design.md +0 -31
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/refactoring.md +0 -10
- package/skills/tdd/tests.md +0 -61
package/build/cli.js
CHANGED
|
@@ -368,22 +368,47 @@ async function doctor() {
|
|
|
368
368
|
(result.fix ? color.dim(`\n Run: ${result.fix}`) : ""));
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
|
-
// Hook scripts exist
|
|
371
|
+
// Hook scripts exist — Algo-D1 protocol path takes precedence.
|
|
372
|
+
// Adapters that override `getHealthChecks` (claude-code today) get a
|
|
373
|
+
// direct `existsSync(join(pluginRoot, "hooks", scriptName))` per
|
|
374
|
+
// HOOK_SCRIPTS entry — no regex round-trip on a hook command, so the
|
|
375
|
+
// #548 doubled-path FAIL class can't surface. Adapters that don't
|
|
376
|
+
// override fall through to the legacy `getHookScriptPaths` flow which
|
|
377
|
+
// generates the hook config and parses each command via
|
|
378
|
+
// `extractHookScriptPath`. Post-D3 every adapter emits buildNodeCommand-
|
|
379
|
+
// shape, so the legacy flow is also safe — but the direct existsSync
|
|
380
|
+
// path is strictly preferable when the adapter offers it.
|
|
372
381
|
p.log.step("Checking hook scripts...");
|
|
373
|
-
const
|
|
374
|
-
if (
|
|
375
|
-
|
|
382
|
+
const adapterHealthChecks = adapter.getHealthChecks?.(pluginRoot) ?? [];
|
|
383
|
+
if (adapterHealthChecks.length > 0) {
|
|
384
|
+
for (const hc of adapterHealthChecks) {
|
|
385
|
+
const result = hc.check();
|
|
386
|
+
if (result.status === "OK") {
|
|
387
|
+
p.log.success(color.green(`${hc.name}: PASS`) +
|
|
388
|
+
(result.detail ? color.dim(` — ${result.detail}`) : ""));
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
p.log.error(color.red(`${hc.name}: FAIL`) +
|
|
392
|
+
(result.detail ? color.dim(` — ${result.detail}`) : ""));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
376
395
|
}
|
|
377
396
|
else {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
397
|
+
const hookScriptPaths = getHookScriptPaths(adapter, pluginRoot);
|
|
398
|
+
if (hookScriptPaths.length === 0) {
|
|
399
|
+
p.log.success(color.green("Hook scripts: PASS") + color.dim(" — no direct .mjs script paths to verify"));
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
for (const scriptPath of hookScriptPaths) {
|
|
403
|
+
const absolutePath = resolve(pluginRoot, scriptPath);
|
|
404
|
+
try {
|
|
405
|
+
accessSync(absolutePath, constants.R_OK);
|
|
406
|
+
p.log.success(color.green("Hook script exists: PASS") + color.dim(` — ${absolutePath}`));
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
p.log.error(color.red("Hook script exists: FAIL") +
|
|
410
|
+
color.dim(` — not found at ${absolutePath}`));
|
|
411
|
+
}
|
|
387
412
|
}
|
|
388
413
|
}
|
|
389
414
|
}
|
package/build/db-base.d.ts
CHANGED
|
@@ -61,11 +61,28 @@ export declare class NodeSQLiteAdapter {
|
|
|
61
61
|
* bundled SQLite always ships with FTS5.
|
|
62
62
|
*/
|
|
63
63
|
export declare function nodeSqliteHasFts5(DatabaseSync: any): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Returns true when the current runtime ships a built-in SQLite binding:
|
|
66
|
+
* - Bun has `bun:sqlite` always
|
|
67
|
+
* - Node has `node:sqlite` since 22.5 (no flag since 22.13)
|
|
68
|
+
*
|
|
69
|
+
* Mirrors the helper in hooks/ensure-deps.mjs:61. Exported so the platform
|
|
70
|
+
* gate in loadDatabase() can be unit-tested without spawning a child
|
|
71
|
+
* process. `versionsOverride` and `bunOverride` are injection points for
|
|
72
|
+
* tests — production callers pass nothing.
|
|
73
|
+
*
|
|
74
|
+
* Widening the gate from `process.platform === "linux"` to this helper is
|
|
75
|
+
* required for Node 26 on macOS arm64 (#551): Node 26 removed
|
|
76
|
+
* `info.This()` from V8 PropertyCallbackInfo, breaking better-sqlite3
|
|
77
|
+
* 12.9.0's native compile. Using node:sqlite sidesteps the native addon
|
|
78
|
+
* entirely on every platform that has it.
|
|
79
|
+
*/
|
|
80
|
+
export declare function hasModernSqlite(versionsOverride?: NodeJS.ProcessVersions, bunOverride?: unknown): boolean;
|
|
64
81
|
/**
|
|
65
82
|
* Lazy-load the SQLite driver for the current runtime.
|
|
66
83
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
67
|
-
*
|
|
68
|
-
* Other Node (or
|
|
84
|
+
* Modern Node (>= 22.5) → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461, #551).
|
|
85
|
+
* Other Node (or modern Node without FTS5) → better-sqlite3 (native addon).
|
|
69
86
|
*/
|
|
70
87
|
export declare function loadDatabase(): typeof DatabaseConstructor;
|
|
71
88
|
/**
|
package/build/db-base.js
CHANGED
|
@@ -184,11 +184,39 @@ export function nodeSqliteHasFts5(DatabaseSync) {
|
|
|
184
184
|
catch { /* probe never opened or already closed */ }
|
|
185
185
|
}
|
|
186
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Returns true when the current runtime ships a built-in SQLite binding:
|
|
189
|
+
* - Bun has `bun:sqlite` always
|
|
190
|
+
* - Node has `node:sqlite` since 22.5 (no flag since 22.13)
|
|
191
|
+
*
|
|
192
|
+
* Mirrors the helper in hooks/ensure-deps.mjs:61. Exported so the platform
|
|
193
|
+
* gate in loadDatabase() can be unit-tested without spawning a child
|
|
194
|
+
* process. `versionsOverride` and `bunOverride` are injection points for
|
|
195
|
+
* tests — production callers pass nothing.
|
|
196
|
+
*
|
|
197
|
+
* Widening the gate from `process.platform === "linux"` to this helper is
|
|
198
|
+
* required for Node 26 on macOS arm64 (#551): Node 26 removed
|
|
199
|
+
* `info.This()` from V8 PropertyCallbackInfo, breaking better-sqlite3
|
|
200
|
+
* 12.9.0's native compile. Using node:sqlite sidesteps the native addon
|
|
201
|
+
* entirely on every platform that has it.
|
|
202
|
+
*/
|
|
203
|
+
export function hasModernSqlite(versionsOverride, bunOverride) {
|
|
204
|
+
const bun = bunOverride !== undefined ? bunOverride : globalThis.Bun;
|
|
205
|
+
if (typeof bun !== "undefined" && bun !== null)
|
|
206
|
+
return true;
|
|
207
|
+
const versions = versionsOverride ?? process.versions;
|
|
208
|
+
const [majorStr, minorStr] = (versions.node ?? "0.0.0").split(".");
|
|
209
|
+
const major = Number(majorStr);
|
|
210
|
+
const minor = Number(minorStr);
|
|
211
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor))
|
|
212
|
+
return false;
|
|
213
|
+
return major > 22 || (major === 22 && minor >= 5);
|
|
214
|
+
}
|
|
187
215
|
/**
|
|
188
216
|
* Lazy-load the SQLite driver for the current runtime.
|
|
189
217
|
* Bun → bun:sqlite via BunSQLiteAdapter (issue #45).
|
|
190
|
-
*
|
|
191
|
-
* Other Node (or
|
|
218
|
+
* Modern Node (>= 22.5) → node:sqlite via NodeSQLiteAdapter when it ships FTS5 (#228, #461, #551).
|
|
219
|
+
* Other Node (or modern Node without FTS5) → better-sqlite3 (native addon).
|
|
192
220
|
*/
|
|
193
221
|
export function loadDatabase() {
|
|
194
222
|
if (!_Database) {
|
|
@@ -211,13 +239,19 @@ export function loadDatabase() {
|
|
|
211
239
|
return adapter;
|
|
212
240
|
};
|
|
213
241
|
}
|
|
214
|
-
else if (
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
242
|
+
else if (hasModernSqlite()) {
|
|
243
|
+
// Any Node >= 22.5 — try node:sqlite to avoid the native addon path
|
|
244
|
+
// entirely. Historically this was Linux-only (avoiding the Linux
|
|
245
|
+
// SIGSEGV per nodejs/node#62515, #228), but Node 26 also broke
|
|
246
|
+
// better-sqlite3's native compile on macOS arm64 by removing
|
|
247
|
+
// V8 `info.This()` (#551). The built-in `node:sqlite` ships its
|
|
248
|
+
// own SQLite, so it sidesteps both issues at once.
|
|
249
|
+
//
|
|
250
|
+
// Probe FTS5 support before committing — some Node builds ship
|
|
251
|
+
// node:sqlite without FTS5, which would silently break ctx_search
|
|
252
|
+
// (#461). The probe runs at most once per process (cached via
|
|
253
|
+
// _Database below), so the cost of an in-memory DatabaseSync is
|
|
254
|
+
// negligible.
|
|
221
255
|
let DatabaseSync = null;
|
|
222
256
|
try {
|
|
223
257
|
// Array.join() prevents esbuild from resolving the specifier at bundle time
|
|
@@ -236,16 +270,16 @@ export function loadDatabase() {
|
|
|
236
270
|
};
|
|
237
271
|
}
|
|
238
272
|
else {
|
|
239
|
-
// node:sqlite missing or built without FTS5 — fall through to
|
|
240
|
-
// Trade-off:
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
273
|
+
// node:sqlite missing or built without FTS5 — fall through to
|
|
274
|
+
// better-sqlite3. Trade-off: on Node 26 + macOS this may now hit
|
|
275
|
+
// the V8 ABI break (#551). A visible crash on the rare
|
|
276
|
+
// unstable build is preferable to silent "no such module: fts5"
|
|
277
|
+
// on every ctx_search call.
|
|
244
278
|
_Database = require("better-sqlite3");
|
|
245
279
|
}
|
|
246
280
|
}
|
|
247
281
|
else {
|
|
248
|
-
//
|
|
282
|
+
// Old Node (< 22.5) without bun:sqlite — fall back to better-sqlite3.
|
|
249
283
|
_Database = require("better-sqlite3");
|
|
250
284
|
}
|
|
251
285
|
}
|
package/build/executor.js
CHANGED
|
@@ -23,6 +23,7 @@ const SCRIPT_EXT = {
|
|
|
23
23
|
perl: "pl",
|
|
24
24
|
r: "R",
|
|
25
25
|
elixir: "exs",
|
|
26
|
+
csharp: "csx",
|
|
26
27
|
};
|
|
27
28
|
/** Pure helper — exported for unit testing. Returns "script" or "script.<ext>". */
|
|
28
29
|
export function buildScriptFilename(language, platform, shellPath) {
|
|
@@ -223,7 +224,7 @@ export class PolyglotExecutor {
|
|
|
223
224
|
// .exe paths now (#506), but if it falls back to the bare "bun" string
|
|
224
225
|
// on Windows that resolution typically goes through a `bun.cmd` shim
|
|
225
226
|
// (npm i -g bun) which CreateProcess can't execute without cmd.exe.
|
|
226
|
-
const needsShell = isWin && ["tsx", "ts-node", "elixir", "bun"].includes(cmd[0]);
|
|
227
|
+
const needsShell = isWin && ["tsx", "ts-node", "elixir", "bun", "dotnet-script"].includes(cmd[0]);
|
|
227
228
|
// On Windows with Git Bash, pass the script as `bash -c "source /posix/path"`
|
|
228
229
|
// rather than `bash /path/to/script.sh`. This avoids MSYS2 path mangling
|
|
229
230
|
// while still allowing MSYS_NO_PATHCONV to protect non-ASCII paths in commands.
|
|
@@ -412,6 +413,30 @@ export class PolyglotExecutor {
|
|
|
412
413
|
"R_PROFILE", // site-wide R profile
|
|
413
414
|
"R_PROFILE_USER", // user R profile
|
|
414
415
|
"R_HOME", // R installation override
|
|
416
|
+
// .NET / C# — runtime/startup hooks, additional deps
|
|
417
|
+
"DOTNET_STARTUP_HOOKS", // injects managed assemblies on startup
|
|
418
|
+
"DOTNET_ADDITIONAL_DEPS", // additional .deps.json injection
|
|
419
|
+
"DOTNET_SHARED_STORE", // shared assembly probe path injection
|
|
420
|
+
"DOTNET_ROOT", // arbitrary .NET runtime override
|
|
421
|
+
"DOTNET_ROOT(x86)", // 32-bit override
|
|
422
|
+
"DOTNET_HOST_PATH", // host binary substitution
|
|
423
|
+
// .NET / C# — profiler attach (loads arbitrary DLL into dotnet host)
|
|
424
|
+
// and IPC-based debugger/IL injection. PR #546 follow-up.
|
|
425
|
+
// learn.microsoft.com/en-us/dotnet/core/runtime-config/debugging-profiling
|
|
426
|
+
"CORECLR_PROFILER", // CLSID of profiler to attach
|
|
427
|
+
"CORECLR_PROFILER_PATH", // path to profiler DLL
|
|
428
|
+
"CORECLR_PROFILER_PATH_32", // 32-bit specific profiler DLL
|
|
429
|
+
"CORECLR_PROFILER_PATH_64", // 64-bit specific profiler DLL
|
|
430
|
+
"CORECLR_PROFILER_PATH_ARM32", // ARM32 specific profiler DLL
|
|
431
|
+
"CORECLR_PROFILER_PATH_ARM64", // ARM64 specific profiler DLL
|
|
432
|
+
"CORECLR_ENABLE_PROFILING", // gates profiler load
|
|
433
|
+
"DOTNET_PROFILER_PATH", // cross-platform alias
|
|
434
|
+
"DOTNET_PROFILER_PATH_32",
|
|
435
|
+
"DOTNET_PROFILER_PATH_64",
|
|
436
|
+
"DOTNET_PROFILER_PATH_ARM32",
|
|
437
|
+
"DOTNET_PROFILER_PATH_ARM64",
|
|
438
|
+
"DOTNET_DiagnosticPorts", // peer attach via diagnostic IPC
|
|
439
|
+
"DOTNET_BUNDLE_EXTRACT_BASE_DIR", // single-file extraction hijack
|
|
415
440
|
// Dynamic linker — shared library injection
|
|
416
441
|
"LD_PRELOAD", // loads .so before all others (Linux)
|
|
417
442
|
"DYLD_INSERT_LIBRARIES", // macOS equivalent of LD_PRELOAD
|
|
@@ -431,10 +456,17 @@ export class PolyglotExecutor {
|
|
|
431
456
|
"GIT_SSH_COMMAND", // arbitrary ssh command
|
|
432
457
|
"GIT_ASKPASS", // arbitrary credential command
|
|
433
458
|
]);
|
|
434
|
-
// Start with parent env, then strip dangerous vars and apply overrides
|
|
459
|
+
// Start with parent env, then strip dangerous vars and apply overrides.
|
|
460
|
+
// The `COMPlus_` prefix sweep covers every COMPlus_* synonym of the
|
|
461
|
+
// DOTNET_* runtime knobs (.NET back-compat alias — case-insensitive).
|
|
462
|
+
// PR #546 follow-up: closes the alias bypass for the explicit denylist
|
|
463
|
+
// entries above.
|
|
435
464
|
const env = {};
|
|
436
465
|
for (const [key, val] of Object.entries(process.env)) {
|
|
437
|
-
if (val !== undefined &&
|
|
466
|
+
if (val !== undefined &&
|
|
467
|
+
!DENIED.has(key) &&
|
|
468
|
+
!key.startsWith("BASH_FUNC_") &&
|
|
469
|
+
!/^COMPlus_/i.test(key)) {
|
|
438
470
|
env[key] = val;
|
|
439
471
|
}
|
|
440
472
|
}
|
|
@@ -508,6 +540,11 @@ export class PolyglotExecutor {
|
|
|
508
540
|
return `FILE_CONTENT_PATH <- ${escaped}\nfile_path <- FILE_CONTENT_PATH\nFILE_CONTENT <- readLines(FILE_CONTENT_PATH, warn=FALSE, encoding="UTF-8")\nFILE_CONTENT <- paste(FILE_CONTENT, collapse="\\n")\n${code}`;
|
|
509
541
|
case "elixir":
|
|
510
542
|
return `file_content_path = ${escaped}\nfile_path = file_content_path\nfile_content = File.read!(file_content_path)\n${code}`;
|
|
543
|
+
case "csharp":
|
|
544
|
+
// .csx forbids `using` directives after any other top-level statement
|
|
545
|
+
// (CS1529). User code inside executeFile must use fully-qualified type
|
|
546
|
+
// names (e.g. `System.Text.Json.JsonDocument`) instead of `using`.
|
|
547
|
+
return `var FILE_CONTENT_PATH = ${escaped};\nvar file_path = FILE_CONTENT_PATH;\nvar FILE_CONTENT = System.IO.File.ReadAllText(FILE_CONTENT_PATH);\n${code}`;
|
|
511
548
|
}
|
|
512
549
|
}
|
|
513
550
|
}
|
package/build/runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare function isAllowlistedShell(shellPath: string): boolean;
|
|
2
|
-
export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r" | "elixir";
|
|
2
|
+
export type Language = "javascript" | "typescript" | "python" | "shell" | "ruby" | "go" | "rust" | "php" | "perl" | "r" | "elixir" | "csharp";
|
|
3
3
|
export interface RuntimeInfo {
|
|
4
4
|
command: string;
|
|
5
5
|
available: boolean;
|
|
@@ -18,6 +18,7 @@ export interface RuntimeMap {
|
|
|
18
18
|
perl: string | null;
|
|
19
19
|
r: string | null;
|
|
20
20
|
elixir: string | null;
|
|
21
|
+
csharp: string | null;
|
|
21
22
|
}
|
|
22
23
|
export declare function detectRuntimes(): RuntimeMap;
|
|
23
24
|
export declare function hasBunRuntime(): boolean;
|
package/build/runtime.js
CHANGED
|
@@ -232,6 +232,7 @@ export function detectRuntimes() {
|
|
|
232
232
|
? "r"
|
|
233
233
|
: null,
|
|
234
234
|
elixir: commandExists("elixir") ? "elixir" : null,
|
|
235
|
+
csharp: commandExists("dotnet-script") ? "dotnet-script" : null,
|
|
235
236
|
};
|
|
236
237
|
}
|
|
237
238
|
export function hasBunRuntime() {
|
|
@@ -269,6 +270,8 @@ export function getRuntimeSummary(runtimes) {
|
|
|
269
270
|
lines.push(` R: ${runtimes.r} (${getVersion(runtimes.r)})`);
|
|
270
271
|
if (runtimes.elixir)
|
|
271
272
|
lines.push(` Elixir: ${runtimes.elixir} (${getVersion(runtimes.elixir)})`);
|
|
273
|
+
if (runtimes.csharp)
|
|
274
|
+
lines.push(` C#: ${runtimes.csharp} (${getVersion(runtimes.csharp)})`);
|
|
272
275
|
if (!bunPreferred) {
|
|
273
276
|
lines.push("");
|
|
274
277
|
lines.push(" Tip: Install Bun for 3-5x faster JS/TS execution → https://bun.sh");
|
|
@@ -295,6 +298,8 @@ export function getAvailableLanguages(runtimes) {
|
|
|
295
298
|
langs.push("r");
|
|
296
299
|
if (runtimes.elixir)
|
|
297
300
|
langs.push("elixir");
|
|
301
|
+
if (runtimes.csharp)
|
|
302
|
+
langs.push("csharp");
|
|
298
303
|
return langs;
|
|
299
304
|
}
|
|
300
305
|
export function buildCommand(runtimes, language, filePath) {
|
|
@@ -376,5 +381,10 @@ export function buildCommand(runtimes, language, filePath) {
|
|
|
376
381
|
throw new Error("Elixir not available. Install elixir.");
|
|
377
382
|
}
|
|
378
383
|
return ["elixir", filePath];
|
|
384
|
+
case "csharp":
|
|
385
|
+
if (!runtimes.csharp) {
|
|
386
|
+
throw new Error("C# not available. Install dotnet-script via `dotnet tool install -g dotnet-script`.");
|
|
387
|
+
}
|
|
388
|
+
return [runtimes.csharp, filePath];
|
|
379
389
|
}
|
|
380
390
|
}
|
package/build/server.js
CHANGED
|
@@ -929,11 +929,12 @@ server.registerTool("ctx_execute", {
|
|
|
929
929
|
"perl",
|
|
930
930
|
"r",
|
|
931
931
|
"elixir",
|
|
932
|
+
"csharp",
|
|
932
933
|
])
|
|
933
934
|
.describe("Runtime language"),
|
|
934
935
|
code: z
|
|
935
936
|
.string()
|
|
936
|
-
.describe("Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go),
|
|
937
|
+
.describe("Source code to execute. Use console.log (JS/TS), print (Python/Ruby/Perl/R), echo (Shell), echo (PHP), fmt.Println (Go), IO.puts (Elixir), or Console.WriteLine (C#) to output a summary to context."),
|
|
937
938
|
timeout: z
|
|
938
939
|
.coerce.number()
|
|
939
940
|
.optional()
|
|
@@ -1224,11 +1225,12 @@ server.registerTool("ctx_execute_file", {
|
|
|
1224
1225
|
"perl",
|
|
1225
1226
|
"r",
|
|
1226
1227
|
"elixir",
|
|
1228
|
+
"csharp",
|
|
1227
1229
|
])
|
|
1228
1230
|
.describe("Runtime language"),
|
|
1229
1231
|
code: z
|
|
1230
1232
|
.string()
|
|
1231
|
-
.describe("Code to process FILE_CONTENT (file_content in Elixir). Print summary via console.log/print/echo/IO.puts."),
|
|
1233
|
+
.describe("Code to process FILE_CONTENT (file_content in Elixir). Print summary via console.log/print/echo/IO.puts/Console.WriteLine."),
|
|
1232
1234
|
timeout: z
|
|
1233
1235
|
.coerce.number()
|
|
1234
1236
|
.optional()
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HookAdapter } from "../adapters/types.js";
|
|
2
2
|
export declare function getCommandsFromHookEntry(entry: unknown): string[];
|
|
3
|
+
/**
|
|
4
|
+
* Extract the hook script path from a hook command string.
|
|
5
|
+
*
|
|
6
|
+
* Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
|
|
7
|
+
* single legacy fallback retained for stale-entry cleanup
|
|
8
|
+
* (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
|
|
9
|
+
* predate `buildNodeCommand`). The legacy branches are deliberately
|
|
10
|
+
* narrow:
|
|
11
|
+
*
|
|
12
|
+
* 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
|
|
13
|
+
* handles this; round-trips with `buildNodeCommand`.
|
|
14
|
+
* 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
|
|
15
|
+
* pre-D3. The script segment is fully quoted, no whitespace
|
|
16
|
+
* ambiguity.
|
|
17
|
+
* 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
|
|
18
|
+
* command is whitespace-safe (exactly two whitespace-separated
|
|
19
|
+
* tokens). The #548 wire shape — `node C:/Users/High Ground …` —
|
|
20
|
+
* contains internal whitespace so this branch refuses it. Returns
|
|
21
|
+
* `null` instead of grabbing the tail after the last whitespace.
|
|
22
|
+
*
|
|
23
|
+
* Anything else returns `null`, letting the doctor (Algo-D1) fall
|
|
24
|
+
* through to direct `existsSync` instead of trusting the regex.
|
|
25
|
+
*/
|
|
3
26
|
export declare function extractHookScriptPath(command: string): string | null;
|
|
4
27
|
export declare function getHookScriptPaths(adapter: HookAdapter, pluginRoot: string): string[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseNodeCommand } from "../adapters/types.js";
|
|
1
2
|
export function getCommandsFromHookEntry(entry) {
|
|
2
3
|
const commands = [];
|
|
3
4
|
if (entry && typeof entry === "object") {
|
|
@@ -17,9 +18,45 @@ export function getCommandsFromHookEntry(entry) {
|
|
|
17
18
|
}
|
|
18
19
|
return commands;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract the hook script path from a hook command string.
|
|
23
|
+
*
|
|
24
|
+
* Post Algo-D2 this is a thin wrapper around `parseNodeCommand` with a
|
|
25
|
+
* single legacy fallback retained for stale-entry cleanup
|
|
26
|
+
* (`configureAllHooks` walks pre-v1.0.124 settings.json shapes that
|
|
27
|
+
* predate `buildNodeCommand`). The legacy branches are deliberately
|
|
28
|
+
* narrow:
|
|
29
|
+
*
|
|
30
|
+
* 1) Canonical: `"<nodePath>" "<scriptPath>.mjs"` — `parseNodeCommand`
|
|
31
|
+
* handles this; round-trips with `buildNodeCommand`.
|
|
32
|
+
* 2) Legacy quoted: `node "<scriptPath>.mjs"` — emitted by claude-code
|
|
33
|
+
* pre-D3. The script segment is fully quoted, no whitespace
|
|
34
|
+
* ambiguity.
|
|
35
|
+
* 3) Legacy unquoted: `node <scriptPath>.mjs` — only when the entire
|
|
36
|
+
* command is whitespace-safe (exactly two whitespace-separated
|
|
37
|
+
* tokens). The #548 wire shape — `node C:/Users/High Ground …` —
|
|
38
|
+
* contains internal whitespace so this branch refuses it. Returns
|
|
39
|
+
* `null` instead of grabbing the tail after the last whitespace.
|
|
40
|
+
*
|
|
41
|
+
* Anything else returns `null`, letting the doctor (Algo-D1) fall
|
|
42
|
+
* through to direct `existsSync` instead of trusting the regex.
|
|
43
|
+
*/
|
|
20
44
|
export function extractHookScriptPath(command) {
|
|
21
|
-
const
|
|
22
|
-
|
|
45
|
+
const parsed = parseNodeCommand(command);
|
|
46
|
+
if (parsed) {
|
|
47
|
+
return parsed.scriptPath.endsWith(".mjs") ? parsed.scriptPath : null;
|
|
48
|
+
}
|
|
49
|
+
// Legacy quoted: `node "/path/with spaces/x.mjs"` (pre-D3 claude-code emit).
|
|
50
|
+
const legacyQuoted = command.match(/^\s*node\s+"([^"]+\.mjs)"\s*$/);
|
|
51
|
+
if (legacyQuoted)
|
|
52
|
+
return legacyQuoted[1];
|
|
53
|
+
// Legacy unquoted: `node /path/x.mjs` — refuses internal whitespace
|
|
54
|
+
// by anchoring both tokens. The #548 ambiguous shape has 3+ tokens
|
|
55
|
+
// (spaces in the path) and falls through to `null`.
|
|
56
|
+
const legacyBare = command.match(/^\s*node\s+(\S+\.mjs)\s*$/);
|
|
57
|
+
if (legacyBare)
|
|
58
|
+
return legacyBare[1];
|
|
59
|
+
return null;
|
|
23
60
|
}
|
|
24
61
|
export function getHookScriptPaths(adapter, pluginRoot) {
|
|
25
62
|
const paths = new Set();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript surface for the start.mjs plugin-cache integrity helper.
|
|
3
|
+
*
|
|
4
|
+
* The actual logic lives in `scripts/plugin-cache-integrity.mjs` (raw
|
|
5
|
+
* `.mjs` so start.mjs can import it without a TS toolchain at boot —
|
|
6
|
+
* #550 fail-fast happens BEFORE any bundle is loaded). This module is
|
|
7
|
+
* the bridge that lets TS consumers (claude-code adapter's
|
|
8
|
+
* getHealthChecks for Algo-D5, the cli doctor surface) call the same
|
|
9
|
+
* function without duplicating the implementation.
|
|
10
|
+
*
|
|
11
|
+
* Single source of truth: scripts/plugin-cache-integrity.mjs. Boot
|
|
12
|
+
* fail-fast (Algo-D4) and doctor diagnostic (Algo-D5) agree
|
|
13
|
+
* byte-for-byte because they call the same exported function.
|
|
14
|
+
*
|
|
15
|
+
* Top-level dynamic import is used (not a static `import` from `.mjs`)
|
|
16
|
+
* because the project is ESM and `import` of a sibling `.mjs` from a
|
|
17
|
+
* `.ts` file relies on the bundler / loader resolving `.mjs`
|
|
18
|
+
* extensions, which esbuild can do but tsc-only typecheck cannot. The
|
|
19
|
+
* dynamic import is resolved by the runtime (Node ESM) regardless of
|
|
20
|
+
* how the consumer was bundled. Errors are caught and surfaced as a
|
|
21
|
+
* FAIL detail — the helper is required to ship in the npm tarball
|
|
22
|
+
* (package.json files[]); a missing helper means the install is
|
|
23
|
+
* fundamentally broken.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Run the integrity check synchronously. If the helper module is
|
|
27
|
+
* still loading (not yet cached) returns a FAIL with detail
|
|
28
|
+
* "integrity helper not yet loaded" — caller should retry once the
|
|
29
|
+
* doctor command's IO is complete. In practice the doctor is invoked
|
|
30
|
+
* many MS after module load so this fallback is defensive only.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkPluginCacheIntegritySync(pluginRoot: string): {
|
|
33
|
+
status: "OK" | "FAIL";
|
|
34
|
+
detail: string;
|
|
35
|
+
};
|
|
36
|
+
/** Force-await the helper load. Tests use this to deflake the eager fire-and-forget. */
|
|
37
|
+
export declare function ensurePluginCacheIntegrityLoaded(): Promise<void>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript surface for the start.mjs plugin-cache integrity helper.
|
|
3
|
+
*
|
|
4
|
+
* The actual logic lives in `scripts/plugin-cache-integrity.mjs` (raw
|
|
5
|
+
* `.mjs` so start.mjs can import it without a TS toolchain at boot —
|
|
6
|
+
* #550 fail-fast happens BEFORE any bundle is loaded). This module is
|
|
7
|
+
* the bridge that lets TS consumers (claude-code adapter's
|
|
8
|
+
* getHealthChecks for Algo-D5, the cli doctor surface) call the same
|
|
9
|
+
* function without duplicating the implementation.
|
|
10
|
+
*
|
|
11
|
+
* Single source of truth: scripts/plugin-cache-integrity.mjs. Boot
|
|
12
|
+
* fail-fast (Algo-D4) and doctor diagnostic (Algo-D5) agree
|
|
13
|
+
* byte-for-byte because they call the same exported function.
|
|
14
|
+
*
|
|
15
|
+
* Top-level dynamic import is used (not a static `import` from `.mjs`)
|
|
16
|
+
* because the project is ESM and `import` of a sibling `.mjs` from a
|
|
17
|
+
* `.ts` file relies on the bundler / loader resolving `.mjs`
|
|
18
|
+
* extensions, which esbuild can do but tsc-only typecheck cannot. The
|
|
19
|
+
* dynamic import is resolved by the runtime (Node ESM) regardless of
|
|
20
|
+
* how the consumer was bundled. Errors are caught and surfaced as a
|
|
21
|
+
* FAIL detail — the helper is required to ship in the npm tarball
|
|
22
|
+
* (package.json files[]); a missing helper means the install is
|
|
23
|
+
* fundamentally broken.
|
|
24
|
+
*/
|
|
25
|
+
let cached = null;
|
|
26
|
+
let cachedError = null;
|
|
27
|
+
async function loadHelper() {
|
|
28
|
+
if (cached)
|
|
29
|
+
return cached;
|
|
30
|
+
if (cachedError)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
// Resolve relative to this compiled file. After tsc emits to
|
|
34
|
+
// build/util/plugin-cache-integrity.js, the helper sits at
|
|
35
|
+
// ../../scripts/plugin-cache-integrity.mjs. After esbuild bundles
|
|
36
|
+
// src/cli.ts to cli.bundle.mjs at the repo root, the same relative
|
|
37
|
+
// path resolves to ./scripts/plugin-cache-integrity.mjs. Both
|
|
38
|
+
// shapes are walked here.
|
|
39
|
+
const candidates = [
|
|
40
|
+
new URL("../../scripts/plugin-cache-integrity.mjs", import.meta.url),
|
|
41
|
+
new URL("./scripts/plugin-cache-integrity.mjs", import.meta.url),
|
|
42
|
+
];
|
|
43
|
+
let lastErr = null;
|
|
44
|
+
for (const url of candidates) {
|
|
45
|
+
try {
|
|
46
|
+
const mod = (await import(url.href));
|
|
47
|
+
if (typeof mod?.assertPluginCacheIntegrity === "function") {
|
|
48
|
+
cached = mod;
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
lastErr = err;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
cachedError =
|
|
57
|
+
lastErr instanceof Error ? lastErr.message : String(lastErr ?? "not found");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
cachedError = err instanceof Error ? err.message : String(err);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Eagerly start the load on module init so the first synchronous
|
|
66
|
+
// check() call can hit the cache. The promise is unawaited
|
|
67
|
+
// intentionally — by the time any HealthCheck.check() runs (doctor
|
|
68
|
+
// command, well after MCP server boot), the import has resolved.
|
|
69
|
+
void loadHelper();
|
|
70
|
+
/**
|
|
71
|
+
* Run the integrity check synchronously. If the helper module is
|
|
72
|
+
* still loading (not yet cached) returns a FAIL with detail
|
|
73
|
+
* "integrity helper not yet loaded" — caller should retry once the
|
|
74
|
+
* doctor command's IO is complete. In practice the doctor is invoked
|
|
75
|
+
* many MS after module load so this fallback is defensive only.
|
|
76
|
+
*/
|
|
77
|
+
export function checkPluginCacheIntegritySync(pluginRoot) {
|
|
78
|
+
if (cached) {
|
|
79
|
+
const result = cached.assertPluginCacheIntegrity({ pluginRoot });
|
|
80
|
+
if (result.ok) {
|
|
81
|
+
return {
|
|
82
|
+
status: "OK",
|
|
83
|
+
detail: `${pluginRoot} (all required runtime siblings present)`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
status: "FAIL",
|
|
88
|
+
detail: `missing: ${result.missing.join(", ")}`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (cachedError) {
|
|
92
|
+
return {
|
|
93
|
+
status: "FAIL",
|
|
94
|
+
detail: `integrity helper unavailable: ${cachedError}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
status: "FAIL",
|
|
99
|
+
detail: "integrity helper not yet loaded",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Force-await the helper load. Tests use this to deflake the eager fire-and-forget. */
|
|
103
|
+
export async function ensurePluginCacheIntegrityLoaded() {
|
|
104
|
+
await loadHelper();
|
|
105
|
+
}
|