context-mode 1.0.21 → 1.0.23
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 +4 -2
- package/.openclaw-plugin/index.ts +11 -0
- package/.openclaw-plugin/openclaw.plugin.json +23 -0
- package/.openclaw-plugin/package.json +28 -0
- package/README.md +165 -26
- package/build/adapters/antigravity/index.d.ts +49 -0
- package/build/adapters/antigravity/index.js +217 -0
- package/build/adapters/client-map.d.ts +10 -0
- package/build/adapters/client-map.js +18 -0
- package/build/adapters/detect.d.ts +8 -1
- package/build/adapters/detect.js +58 -1
- package/build/adapters/kiro/hooks.d.ts +32 -0
- package/build/adapters/kiro/hooks.js +47 -0
- package/build/adapters/kiro/index.d.ts +50 -0
- package/build/adapters/kiro/index.js +325 -0
- package/build/adapters/openclaw/config.d.ts +8 -0
- package/build/adapters/openclaw/config.js +8 -0
- package/build/adapters/openclaw/hooks.d.ts +50 -0
- package/build/adapters/openclaw/hooks.js +61 -0
- package/build/adapters/openclaw/index.d.ts +51 -0
- package/build/adapters/openclaw/index.js +459 -0
- package/build/adapters/openclaw/session-db.d.ts +55 -0
- package/build/adapters/openclaw/session-db.js +88 -0
- package/build/adapters/types.d.ts +1 -1
- package/build/cli.js +5 -3
- package/build/executor.js +99 -112
- package/build/openclaw/workspace-router.d.ts +29 -0
- package/build/openclaw/workspace-router.js +64 -0
- package/build/openclaw-plugin.d.ts +121 -0
- package/build/openclaw-plugin.js +525 -0
- package/build/server.js +45 -10
- package/build/session/db.d.ts +9 -0
- package/build/session/db.js +38 -0
- package/cli.bundle.mjs +136 -124
- package/configs/antigravity/GEMINI.md +58 -0
- package/configs/antigravity/mcp_config.json +7 -0
- package/configs/kiro/mcp_config.json +7 -0
- package/configs/openclaw/AGENTS.md +58 -0
- package/configs/openclaw/openclaw.json +13 -0
- package/hooks/core/routing.mjs +16 -8
- package/hooks/kiro/posttooluse.mjs +58 -0
- package/hooks/kiro/pretooluse.mjs +63 -0
- package/hooks/posttooluse.mjs +6 -5
- package/hooks/precompact.mjs +5 -4
- package/hooks/session-db.bundle.mjs +57 -0
- package/hooks/session-extract.bundle.mjs +1 -0
- package/hooks/session-helpers.mjs +41 -3
- package/hooks/session-loaders.mjs +28 -0
- package/hooks/session-snapshot.bundle.mjs +14 -0
- package/hooks/sessionstart.mjs +6 -5
- package/hooks/userpromptsubmit.mjs +6 -5
- package/hooks/vscode-copilot/posttooluse.mjs +5 -4
- package/hooks/vscode-copilot/precompact.mjs +5 -4
- package/hooks/vscode-copilot/sessionstart.mjs +5 -4
- package/openclaw.plugin.json +23 -0
- package/package.json +13 -2
- package/server.bundle.mjs +94 -82
- package/start.mjs +1 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClawSessionDB — OpenClaw-specific extension of SessionDB.
|
|
3
|
+
*
|
|
4
|
+
* Adds session_key mapping (openclaw_session_map table) and session
|
|
5
|
+
* rename support needed for OpenClaw's gateway restart re-keying.
|
|
6
|
+
*
|
|
7
|
+
* The shared SessionDB remains unaware of session_key; all OpenClaw-specific
|
|
8
|
+
* session mapping lives here.
|
|
9
|
+
*/
|
|
10
|
+
import { SessionDB } from "../../session/db.js";
|
|
11
|
+
// ─────────────────────────────────────────────────────────
|
|
12
|
+
// OpenClawSessionDB
|
|
13
|
+
// ─────────────────────────────────────────────────────────
|
|
14
|
+
export class OpenClawSessionDB extends SessionDB {
|
|
15
|
+
// ── Schema ──
|
|
16
|
+
initSchema() {
|
|
17
|
+
super.initSchema();
|
|
18
|
+
this.db.exec(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS openclaw_session_map (
|
|
20
|
+
session_key TEXT PRIMARY KEY,
|
|
21
|
+
session_id TEXT NOT NULL,
|
|
22
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
23
|
+
);
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
prepareStatements() {
|
|
27
|
+
super.prepareStatements();
|
|
28
|
+
this.ocStmts = new Map();
|
|
29
|
+
const p = (key, sql) => {
|
|
30
|
+
this.ocStmts.set(key, this.db.prepare(sql));
|
|
31
|
+
};
|
|
32
|
+
p("getMostRecentSession", `SELECT session_id FROM openclaw_session_map WHERE session_key = ?`);
|
|
33
|
+
p("upsertSessionMap", `INSERT INTO openclaw_session_map (session_key, session_id)
|
|
34
|
+
VALUES (?, ?)
|
|
35
|
+
ON CONFLICT(session_key) DO UPDATE SET
|
|
36
|
+
session_id = excluded.session_id`);
|
|
37
|
+
p("deleteSessionMap", `DELETE FROM openclaw_session_map WHERE session_key = ?`);
|
|
38
|
+
p("renameSessionMeta", `UPDATE session_meta SET session_id = ? WHERE session_id = ?`);
|
|
39
|
+
p("renameSessionEvents", `UPDATE session_events SET session_id = ? WHERE session_id = ?`);
|
|
40
|
+
p("renameSessionResume", `UPDATE session_resume SET session_id = ? WHERE session_id = ?`);
|
|
41
|
+
p("renameSessionMap", `UPDATE openclaw_session_map SET session_id = ? WHERE session_id = ?`);
|
|
42
|
+
}
|
|
43
|
+
/** Shorthand to retrieve an OpenClaw-specific cached statement. */
|
|
44
|
+
oc(key) {
|
|
45
|
+
return this.ocStmts.get(key);
|
|
46
|
+
}
|
|
47
|
+
// ═══════════════════════════════════════════
|
|
48
|
+
// Session key mapping
|
|
49
|
+
// ═══════════════════════════════════════════
|
|
50
|
+
/**
|
|
51
|
+
* Ensure a session metadata entry exists with an associated session_key.
|
|
52
|
+
* Calls the parent's 2-param ensureSession and also records the mapping
|
|
53
|
+
* in openclaw_session_map.
|
|
54
|
+
*/
|
|
55
|
+
ensureSessionWithKey(sessionId, projectDir, sessionKey) {
|
|
56
|
+
this.ensureSession(sessionId, projectDir);
|
|
57
|
+
this.oc("upsertSessionMap").run(sessionKey, sessionId);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get the session_id of the most recently mapped session for a given sessionKey.
|
|
61
|
+
* Returns null if no sessions exist for that key.
|
|
62
|
+
*/
|
|
63
|
+
getMostRecentSession(sessionKey) {
|
|
64
|
+
const row = this.oc("getMostRecentSession").get(sessionKey);
|
|
65
|
+
return row?.session_id ?? null;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Rename a session ID in-place across all tables (session_meta, session_events,
|
|
69
|
+
* session_resume, openclaw_session_map), preserving all events, metadata,
|
|
70
|
+
* and resume snapshots. Used when OpenClaw re-keys session IDs on gateway
|
|
71
|
+
* restart so accumulated events survive the re-key.
|
|
72
|
+
*/
|
|
73
|
+
renameSession(oldId, newId) {
|
|
74
|
+
this.db.transaction(() => {
|
|
75
|
+
this.oc("renameSessionMeta").run(newId, oldId);
|
|
76
|
+
this.oc("renameSessionEvents").run(newId, oldId);
|
|
77
|
+
this.oc("renameSessionResume").run(newId, oldId);
|
|
78
|
+
this.oc("renameSessionMap").run(newId, oldId);
|
|
79
|
+
})();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Remove a session_key mapping from openclaw_session_map.
|
|
83
|
+
* Called on command:stop to clean up agent session tracking.
|
|
84
|
+
*/
|
|
85
|
+
removeSessionKey(sessionKey) {
|
|
86
|
+
this.oc("deleteSessionMap").run(sessionKey);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -206,7 +206,7 @@ export interface DiagnosticResult {
|
|
|
206
206
|
fix?: string;
|
|
207
207
|
}
|
|
208
208
|
/** Supported platform identifiers. */
|
|
209
|
-
export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "codex" | "vscode-copilot" | "cursor" | "unknown";
|
|
209
|
+
export type PlatformId = "claude-code" | "gemini-cli" | "opencode" | "openclaw" | "codex" | "vscode-copilot" | "cursor" | "antigravity" | "kiro" | "unknown";
|
|
210
210
|
/** Detection signal used to identify which platform is running. */
|
|
211
211
|
export interface DetectionSignal {
|
|
212
212
|
/** Platform identifier. */
|
package/build/cli.js
CHANGED
|
@@ -97,8 +97,9 @@ export function toUnixPath(p) {
|
|
|
97
97
|
function getPluginRoot() {
|
|
98
98
|
const __filename = fileURLToPath(import.meta.url);
|
|
99
99
|
const __dirname = dirname(__filename);
|
|
100
|
-
// build/cli.js → go up one level; cli.bundle.mjs at project root → stay here
|
|
101
|
-
if (__dirname.endsWith("/build") || __dirname.endsWith("\\build")
|
|
100
|
+
// build/cli.js or src/cli.ts → go up one level; cli.bundle.mjs at project root → stay here
|
|
101
|
+
if (__dirname.endsWith("/build") || __dirname.endsWith("\\build") ||
|
|
102
|
+
__dirname.endsWith("/src") || __dirname.endsWith("\\src")) {
|
|
102
103
|
return resolve(__dirname, "..");
|
|
103
104
|
}
|
|
104
105
|
return __dirname;
|
|
@@ -202,7 +203,8 @@ async function doctor() {
|
|
|
202
203
|
}
|
|
203
204
|
else {
|
|
204
205
|
criticalFails++;
|
|
205
|
-
|
|
206
|
+
const detail = result.stderr?.trim() ? ` (${result.stderr.trim().slice(0, 200)})` : "";
|
|
207
|
+
p.log.error(color.red("Server test: FAIL") + ` — exit ${result.exitCode}${detail}`);
|
|
206
208
|
}
|
|
207
209
|
}
|
|
208
210
|
catch (err) {
|
package/build/executor.js
CHANGED
|
@@ -129,7 +129,7 @@ export class PolyglotExecutor {
|
|
|
129
129
|
try {
|
|
130
130
|
execSync(`rustc ${srcPath} -o ${binPath}`, {
|
|
131
131
|
cwd,
|
|
132
|
-
timeout: Math.min(timeout,
|
|
132
|
+
timeout: Math.min(timeout, 60_000),
|
|
133
133
|
encoding: "utf-8",
|
|
134
134
|
stdio: ["pipe", "pipe", "pipe"],
|
|
135
135
|
});
|
|
@@ -262,126 +262,113 @@ export class PolyglotExecutor {
|
|
|
262
262
|
}
|
|
263
263
|
#buildSafeEnv(tmpDir) {
|
|
264
264
|
const realHome = process.env.HOME ?? process.env.USERPROFILE ?? tmpDir;
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"
|
|
271
|
-
//
|
|
272
|
-
"
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
"
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
"
|
|
284
|
-
//
|
|
285
|
-
"
|
|
286
|
-
"
|
|
287
|
-
"
|
|
288
|
-
//
|
|
289
|
-
"
|
|
290
|
-
"
|
|
291
|
-
|
|
292
|
-
"
|
|
293
|
-
"
|
|
294
|
-
"
|
|
295
|
-
"
|
|
296
|
-
//
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
//
|
|
300
|
-
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
"
|
|
304
|
-
"
|
|
305
|
-
//
|
|
306
|
-
"
|
|
307
|
-
"
|
|
308
|
-
"
|
|
309
|
-
"
|
|
310
|
-
"
|
|
311
|
-
"
|
|
312
|
-
|
|
313
|
-
"
|
|
314
|
-
"
|
|
315
|
-
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
319
|
-
|
|
320
|
-
"
|
|
321
|
-
"
|
|
322
|
-
|
|
323
|
-
"
|
|
324
|
-
"
|
|
325
|
-
|
|
326
|
-
"
|
|
327
|
-
"
|
|
328
|
-
"
|
|
329
|
-
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"
|
|
333
|
-
"
|
|
334
|
-
"
|
|
335
|
-
"
|
|
336
|
-
"
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"
|
|
342
|
-
|
|
343
|
-
];
|
|
344
|
-
const env = {
|
|
345
|
-
PATH: process.env.PATH ?? (isWin ? "" : "/usr/local/bin:/usr/bin:/bin"),
|
|
346
|
-
HOME: realHome,
|
|
347
|
-
TMPDIR: tmpDir,
|
|
348
|
-
LANG: "en_US.UTF-8",
|
|
349
|
-
PYTHONDONTWRITEBYTECODE: "1",
|
|
350
|
-
PYTHONUNBUFFERED: "1",
|
|
351
|
-
PYTHONUTF8: "1",
|
|
352
|
-
NO_COLOR: "1",
|
|
353
|
-
};
|
|
354
|
-
// Windows-critical env vars
|
|
355
|
-
if (isWin) {
|
|
356
|
-
const winVars = [
|
|
357
|
-
"SYSTEMROOT", "SystemRoot", "COMSPEC", "PATHEXT",
|
|
358
|
-
"USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP", "TMP",
|
|
359
|
-
];
|
|
360
|
-
for (const key of winVars) {
|
|
361
|
-
if (process.env[key])
|
|
362
|
-
env[key] = process.env[key];
|
|
265
|
+
// Denylist: env vars that corrupt sandbox stdout, inject code, or break
|
|
266
|
+
// language runtimes. Each entry is backed by CVE, MITRE, or live testing.
|
|
267
|
+
// See: https://www.elttam.com/blog/env/, MITRE T1574.006
|
|
268
|
+
const DENIED = new Set([
|
|
269
|
+
// Shell — auto-execute scripts, override builtins
|
|
270
|
+
"BASH_ENV", // sourced by non-interactive bash
|
|
271
|
+
"ENV", // sourced by sh/dash
|
|
272
|
+
"PROMPT_COMMAND", // runs before each prompt
|
|
273
|
+
"PS4", // $(cmd) expansion in xtrace
|
|
274
|
+
"SHELLOPTS", // enables xtrace/verbose, dumps to stdout
|
|
275
|
+
"BASHOPTS", // bash-specific shell options
|
|
276
|
+
"CDPATH", // makes cd print to stdout
|
|
277
|
+
"INPUTRC", // readline key rebinding
|
|
278
|
+
"BASH_XTRACEFD", // redirects debug output to stdout
|
|
279
|
+
// Node.js — require injection, inspector
|
|
280
|
+
"NODE_OPTIONS", // --require, --loader, --inspect
|
|
281
|
+
"NODE_PATH", // module search path injection
|
|
282
|
+
// Python — stdlib override, startup injection
|
|
283
|
+
"PYTHONSTARTUP", // auto-executes in interactive mode
|
|
284
|
+
"PYTHONHOME", // overrides stdlib location (breaks Python)
|
|
285
|
+
"PYTHONWARNINGS", // triggers module import chain → RCE
|
|
286
|
+
"PYTHONBREAKPOINT", // arbitrary callable
|
|
287
|
+
"PYTHONINSPECT", // enters interactive mode after script
|
|
288
|
+
// Ruby — option/module injection
|
|
289
|
+
"RUBYOPT", // injects CLI options (-r loads files)
|
|
290
|
+
"RUBYLIB", // module search path injection
|
|
291
|
+
// Perl — option/module injection
|
|
292
|
+
"PERL5OPT", // injects CLI options (-M runs code)
|
|
293
|
+
"PERL5LIB", // module search path injection
|
|
294
|
+
"PERLLIB", // legacy module search path
|
|
295
|
+
"PERL5DB", // debugger command injection
|
|
296
|
+
// Elixir/Erlang — eval injection
|
|
297
|
+
"ERL_AFLAGS", // prepends erl flags (-eval runs code)
|
|
298
|
+
"ERL_FLAGS", // appends erl flags
|
|
299
|
+
"ELIXIR_ERL_OPTIONS", // Elixir-specific erl flags
|
|
300
|
+
"ERL_LIBS", // beam file loading
|
|
301
|
+
// Go — compiler/linker injection
|
|
302
|
+
"GOFLAGS", // injects go command flags
|
|
303
|
+
"CGO_CFLAGS", // C compiler flag injection
|
|
304
|
+
"CGO_LDFLAGS", // linker flag injection
|
|
305
|
+
// Rust — compiler substitution
|
|
306
|
+
"RUSTC", // arbitrary compiler binary
|
|
307
|
+
"RUSTC_WRAPPER", // compiler wrapper injection
|
|
308
|
+
"RUSTC_WORKSPACE_WRAPPER",
|
|
309
|
+
"CARGO_BUILD_RUSTC",
|
|
310
|
+
"CARGO_BUILD_RUSTC_WRAPPER",
|
|
311
|
+
"RUSTFLAGS", // compiler flag injection
|
|
312
|
+
// PHP — config injection
|
|
313
|
+
"PHPRC", // auto_prepend_file → RCE
|
|
314
|
+
"PHP_INI_SCAN_DIR", // additional .ini loading
|
|
315
|
+
// R — startup script injection
|
|
316
|
+
"R_PROFILE", // site-wide R profile
|
|
317
|
+
"R_PROFILE_USER", // user R profile
|
|
318
|
+
"R_HOME", // R installation override
|
|
319
|
+
// Dynamic linker — shared library injection
|
|
320
|
+
"LD_PRELOAD", // loads .so before all others (Linux)
|
|
321
|
+
"DYLD_INSERT_LIBRARIES", // macOS equivalent of LD_PRELOAD
|
|
322
|
+
// OpenSSL — engine loading
|
|
323
|
+
"OPENSSL_CONF", // loads engine modules → .so exec
|
|
324
|
+
"OPENSSL_ENGINES", // engine directory override
|
|
325
|
+
// Compiler — binary substitution
|
|
326
|
+
"CC", // C compiler override
|
|
327
|
+
"CXX", // C++ compiler override
|
|
328
|
+
"AR", // archiver override
|
|
329
|
+
// Git — command injection via hooks/config
|
|
330
|
+
"GIT_TEMPLATE_DIR", // hook injection on git init
|
|
331
|
+
"GIT_CONFIG_GLOBAL", // core.pager/editor runs commands
|
|
332
|
+
"GIT_CONFIG_SYSTEM", // system-level config injection
|
|
333
|
+
"GIT_EXEC_PATH", // substitute git subcommands
|
|
334
|
+
"GIT_SSH", // arbitrary command instead of ssh
|
|
335
|
+
"GIT_SSH_COMMAND", // arbitrary ssh command
|
|
336
|
+
"GIT_ASKPASS", // arbitrary credential command
|
|
337
|
+
]);
|
|
338
|
+
// Start with parent env, then strip dangerous vars and apply overrides
|
|
339
|
+
const env = {};
|
|
340
|
+
for (const [key, val] of Object.entries(process.env)) {
|
|
341
|
+
if (val !== undefined && !DENIED.has(key) && !key.startsWith("BASH_FUNC_")) {
|
|
342
|
+
env[key] = val;
|
|
363
343
|
}
|
|
364
|
-
|
|
365
|
-
|
|
344
|
+
}
|
|
345
|
+
// Sandbox overrides — forced values for correct sandbox behavior
|
|
346
|
+
env["TMPDIR"] = tmpDir;
|
|
347
|
+
env["HOME"] = realHome;
|
|
348
|
+
env["LANG"] = "en_US.UTF-8";
|
|
349
|
+
env["PYTHONDONTWRITEBYTECODE"] = "1";
|
|
350
|
+
env["PYTHONUNBUFFERED"] = "1";
|
|
351
|
+
env["PYTHONUTF8"] = "1";
|
|
352
|
+
env["NO_COLOR"] = "1";
|
|
353
|
+
// Windows uses "Path" (not "PATH") — normalize to "PATH" for consistency
|
|
354
|
+
if (isWin && !env["PATH"] && env["Path"]) {
|
|
355
|
+
env["PATH"] = env["Path"];
|
|
356
|
+
delete env["Path"];
|
|
357
|
+
}
|
|
358
|
+
if (!env["PATH"]) {
|
|
359
|
+
env["PATH"] = isWin ? "" : "/usr/local/bin:/usr/bin:/bin";
|
|
360
|
+
}
|
|
361
|
+
// Windows-critical env vars and path fixes
|
|
362
|
+
if (isWin) {
|
|
366
363
|
env["MSYS_NO_PATHCONV"] = "1";
|
|
367
364
|
env["MSYS2_ARG_CONV_EXCL"] = "*";
|
|
368
|
-
// Ensure Git Bash unix tools (cat, ls, head, etc.) are on PATH.
|
|
369
|
-
// The MCP server process may not inherit the full user PATH that
|
|
370
|
-
// includes Git's usr/bin directory.
|
|
371
365
|
const gitUsrBin = "C:\\Program Files\\Git\\usr\\bin";
|
|
372
366
|
const gitBin = "C:\\Program Files\\Git\\bin";
|
|
373
367
|
if (!env["PATH"].includes(gitUsrBin)) {
|
|
374
368
|
env["PATH"] = `${gitUsrBin};${gitBin};${env["PATH"]}`;
|
|
375
369
|
}
|
|
376
370
|
}
|
|
377
|
-
for (const key of passthrough) {
|
|
378
|
-
if (process.env[key]) {
|
|
379
|
-
env[key] = process.env[key];
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
371
|
// Ensure SSL_CERT_FILE is set so Python/Ruby HTTPS works in sandbox.
|
|
383
|
-
// On macOS, it's typically unset (Python uses its own bundle or none),
|
|
384
|
-
// causing urllib/requests to fail with SSL cert verification errors.
|
|
385
372
|
if (!env["SSL_CERT_FILE"]) {
|
|
386
373
|
const certPaths = isWin ? [] : [
|
|
387
374
|
"/etc/ssl/cert.pem", // macOS, some Linux
|
|
@@ -416,7 +403,7 @@ export class PolyglotExecutor {
|
|
|
416
403
|
case "go":
|
|
417
404
|
return `package main\n\nimport (\n\t"fmt"\n\t"os"\n)\n\nvar FILE_CONTENT_PATH = ${escaped}\nvar file_path = FILE_CONTENT_PATH\n\nfunc main() {\n\tb, _ := os.ReadFile(FILE_CONTENT_PATH)\n\tFILE_CONTENT := string(b)\n\t_ = FILE_CONTENT\n\t_ = fmt.Sprint()\n${code}\n}\n`;
|
|
418
405
|
case "rust":
|
|
419
|
-
return
|
|
406
|
+
return `#![allow(unused_variables)]\nuse std::fs;\n\nfn main() {\n let file_content_path = ${escaped};\n let file_path = file_content_path;\n let file_content = fs::read_to_string(file_content_path).unwrap();\n${code}\n}\n`;
|
|
420
407
|
case "php":
|
|
421
408
|
return `<?php\n$FILE_CONTENT_PATH = ${escaped};\n$file_path = $FILE_CONTENT_PATH;\n$FILE_CONTENT = file_get_contents($FILE_CONTENT_PATH);\n${code}`;
|
|
422
409
|
case "perl":
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the agent workspace path from tool call params.
|
|
3
|
+
* Looks for /openclaw/workspace-<name> patterns in cwd, file_path, and command.
|
|
4
|
+
* Returns the workspace root (e.g. "/openclaw/workspace-trainer") or null.
|
|
5
|
+
*/
|
|
6
|
+
export declare function extractWorkspace(params: Record<string, unknown>): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Maps agent workspaces to sessionIds using sessionKey convention.
|
|
9
|
+
* sessionKey pattern: "agent:<name>:main" → workspace "/openclaw/workspace-<name>"
|
|
10
|
+
*
|
|
11
|
+
* Why this exists alongside per-session closures:
|
|
12
|
+
* Each register() call creates its own closure with its own sessionId, which
|
|
13
|
+
* naturally isolates sessions. The WorkspaceRouter acts as a safety net for
|
|
14
|
+
* after_tool_call events where OpenClaw may deliver the event to the wrong
|
|
15
|
+
* closure (e.g. tool calls interleaving across agents). It resolves the correct
|
|
16
|
+
* sessionId from workspace paths in tool params, falling back to the closure
|
|
17
|
+
* sessionId when no workspace is detected.
|
|
18
|
+
*/
|
|
19
|
+
export declare class WorkspaceRouter {
|
|
20
|
+
private map;
|
|
21
|
+
/** Register a session from session_start event. */
|
|
22
|
+
registerSession(sessionKey: string, sessionId: string): void;
|
|
23
|
+
/** Remove a session (e.g. on command:stop). */
|
|
24
|
+
removeSession(sessionKey: string): void;
|
|
25
|
+
/** Resolve sessionId from tool call params. Returns null if no match. */
|
|
26
|
+
resolveSessionId(params: Record<string, unknown>): string | null;
|
|
27
|
+
/** Derive workspace path from sessionKey. */
|
|
28
|
+
private workspaceFromKey;
|
|
29
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the agent workspace path from tool call params.
|
|
3
|
+
* Looks for /openclaw/workspace-<name> patterns in cwd, file_path, and command.
|
|
4
|
+
* Returns the workspace root (e.g. "/openclaw/workspace-trainer") or null.
|
|
5
|
+
*/
|
|
6
|
+
export function extractWorkspace(params) {
|
|
7
|
+
// Priority: cwd > file_path > command (most specific first)
|
|
8
|
+
const sources = [
|
|
9
|
+
params.cwd,
|
|
10
|
+
params.file_path,
|
|
11
|
+
params.command,
|
|
12
|
+
].filter((v) => typeof v === "string");
|
|
13
|
+
for (const src of sources) {
|
|
14
|
+
const match = src.match(/\/openclaw\/workspace-[a-zA-Z0-9_-]+/);
|
|
15
|
+
if (match)
|
|
16
|
+
return match[0];
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Maps agent workspaces to sessionIds using sessionKey convention.
|
|
22
|
+
* sessionKey pattern: "agent:<name>:main" → workspace "/openclaw/workspace-<name>"
|
|
23
|
+
*
|
|
24
|
+
* Why this exists alongside per-session closures:
|
|
25
|
+
* Each register() call creates its own closure with its own sessionId, which
|
|
26
|
+
* naturally isolates sessions. The WorkspaceRouter acts as a safety net for
|
|
27
|
+
* after_tool_call events where OpenClaw may deliver the event to the wrong
|
|
28
|
+
* closure (e.g. tool calls interleaving across agents). It resolves the correct
|
|
29
|
+
* sessionId from workspace paths in tool params, falling back to the closure
|
|
30
|
+
* sessionId when no workspace is detected.
|
|
31
|
+
*/
|
|
32
|
+
export class WorkspaceRouter {
|
|
33
|
+
// workspace path → sessionId
|
|
34
|
+
map = new Map();
|
|
35
|
+
/** Register a session from session_start event. */
|
|
36
|
+
registerSession(sessionKey, sessionId) {
|
|
37
|
+
const workspace = this.workspaceFromKey(sessionKey);
|
|
38
|
+
if (workspace) {
|
|
39
|
+
this.map.set(workspace, sessionId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Remove a session (e.g. on command:stop). */
|
|
43
|
+
removeSession(sessionKey) {
|
|
44
|
+
const workspace = this.workspaceFromKey(sessionKey);
|
|
45
|
+
if (workspace) {
|
|
46
|
+
this.map.delete(workspace);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Resolve sessionId from tool call params. Returns null if no match. */
|
|
50
|
+
resolveSessionId(params) {
|
|
51
|
+
const workspace = extractWorkspace(params);
|
|
52
|
+
if (!workspace)
|
|
53
|
+
return null;
|
|
54
|
+
return this.map.get(workspace) ?? null;
|
|
55
|
+
}
|
|
56
|
+
/** Derive workspace path from sessionKey. */
|
|
57
|
+
workspaceFromKey(key) {
|
|
58
|
+
// Pattern: "agent:<name>:main" or "agent:<name>:<channel>"
|
|
59
|
+
const match = key.match(/^agent:([^:]+):/);
|
|
60
|
+
if (!match)
|
|
61
|
+
return null;
|
|
62
|
+
return `/openclaw/workspace-${match[1]}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw TypeScript plugin entry point for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* Exports an object with { id, name, configSchema, register(api) } for
|
|
5
|
+
* declarative metadata and config validation before code execution.
|
|
6
|
+
*
|
|
7
|
+
* register(api) registers:
|
|
8
|
+
* - before_tool_call hook — Routing enforcement (deny/modify/passthrough)
|
|
9
|
+
* - after_tool_call hook — Session event capture
|
|
10
|
+
* - command:new hook — Session initialization and cleanup
|
|
11
|
+
* - session_start hook — Re-key DB session to OpenClaw's session ID
|
|
12
|
+
* - before_compaction hook — Flush events to resume snapshot
|
|
13
|
+
* - after_compaction hook — Increment compact count
|
|
14
|
+
* - before_prompt_build (p=10) — Resume snapshot injection into system context
|
|
15
|
+
* - before_prompt_build (p=5) — Routing instruction injection into system context
|
|
16
|
+
* - context-mode engine — Context engine with compaction management
|
|
17
|
+
* - /ctx-stats command — Auto-reply command for session statistics
|
|
18
|
+
* - /ctx-doctor command — Auto-reply command for diagnostics
|
|
19
|
+
* - /ctx-upgrade command — Auto-reply command for upgrade
|
|
20
|
+
*
|
|
21
|
+
* Loaded by OpenClaw via: openclaw.extensions entry in package.json
|
|
22
|
+
*
|
|
23
|
+
* OpenClaw plugin paradigm:
|
|
24
|
+
* - Plugins export { id, name, configSchema, register(api) } for metadata
|
|
25
|
+
* - api.registerHook() for event-driven hooks
|
|
26
|
+
* - api.on() for typed lifecycle hooks
|
|
27
|
+
* - api.registerContextEngine() for compaction ownership
|
|
28
|
+
* - api.registerCommand() for auto-reply slash commands
|
|
29
|
+
* - Plugins run in-process with the Gateway (trusted code)
|
|
30
|
+
*/
|
|
31
|
+
/** Context for auto-reply command handlers. */
|
|
32
|
+
interface CommandContext {
|
|
33
|
+
senderId?: string;
|
|
34
|
+
channel?: string;
|
|
35
|
+
isAuthorizedSender?: boolean;
|
|
36
|
+
args?: string;
|
|
37
|
+
commandBody?: string;
|
|
38
|
+
config?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** OpenClaw plugin API provided to the register function. */
|
|
41
|
+
interface OpenClawPluginApi {
|
|
42
|
+
registerHook(event: string, handler: (...args: unknown[]) => unknown, meta: {
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
}): void;
|
|
46
|
+
/**
|
|
47
|
+
* Register a typed lifecycle hook.
|
|
48
|
+
* Supported names: "session_start", "before_compaction", "after_compaction",
|
|
49
|
+
* "before_prompt_build"
|
|
50
|
+
*/
|
|
51
|
+
on(event: string, handler: (...args: unknown[]) => unknown, opts?: {
|
|
52
|
+
priority?: number;
|
|
53
|
+
}): void;
|
|
54
|
+
registerContextEngine(id: string, factory: () => ContextEngineInstance): void;
|
|
55
|
+
registerCommand?(cmd: {
|
|
56
|
+
name: string;
|
|
57
|
+
description: string;
|
|
58
|
+
acceptsArgs?: boolean;
|
|
59
|
+
requireAuth?: boolean;
|
|
60
|
+
handler: (ctx: CommandContext) => {
|
|
61
|
+
text: string;
|
|
62
|
+
} | Promise<{
|
|
63
|
+
text: string;
|
|
64
|
+
}>;
|
|
65
|
+
}): void;
|
|
66
|
+
registerCli?(factory: (ctx: {
|
|
67
|
+
program: unknown;
|
|
68
|
+
}) => void, meta: {
|
|
69
|
+
commands: string[];
|
|
70
|
+
}): void;
|
|
71
|
+
logger?: {
|
|
72
|
+
info: (...args: unknown[]) => void;
|
|
73
|
+
error: (...args: unknown[]) => void;
|
|
74
|
+
debug?: (...args: unknown[]) => void;
|
|
75
|
+
warn?: (...args: unknown[]) => void;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Context engine instance returned by the factory. */
|
|
79
|
+
interface ContextEngineInstance {
|
|
80
|
+
info: {
|
|
81
|
+
id: string;
|
|
82
|
+
name: string;
|
|
83
|
+
ownsCompaction: boolean;
|
|
84
|
+
};
|
|
85
|
+
ingest(data: unknown): Promise<{
|
|
86
|
+
ingested: boolean;
|
|
87
|
+
}>;
|
|
88
|
+
assemble(ctx: {
|
|
89
|
+
messages: unknown[];
|
|
90
|
+
}): Promise<{
|
|
91
|
+
messages: unknown[];
|
|
92
|
+
estimatedTokens: number;
|
|
93
|
+
}>;
|
|
94
|
+
compact(): Promise<{
|
|
95
|
+
ok: boolean;
|
|
96
|
+
compacted: boolean;
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* OpenClaw plugin definition. The object form provides declarative metadata
|
|
101
|
+
* (id, name, configSchema) that OpenClaw can read without executing code.
|
|
102
|
+
* register() is called once per agent session with a fresh api object.
|
|
103
|
+
* Each call creates isolated closures (db, sessionId, hooks) — no shared state.
|
|
104
|
+
*/
|
|
105
|
+
declare const _default: {
|
|
106
|
+
id: string;
|
|
107
|
+
name: string;
|
|
108
|
+
configSchema: {
|
|
109
|
+
type: "object";
|
|
110
|
+
properties: {
|
|
111
|
+
enabled: {
|
|
112
|
+
type: "boolean";
|
|
113
|
+
default: boolean;
|
|
114
|
+
description: string;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
additionalProperties: boolean;
|
|
118
|
+
};
|
|
119
|
+
register(api: OpenClawPluginApi): void;
|
|
120
|
+
};
|
|
121
|
+
export default _default;
|