context-mode 1.0.105 → 1.0.107
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/build/adapters/copilot-base.d.ts +3 -3
- package/build/adapters/cursor/hooks.js +8 -0
- package/build/adapters/cursor/index.js +4 -1
- package/build/adapters/gemini-cli/hooks.d.ts +6 -1
- package/build/adapters/gemini-cli/hooks.js +7 -1
- package/build/adapters/gemini-cli/index.js +12 -0
- package/build/adapters/kiro/hooks.js +4 -0
- package/build/adapters/kiro/index.d.ts +9 -2
- package/build/adapters/kiro/index.js +49 -27
- package/build/adapters/opencode/index.js +6 -0
- package/build/adapters/qwen-code/index.js +18 -0
- package/build/adapters/vscode-copilot/hooks.d.ts +0 -4
- package/build/adapters/vscode-copilot/hooks.js +6 -6
- package/build/cli.js +1 -0
- package/build/openclaw/mcp-tools.d.ts +54 -0
- package/build/openclaw/mcp-tools.js +198 -0
- package/build/openclaw-plugin.d.ts +9 -0
- package/build/openclaw-plugin.js +132 -16
- package/build/opencode-plugin.d.ts +29 -4
- package/build/opencode-plugin.js +185 -11
- package/build/pi-extension.js +123 -29
- package/build/server.d.ts +1 -0
- package/build/server.js +28 -2
- package/build/session/db.d.ts +12 -3
- package/build/session/db.js +19 -4
- package/build/session/extract.d.ts +1 -1
- package/build/session/extract.js +46 -1
- package/cli.bundle.mjs +128 -127
- package/hooks/core/platform-detect.mjs +49 -0
- package/hooks/core/routing.mjs +13 -1
- package/hooks/cursor/afteragentresponse.mjs +74 -0
- package/hooks/gemini-cli/beforeagent.mjs +99 -0
- package/hooks/kiro/agentspawn.mjs +97 -0
- package/hooks/kiro/userpromptsubmit.mjs +88 -0
- package/hooks/session-db.bundle.mjs +4 -3
- package/hooks/session-extract.bundle.mjs +2 -2
- package/hooks/sessionstart.mjs +3 -1
- package/hooks/vscode-copilot/sessionstart.mjs +13 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/server.bundle.mjs +72 -71
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw MCP tool registry.
|
|
3
|
+
*
|
|
4
|
+
* Catalogs the 11 ctx_* tools that OpenClaw plugin must register via
|
|
5
|
+
* api.registerTool(...) so the routing block (which nudges agents toward
|
|
6
|
+
* ctx_execute, ctx_search, etc.) actually has tools to call. Without this,
|
|
7
|
+
* Phase 7 audit (v1.0.107-adapter-openclaw.json) flagged severity=CRITICAL —
|
|
8
|
+
* routing-block premise is broken when the named tools don't exist.
|
|
9
|
+
*
|
|
10
|
+
* Pattern mirrors the swarmvault MCP plugin
|
|
11
|
+
* (refs/plugin-examples/openclaw/swarmvault/packages/engine/src/mcp.ts:46-51):
|
|
12
|
+
* server.registerTool(name, { description, inputSchema }, handler)
|
|
13
|
+
*
|
|
14
|
+
* OpenClaw signature is slightly different — see building-plugins.md:116
|
|
15
|
+
* api.registerTool({ name, description, parameters: TypeBox, execute(id, params) })
|
|
16
|
+
*
|
|
17
|
+
* Tool handlers are intentionally thin shims that delegate to the bundled CLI
|
|
18
|
+
* (cli.bundle.mjs) — same fall-through pattern already used by ctx-doctor and
|
|
19
|
+
* ctx-upgrade slash commands. This keeps the plugin's blast radius minimal:
|
|
20
|
+
* we don't re-export the entire MCP server stack inside OpenClaw's process.
|
|
21
|
+
*
|
|
22
|
+
* The 11 tools mirror src/server.ts registerTool calls (lines 897, 1226, 1371,
|
|
23
|
+
* 1497, 2034, 2256, 2440, 2501, 2592, 2712, 2808).
|
|
24
|
+
*/
|
|
25
|
+
/** Wrap any handler so failures become a well-formed text error rather than crashing. */
|
|
26
|
+
function safe(handler) {
|
|
27
|
+
return async (_id, params) => {
|
|
28
|
+
try {
|
|
29
|
+
return await handler(params ?? {});
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: "text",
|
|
37
|
+
text: `[context-mode] tool error: ${message}`,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Stub handler — points users at the bundled CLI for full functionality. */
|
|
45
|
+
function cliRedirect(toolName) {
|
|
46
|
+
return safe(async () => ({
|
|
47
|
+
content: [
|
|
48
|
+
{
|
|
49
|
+
type: "text",
|
|
50
|
+
text: `[context-mode] ${toolName} is exposed via the bundled context-mode CLI. Run 'context-mode ${toolName}' or invoke the MCP server directly. This OpenClaw stub registers the tool name so the routing block remains valid; full execution requires the standalone MCP transport.`,
|
|
51
|
+
},
|
|
52
|
+
],
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The 11 ctx_* tool definitions registered into OpenClaw via api.registerTool.
|
|
57
|
+
* Names + descriptions mirror src/server.ts registerTool blocks 1:1 so prompts
|
|
58
|
+
* referencing them (routing block, AGENTS.md) resolve to real callable tools.
|
|
59
|
+
*/
|
|
60
|
+
export const OPENCLAW_TOOL_DEFS = [
|
|
61
|
+
{
|
|
62
|
+
name: "ctx_execute",
|
|
63
|
+
description: "Execute code in a sandboxed subprocess. Only stdout enters context. Prefer over Bash for any command producing >20 lines.",
|
|
64
|
+
parameters: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
language: { type: "string", description: "Runtime language" },
|
|
68
|
+
code: { type: "string", description: "Source code to execute" },
|
|
69
|
+
timeout: { type: "number", description: "Max execution time in ms" },
|
|
70
|
+
},
|
|
71
|
+
required: ["language", "code"],
|
|
72
|
+
additionalProperties: true,
|
|
73
|
+
},
|
|
74
|
+
execute: cliRedirect("ctx_execute"),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "ctx_execute_file",
|
|
78
|
+
description: "Execute code with a file path. Only printed summary enters context — raw file stays in sandbox.",
|
|
79
|
+
parameters: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
path: { type: "string", description: "File path" },
|
|
83
|
+
language: { type: "string", description: "Runtime language" },
|
|
84
|
+
code: { type: "string", description: "Source code" },
|
|
85
|
+
},
|
|
86
|
+
required: ["path", "language", "code"],
|
|
87
|
+
additionalProperties: true,
|
|
88
|
+
},
|
|
89
|
+
execute: cliRedirect("ctx_execute_file"),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "ctx_index",
|
|
93
|
+
description: "Store content in the FTS5 knowledge base for later search.",
|
|
94
|
+
parameters: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
content: { type: "string", description: "Text to index" },
|
|
98
|
+
source: { type: "string", description: "Descriptive source label" },
|
|
99
|
+
},
|
|
100
|
+
required: ["content", "source"],
|
|
101
|
+
additionalProperties: true,
|
|
102
|
+
},
|
|
103
|
+
execute: cliRedirect("ctx_index"),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "ctx_search",
|
|
107
|
+
description: "Query indexed content via FTS5. Pass all questions as an array in ONE call.",
|
|
108
|
+
parameters: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
queries: { type: "array", description: "Search queries" },
|
|
112
|
+
source: { type: "string", description: "Optional source filter" },
|
|
113
|
+
sort: { type: "string", description: "relevance | timeline" },
|
|
114
|
+
},
|
|
115
|
+
additionalProperties: true,
|
|
116
|
+
},
|
|
117
|
+
execute: cliRedirect("ctx_search"),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "ctx_fetch_and_index",
|
|
121
|
+
description: "Fetch a URL, chunk it, and index — raw HTML never enters context.",
|
|
122
|
+
parameters: {
|
|
123
|
+
type: "object",
|
|
124
|
+
properties: {
|
|
125
|
+
url: { type: "string", description: "URL to fetch" },
|
|
126
|
+
source: { type: "string", description: "Source label for indexed chunks" },
|
|
127
|
+
},
|
|
128
|
+
required: ["url"],
|
|
129
|
+
additionalProperties: true,
|
|
130
|
+
},
|
|
131
|
+
execute: cliRedirect("ctx_fetch_and_index"),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "ctx_batch_execute",
|
|
135
|
+
description: "Run multiple commands and search queries in ONE call. Primary research tool — replaces 30+ individual calls.",
|
|
136
|
+
parameters: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
commands: { type: "array", description: "Array of {label, command} objects" },
|
|
140
|
+
queries: { type: "array", description: "Search queries to run after indexing" },
|
|
141
|
+
},
|
|
142
|
+
additionalProperties: true,
|
|
143
|
+
},
|
|
144
|
+
execute: cliRedirect("ctx_batch_execute"),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "ctx_stats",
|
|
148
|
+
description: "Show context-mode session statistics — token consumption and per-tool breakdown.",
|
|
149
|
+
parameters: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {},
|
|
152
|
+
additionalProperties: true,
|
|
153
|
+
},
|
|
154
|
+
execute: cliRedirect("ctx_stats"),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "ctx_doctor",
|
|
158
|
+
description: "Run context-mode diagnostics — runtimes, hooks, FTS5, plugin registration.",
|
|
159
|
+
parameters: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {},
|
|
162
|
+
additionalProperties: true,
|
|
163
|
+
},
|
|
164
|
+
execute: cliRedirect("ctx_doctor"),
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: "ctx_upgrade",
|
|
168
|
+
description: "Upgrade context-mode to the latest version.",
|
|
169
|
+
parameters: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {},
|
|
172
|
+
additionalProperties: true,
|
|
173
|
+
},
|
|
174
|
+
execute: cliRedirect("ctx_upgrade"),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "ctx_purge",
|
|
178
|
+
description: "Permanently delete all indexed content and reset session stats. Destructive.",
|
|
179
|
+
parameters: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {},
|
|
182
|
+
additionalProperties: true,
|
|
183
|
+
},
|
|
184
|
+
execute: cliRedirect("ctx_purge"),
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "ctx_insight",
|
|
188
|
+
description: "Open the context-mode Insight analytics dashboard in the browser.",
|
|
189
|
+
parameters: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {},
|
|
192
|
+
additionalProperties: true,
|
|
193
|
+
},
|
|
194
|
+
execute: cliRedirect("ctx_insight"),
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
/** Stable list of tool names — used by tests and manifest validation. */
|
|
198
|
+
export const OPENCLAW_TOOL_NAMES = OPENCLAW_TOOL_DEFS.map((def) => def.name);
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* - api.registerCommand() for auto-reply slash commands
|
|
29
29
|
* - Plugins run in-process with the Gateway (trusted code)
|
|
30
30
|
*/
|
|
31
|
+
import type { OpenClawToolDef } from "./openclaw/mcp-tools.js";
|
|
31
32
|
/** Context for auto-reply command handlers. */
|
|
32
33
|
interface CommandContext {
|
|
33
34
|
senderId?: string;
|
|
@@ -68,6 +69,14 @@ interface OpenClawPluginApi {
|
|
|
68
69
|
}) => void, meta: {
|
|
69
70
|
commands: string[];
|
|
70
71
|
}): void;
|
|
72
|
+
/**
|
|
73
|
+
* Register an agent tool (OpenClaw native registerTool) — see
|
|
74
|
+
* refs/platforms/openclaw/docs/plugins/building-plugins.md:116. Optional in
|
|
75
|
+
* the type so we degrade silently on legacy hosts that pre-date this API.
|
|
76
|
+
*/
|
|
77
|
+
registerTool?(tool: OpenClawToolDef, opts?: {
|
|
78
|
+
optional?: boolean;
|
|
79
|
+
}): void;
|
|
71
80
|
logger?: {
|
|
72
81
|
info: (...args: unknown[]) => void;
|
|
73
82
|
error: (...args: unknown[]) => void;
|
package/build/openclaw-plugin.js
CHANGED
|
@@ -38,6 +38,24 @@ import { extractEvents, extractUserEvents } from "./session/extract.js";
|
|
|
38
38
|
import { buildResumeSnapshot } from "./session/snapshot.js";
|
|
39
39
|
import { WorkspaceRouter } from "./openclaw/workspace-router.js";
|
|
40
40
|
import { buildNodeCommand } from "./adapters/types.js";
|
|
41
|
+
import { OPENCLAW_TOOL_DEFS } from "./openclaw/mcp-tools.js";
|
|
42
|
+
// ── System-reminder filter (CCv2 — SLICE OClaw-3) ─────────
|
|
43
|
+
// Mirror hooks/userpromptsubmit.mjs:30-33: skip system-generated wrappers
|
|
44
|
+
// so before_model_resolve never inserts spurious user-prompt events.
|
|
45
|
+
const SYSTEM_REMINDER_PREFIXES = [
|
|
46
|
+
"<system-reminder>",
|
|
47
|
+
"<task-notification>",
|
|
48
|
+
"<context_guidance>",
|
|
49
|
+
"<tool-result>",
|
|
50
|
+
];
|
|
51
|
+
function isSystemReminderMessage(msg) {
|
|
52
|
+
const trimmed = msg.trimStart();
|
|
53
|
+
for (const prefix of SYSTEM_REMINDER_PREFIXES) {
|
|
54
|
+
if (trimmed.startsWith(prefix))
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
41
59
|
/** Plugin config schema for OpenClaw validation. */
|
|
42
60
|
const configSchema = {
|
|
43
61
|
type: "object",
|
|
@@ -114,27 +132,46 @@ export default {
|
|
|
114
132
|
// Start with temp UUID — session_start will assign the real ID + sessionKey
|
|
115
133
|
let sessionId = randomUUID();
|
|
116
134
|
log.info("register() called, sessionId:", sessionId.slice(0, 8));
|
|
135
|
+
// SLICE OClaw-6 (F6 retraction): `resumeInjected` is correctly scoped
|
|
136
|
+
// per-register() singleton — Phase 7 confirmed F6 fabrication-as-tech-debt.
|
|
137
|
+
// Each OpenClaw agent session calls register() once and gets its own
|
|
138
|
+
// closure; the flag prevents double-injection of the resume snapshot in
|
|
139
|
+
// back-to-back before_prompt_build calls within the same session. Do not
|
|
140
|
+
// promote to module scope.
|
|
117
141
|
let resumeInjected = false;
|
|
118
142
|
let sessionKey;
|
|
119
143
|
// Create temp session so after_tool_call events before session_start have a valid row
|
|
120
144
|
db.ensureSession(sessionId, projectDir);
|
|
121
145
|
const workspaceRouter = new WorkspaceRouter();
|
|
122
|
-
//
|
|
146
|
+
// Async init: load routing module + dynamic routing-block factory.
|
|
147
|
+
// SLICE OClaw-2: replaced static readFileSync(configs/openclaw/AGENTS.md)
|
|
148
|
+
// with createRoutingBlock(createToolNamer("openclaw")) so OpenClaw-specific
|
|
149
|
+
// MCP-prefix substitution stays in lockstep with hooks/routing-block.mjs.
|
|
123
150
|
let routingInstructions = "";
|
|
124
|
-
try {
|
|
125
|
-
const instructionsPath = resolve(buildDir, "..", "configs", "openclaw", "AGENTS.md");
|
|
126
|
-
if (existsSync(instructionsPath)) {
|
|
127
|
-
routingInstructions = readFileSync(instructionsPath, "utf-8");
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
// best effort
|
|
132
|
-
}
|
|
133
|
-
// Async init: load routing module. Hooks await this.
|
|
134
151
|
const initPromise = (async () => {
|
|
135
152
|
const routingPath = resolve(buildDir, "..", "hooks", "core", "routing.mjs");
|
|
136
153
|
const routing = await import(pathToFileURL(routingPath).href);
|
|
137
154
|
await routing.initSecurity(buildDir);
|
|
155
|
+
try {
|
|
156
|
+
const blockMod = await import(pathToFileURL(resolve(buildDir, "..", "hooks", "routing-block.mjs")).href);
|
|
157
|
+
const namingMod = await import(pathToFileURL(resolve(buildDir, "..", "hooks", "core", "tool-naming.mjs")).href);
|
|
158
|
+
const toolNamer = namingMod.createToolNamer("openclaw");
|
|
159
|
+
routingInstructions = blockMod.createRoutingBlock(toolNamer);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
log.warn?.("failed to build dynamic routing block", err);
|
|
163
|
+
// Fallback: legacy disk-read of AGENTS.md (kept for resilience only —
|
|
164
|
+
// primary path is the dynamic factory above).
|
|
165
|
+
try {
|
|
166
|
+
const instructionsPath = resolve(buildDir, "..", "configs", "openclaw", "AGENTS.md");
|
|
167
|
+
if (existsSync(instructionsPath)) {
|
|
168
|
+
routingInstructions = readFileSync(instructionsPath, "utf-8");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// best effort
|
|
173
|
+
}
|
|
174
|
+
}
|
|
138
175
|
return { routing };
|
|
139
176
|
})();
|
|
140
177
|
// ── 1. tool_call:before — Routing enforcement ──────────
|
|
@@ -359,6 +396,12 @@ export default {
|
|
|
359
396
|
log.debug("before_model_resolve", { hasMessage: !!messageText });
|
|
360
397
|
if (!messageText)
|
|
361
398
|
return;
|
|
399
|
+
// SLICE OClaw-3: skip system-generated wrappers so we never
|
|
400
|
+
// misclassify them as user prompts. Mirrors hooks/userpromptsubmit.mjs:30-33.
|
|
401
|
+
if (isSystemReminderMessage(messageText)) {
|
|
402
|
+
log.debug("before_model_resolve[skip-system-reminder]");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
362
405
|
const events = extractUserEvents(messageText);
|
|
363
406
|
for (const ev of events) {
|
|
364
407
|
db.insertEvent(sid, ev, "PostToolUse");
|
|
@@ -389,12 +432,85 @@ export default {
|
|
|
389
432
|
}
|
|
390
433
|
}, { priority: 10 });
|
|
391
434
|
// ── 8. before_prompt_build — Routing instruction injection ──
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
435
|
+
// SLICE OClaw-2: register unconditionally; routingInstructions is populated
|
|
436
|
+
// asynchronously by initPromise. The closure resolves the latest value at
|
|
437
|
+
// call-time, so the first prompt-build firing after dynamic-import resolution
|
|
438
|
+
// sees the dynamic ROUTING_BLOCK XML (matching hooks/routing-block.mjs).
|
|
439
|
+
api.on("before_prompt_build", () => {
|
|
440
|
+
if (!routingInstructions)
|
|
441
|
+
return undefined;
|
|
442
|
+
log.debug("before_prompt_build[routing]", { hasInstructions: !!routingInstructions });
|
|
443
|
+
// v1.0.107 — visible marker so OpenClaw users can verify the routing
|
|
444
|
+
// block reached the model (Mickey-class verification path; mirrors
|
|
445
|
+
// OpenCode + Pi adapters).
|
|
446
|
+
const marker = `<!-- context-mode: routing block injected (sessionID=${String(sessionId).slice(0, 8)}) -->`;
|
|
447
|
+
return { appendSystemContext: marker + "\n" + routingInstructions };
|
|
448
|
+
}, { priority: 5 });
|
|
449
|
+
// ── 8b. registerTool — Expose 11 ctx_* tools (SLICE OClaw-1) ────
|
|
450
|
+
// Phase 7 audit (v1.0.107-adapter-openclaw.json) flagged severity=CRITICAL:
|
|
451
|
+
// routing block tells agents to call ctx_execute / ctx_search / etc. but
|
|
452
|
+
// nothing called api.registerTool, so the tools didn't exist in the
|
|
453
|
+
// OpenClaw session. This loop fixes that — mirrors swarmvault MCP pattern
|
|
454
|
+
// (refs/plugin-examples/openclaw/swarmvault/packages/engine/src/mcp.ts:46-51).
|
|
455
|
+
if (api.registerTool) {
|
|
456
|
+
for (const def of OPENCLAW_TOOL_DEFS) {
|
|
457
|
+
try {
|
|
458
|
+
api.registerTool(def);
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
log.warn?.("registerTool failed", { name: def.name }, err);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
log.debug("registerTool[ctx_*]", { count: OPENCLAW_TOOL_DEFS.length });
|
|
397
465
|
}
|
|
466
|
+
else {
|
|
467
|
+
log.warn?.("api.registerTool unavailable — ctx_* tools not exposed in this OpenClaw build");
|
|
468
|
+
}
|
|
469
|
+
// ── 8c. session_end — Finalize resume snapshot (SLICE OClaw-4) ───
|
|
470
|
+
// OpenClaw fires session_end at session lifecycle boundaries (per
|
|
471
|
+
// refs/platforms/openclaw/docs/plugins/hooks.md:110). We persist a final
|
|
472
|
+
// resume snapshot so a future session_start with resumedFrom can re-attach.
|
|
473
|
+
api.on("session_end", async () => {
|
|
474
|
+
try {
|
|
475
|
+
const sid = sessionId;
|
|
476
|
+
const allEvents = db.getEvents(sid);
|
|
477
|
+
log.debug("session_end", { sessionId: sid.slice(0, 8), events: allEvents.length });
|
|
478
|
+
if (allEvents.length === 0)
|
|
479
|
+
return;
|
|
480
|
+
const freshStats = db.getSessionStats(sid);
|
|
481
|
+
const snapshot = buildResumeSnapshot(allEvents, {
|
|
482
|
+
compactCount: freshStats?.compact_count ?? 0,
|
|
483
|
+
});
|
|
484
|
+
db.upsertResume(sid, snapshot, allEvents.length);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// best effort — never break session shutdown
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
// ── 8d. subagent_spawning — Inject routing block (SLICE OClaw-5) ─
|
|
491
|
+
// OpenClaw's subagent lifecycle (hooks.md:116) gives us a chance to seed
|
|
492
|
+
// every spawned subagent with the same routing block the parent agent
|
|
493
|
+
// sees. Without this, subagents have no MCP-routing guidance and degrade
|
|
494
|
+
// back to flooding the context with raw tool output.
|
|
495
|
+
api.on("subagent_spawning", (event) => {
|
|
496
|
+
try {
|
|
497
|
+
const e = (event ?? {});
|
|
498
|
+
const basePrompt = e?.input?.prompt ?? "";
|
|
499
|
+
if (!routingInstructions)
|
|
500
|
+
return undefined;
|
|
501
|
+
const newPrompt = basePrompt
|
|
502
|
+
? `${basePrompt}\n\n${routingInstructions}`
|
|
503
|
+
: routingInstructions;
|
|
504
|
+
log.debug("subagent_spawning[inject-routing]", {
|
|
505
|
+
basePromptLen: basePrompt.length,
|
|
506
|
+
blockLen: routingInstructions.length,
|
|
507
|
+
});
|
|
508
|
+
return { inputOverride: { ...(e.input ?? {}), prompt: newPrompt } };
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return undefined;
|
|
512
|
+
}
|
|
513
|
+
});
|
|
398
514
|
// ── 9. Context engine — Compaction management ──────────
|
|
399
515
|
api.registerContextEngine("context-mode", () => ({
|
|
400
516
|
info: {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenCode / KiloCode TypeScript plugin entry point for context-mode.
|
|
3
3
|
*
|
|
4
|
-
* Provides
|
|
4
|
+
* Provides five hooks (v1.0.107 — Mickey OC-1..OC-4 follow-up):
|
|
5
5
|
* - tool.execute.before — Routing enforcement (deny/modify/passthrough)
|
|
6
|
-
* - tool.execute.after — Session event capture
|
|
7
|
-
* - experimental.session.compacting — Compaction snapshot
|
|
6
|
+
* - tool.execute.after — Session event capture + first-fire AGENTS.md scan (OC-4)
|
|
7
|
+
* - experimental.session.compacting — Compaction snapshot + budget-capped auto-injection (OC-3)
|
|
8
|
+
* - experimental.chat.system.transform — ROUTING_BLOCK + resume snapshot injection (OC-1)
|
|
9
|
+
* - chat.message — User-prompt capture w/ CCv2 inline filter (OC-2)
|
|
8
10
|
*
|
|
9
11
|
* KiloCode loads this via: import("context-mode") → expects default export
|
|
10
12
|
* with shape { server: (input) => Promise<Hooks> } (PluginModule).
|
|
@@ -14,7 +16,7 @@
|
|
|
14
16
|
*
|
|
15
17
|
* Constraints:
|
|
16
18
|
* - No SessionStart hook (OpenCode doesn't support it — #14808, #5409)
|
|
17
|
-
* -
|
|
19
|
+
* - context injection now via chat.system.transform surrogate (OC-1)
|
|
18
20
|
* - No routing file auto-write (avoid dirtying project trees)
|
|
19
21
|
* - Session cleanup happens at plugin init (no SessionStart)
|
|
20
22
|
*/
|
|
@@ -77,6 +79,28 @@ interface SystemTransformHookInput {
|
|
|
77
79
|
interface SystemTransformHookOutput {
|
|
78
80
|
system: string[];
|
|
79
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* OpenCode chat.message hook — verified against
|
|
84
|
+
* refs/platforms/opencode/packages/plugin/src/index.ts:233.
|
|
85
|
+
* input: { sessionID; agent?; model?; messageID?; variant? }
|
|
86
|
+
* output: { message: UserMessage; parts: Part[] }
|
|
87
|
+
* We read text from `parts[*].text` (the orchestrator reference at
|
|
88
|
+
* refs/plugin-examples/opencode/opencode-orchestrator/src/plugin-handlers/
|
|
89
|
+
* chat-message-handler.ts:41-65 uses the same pattern).
|
|
90
|
+
*/
|
|
91
|
+
interface ChatMessageHookInput {
|
|
92
|
+
sessionID: string;
|
|
93
|
+
agent?: string;
|
|
94
|
+
messageID?: string;
|
|
95
|
+
}
|
|
96
|
+
interface ChatMessagePart {
|
|
97
|
+
type: string;
|
|
98
|
+
text?: string;
|
|
99
|
+
}
|
|
100
|
+
interface ChatMessageHookOutput {
|
|
101
|
+
message: unknown;
|
|
102
|
+
parts: ChatMessagePart[];
|
|
103
|
+
}
|
|
80
104
|
/**
|
|
81
105
|
* Plugin factory. Called once when KiloCode/OpenCode loads the plugin.
|
|
82
106
|
* Returns an object mapping hook event names to async handler functions.
|
|
@@ -87,6 +111,7 @@ interface SystemTransformHookOutput {
|
|
|
87
111
|
declare function createContextModePlugin(ctx: PluginContext): Promise<{
|
|
88
112
|
"tool.execute.before": (input: BeforeHookInput, output: BeforeHookOutput) => Promise<void>;
|
|
89
113
|
"tool.execute.after": (input: AfterHookInput, output: AfterHookOutput) => Promise<void>;
|
|
114
|
+
"chat.message": (input: ChatMessageHookInput, output: ChatMessageHookOutput) => Promise<void>;
|
|
90
115
|
"experimental.session.compacting": (input: CompactingHookInput, output: CompactingHookOutput) => Promise<string>;
|
|
91
116
|
"experimental.chat.system.transform": (input: SystemTransformHookInput, output: SystemTransformHookOutput) => Promise<void>;
|
|
92
117
|
}>;
|