context-mode 1.0.135 → 1.0.137

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.
Files changed (51) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/.codex-plugin/hooks.json +65 -0
  4. package/.codex-plugin/mcp.json +9 -0
  5. package/.codex-plugin/plugin.json +31 -0
  6. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  7. package/.openclaw-plugin/package.json +1 -1
  8. package/README.md +53 -24
  9. package/build/adapters/codex/index.js +24 -3
  10. package/build/adapters/opencode/index.d.ts +1 -0
  11. package/build/adapters/opencode/index.js +25 -0
  12. package/build/adapters/opencode/plugin.d.ts +22 -0
  13. package/build/adapters/opencode/plugin.js +52 -0
  14. package/build/adapters/pi/extension.js +20 -4
  15. package/build/adapters/pi/mcp-bridge.d.ts +39 -2
  16. package/build/adapters/pi/mcp-bridge.js +184 -24
  17. package/build/lifecycle.d.ts +2 -51
  18. package/build/lifecycle.js +3 -67
  19. package/build/server.d.ts +19 -0
  20. package/build/server.js +141 -58
  21. package/build/session/db.d.ts +6 -0
  22. package/build/session/db.js +17 -3
  23. package/build/session/extract.js +39 -1
  24. package/build/util/sibling-mcp.d.ts +0 -40
  25. package/build/util/sibling-mcp.js +11 -116
  26. package/cli.bundle.mjs +131 -129
  27. package/configs/kilo/kilo.json +3 -7
  28. package/configs/opencode/opencode.json +3 -7
  29. package/hooks/codex/platform.mjs +1 -0
  30. package/hooks/codex/posttooluse.mjs +1 -0
  31. package/hooks/codex/precompact.mjs +1 -0
  32. package/hooks/codex/pretooluse.mjs +1 -0
  33. package/hooks/codex/sessionstart.mjs +1 -0
  34. package/hooks/codex/stop.mjs +1 -0
  35. package/hooks/codex/userpromptsubmit.mjs +1 -0
  36. package/hooks/core/routing.mjs +112 -10
  37. package/hooks/ensure-deps.mjs +14 -3
  38. package/hooks/normalize-hooks.mjs +101 -19
  39. package/hooks/session-db.bundle.mjs +3 -3
  40. package/hooks/session-extract.bundle.mjs +2 -2
  41. package/openclaw.plugin.json +1 -1
  42. package/package.json +2 -1
  43. package/server.bundle.mjs +112 -110
  44. package/build/openclaw-plugin.d.ts +0 -130
  45. package/build/openclaw-plugin.js +0 -626
  46. package/build/opencode-plugin.d.ts +0 -122
  47. package/build/opencode-plugin.js +0 -372
  48. package/build/pi-extension.d.ts +0 -14
  49. package/build/pi-extension.js +0 -451
  50. package/build/util/db-lock.d.ts +0 -65
  51. package/build/util/db-lock.js +0 -166
@@ -96,6 +96,87 @@ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
96
96
  // Tools/call may run shell commands or fetch URLs — wider window than
97
97
  // initialize/list, but still bounded so a hung server can't block Pi.
98
98
  const DEFAULT_CALL_TIMEOUT_MS = 120_000;
99
+ class PiTextComponent {
100
+ text;
101
+ constructor(text = "") {
102
+ this.text = text;
103
+ }
104
+ setText(text) {
105
+ this.text = text;
106
+ }
107
+ invalidate() {
108
+ // Stateless renderer: no cached layout to invalidate.
109
+ }
110
+ render(width) {
111
+ if (!this.text || this.text.trim() === "")
112
+ return [];
113
+ return this.text
114
+ .replace(/\t/g, " ")
115
+ .split(/\r?\n/)
116
+ .map((line) => truncateAnsiLine(line, Math.max(1, width)));
117
+ }
118
+ }
119
+ const ANSI_PATTERN = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07]*(?:\x07|\x1b\\)/g;
120
+ function truncateAnsiLine(line, maxWidth) {
121
+ if (maxWidth <= 0)
122
+ return "";
123
+ let output = "";
124
+ let visible = 0;
125
+ let index = 0;
126
+ ANSI_PATTERN.lastIndex = 0;
127
+ for (;;) {
128
+ const match = ANSI_PATTERN.exec(line);
129
+ const end = match?.index ?? line.length;
130
+ const chunk = line.slice(index, end);
131
+ for (const char of chunk) {
132
+ if (visible >= maxWidth)
133
+ return output;
134
+ output += char;
135
+ visible++;
136
+ }
137
+ if (!match)
138
+ return output;
139
+ output += match[0];
140
+ index = ANSI_PATTERN.lastIndex;
141
+ }
142
+ }
143
+ function createContextModeCallRenderer(toolName) {
144
+ return (_args, theme, context) => {
145
+ const text = context.lastComponent instanceof PiTextComponent
146
+ ? context.lastComponent
147
+ : new PiTextComponent();
148
+ text.setText(theme.fg("toolTitle", theme.bold(toolName)));
149
+ return text;
150
+ };
151
+ }
152
+ function createContextModeResultRenderer(toolName) {
153
+ return (result, { expanded, isPartial }, theme, context) => {
154
+ const text = context.lastComponent instanceof PiTextComponent
155
+ ? context.lastComponent
156
+ : new PiTextComponent();
157
+ if (isPartial) {
158
+ text.setText(theme.fg("warning", "indexing/searching..."));
159
+ return text;
160
+ }
161
+ const output = (result.content ?? [])
162
+ .filter((c) => c?.type === "text" && typeof c.text === "string")
163
+ .map((c) => c.text)
164
+ .join("\n");
165
+ if (expanded) {
166
+ text.setText(theme.fg("toolOutput", output));
167
+ return text;
168
+ }
169
+ const firstLine = output
170
+ .split(/\r?\n/)
171
+ .find((line) => line.trim().length > 0)
172
+ ?.trim();
173
+ const status = firstLine && firstLine.length <= 180
174
+ ? firstLine
175
+ : `${toolName} completed`;
176
+ text.setText(theme.fg("toolOutput", status));
177
+ return text;
178
+ };
179
+ }
99
180
  /**
100
181
  * Minimal stdio JSON-RPC client targeting the context-mode MCP server.
101
182
  *
@@ -118,6 +199,15 @@ export class MCPStdioClient {
118
199
  buffer = "";
119
200
  initialized = false;
120
201
  exited = false;
202
+ /**
203
+ * In-flight respawn promise — set while {@link respawn} runs so
204
+ * concurrent callers awaiting `request()` after an idle exit observe
205
+ * the SAME respawn, not N parallel ones. Without this guard, two
206
+ * simultaneous `callTool` calls would each see `this.exited === true`,
207
+ * each fire their own `respawn()`, and the loser leaks an orphaned
208
+ * child process the GC cannot reach (no `.kill()` reference).
209
+ */
210
+ respawnPromise = null;
121
211
  /**
122
212
  * Live env passed to the spawned child — exposed (read-only intent)
123
213
  * so tests can pin the fork-bomb-prevention env counter (#516)
@@ -232,11 +322,31 @@ export class MCPStdioClient {
232
322
  handler.resolve(msg.result);
233
323
  }
234
324
  }
235
- request(method, params, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
325
+ async request(method, params, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
326
+ // Respawn-on-idle-exit (#583, #583-followup).
327
+ //
328
+ // Initial #583 fix patched callTool() only. The structural location is
329
+ // here: `request()` is the single chokepoint for `initialize`,
330
+ // `tools/list`, `tools/call`, and any future method. Patching at this
331
+ // layer means listTools / re-initialize paths after an idle exit also
332
+ // self-heal, not just the registered-tool happy path.
333
+ //
334
+ // Sequencing is critical: respawn() resets `exited`, `child`, and
335
+ // `buffer` BEFORE start() + initialize(). The initialize() call inside
336
+ // respawn() goes through this same request() — recursion is safe
337
+ // because by the time we re-enter, `exited` is false again. We use a
338
+ // single-flight `respawnPromise` so concurrent callers share the same
339
+ // respawn (orphan-child guard, see field comment).
340
+ if (this.exited) {
341
+ if (!this.respawnPromise) {
342
+ this.respawnPromise = this.respawn().finally(() => {
343
+ this.respawnPromise = null;
344
+ });
345
+ }
346
+ await this.respawnPromise;
347
+ }
236
348
  if (!this.child)
237
349
  throw new Error("MCP client not started");
238
- if (this.exited)
239
- return Promise.reject(new Error("MCP server has exited"));
240
350
  const id = ++this.requestId;
241
351
  return new Promise((resolve, reject) => {
242
352
  const timer = setTimeout(() => {
@@ -256,14 +366,60 @@ export class MCPStdioClient {
256
366
  },
257
367
  });
258
368
  const frame = JSON.stringify({ jsonrpc: "2.0", id, method, params });
259
- this.child.stdin?.write(frame + "\n");
369
+ const rejectWrite = (err) => {
370
+ const handler = this.pending.get(id);
371
+ if (handler) {
372
+ this.pending.delete(id);
373
+ handler.reject(err);
374
+ return;
375
+ }
376
+ reject(err);
377
+ };
378
+ this.writeFrame(frame, rejectWrite);
260
379
  });
261
380
  }
381
+ writeFrame(frame, onError) {
382
+ if (!this.child || this.exited) {
383
+ onError?.(new Error("MCP server exited"));
384
+ return false;
385
+ }
386
+ const stdin = this.child.stdin;
387
+ if (!stdin || stdin.destroyed || stdin.writableEnded || stdin.closed) {
388
+ this.onExit();
389
+ onError?.(new Error("MCP server stdin unavailable"));
390
+ return false;
391
+ }
392
+ try {
393
+ stdin.write(frame + "\n", (err) => {
394
+ if (!err)
395
+ return;
396
+ const code = err.code;
397
+ if (code === "EPIPE" || code === "ERR_STREAM_DESTROYED") {
398
+ this.onExit();
399
+ onError?.(err);
400
+ return;
401
+ }
402
+ onError?.(err);
403
+ });
404
+ return true;
405
+ }
406
+ catch (err) {
407
+ const code = err && typeof err === "object" && "code" in err
408
+ ? err.code
409
+ : undefined;
410
+ if (err instanceof Error && (code === "EPIPE" || code === "ERR_STREAM_DESTROYED")) {
411
+ this.onExit();
412
+ onError?.(err);
413
+ return false;
414
+ }
415
+ throw err;
416
+ }
417
+ }
262
418
  notify(method, params) {
263
419
  if (!this.child)
264
420
  return;
265
421
  const frame = JSON.stringify({ jsonrpc: "2.0", method, params });
266
- this.child.stdin?.write(frame + "\n");
422
+ this.writeFrame(frame);
267
423
  }
268
424
  async initialize() {
269
425
  if (this.initialized)
@@ -284,33 +440,35 @@ export class MCPStdioClient {
284
440
  return Array.isArray(result.tools) ? result.tools : [];
285
441
  }
286
442
  async callTool(name, args) {
287
- // Respawn-on-idle-exit (#583). The MCP server gained an idle
288
- // self-shutdown in 1.0.132 (#565/#568, src/lifecycle.ts). When the
289
- // Pi-spawned child exits cleanly after the idle window, Pi keeps the
290
- // tool handles registered, but the bridge client is `exited=true`
291
- // and every subsequent request would reject with
292
- // "MCP server has exited" — leaving Pi's ctx_* tools permanently
293
- // broken until the user restarts Pi.
294
- //
295
- // The structural fix is here, not in lifecycle.ts: the bridge owns
296
- // the child lifecycle, so it transparently respawns + re-initialises
297
- // the server on the next call. Restores parity with adapters whose
298
- // host MCP client respawns on EOF (Claude Code, Codex, etc.).
299
- if (this.exited)
300
- await this.respawn();
443
+ // Respawn-on-idle-exit is now handled centrally in `request()`
444
+ // (#583 follow-up). Originally patched here in #583 moving it up
445
+ // one layer covers `listTools` / `initialize` paths too, with a
446
+ // single-flight guard against orphan child processes from
447
+ // concurrent callers.
301
448
  return this.request("tools/call", { name, arguments: args ?? {} }, DEFAULT_CALL_TIMEOUT_MS);
302
449
  }
303
450
  /**
304
- * Respawn the MCP child after an exit (clean idle shutdown or crash).
451
+ * Respawn the MCP child after an exit (clean shutdown or crash).
305
452
  * Resets state so a fresh `start()` + `initialize()` cycle runs, then
306
453
  * the caller's pending request flows through the new child.
307
454
  *
308
- * Internalexposed only via the public `callTool()` happy path.
455
+ * Single-flightconcurrent callers share one in-flight respawn via
456
+ * {@link respawnPromise}. Internal — only entered via {@link request}.
457
+ *
458
+ * Sequencing pinned (do not reorder without updating the regression
459
+ * test in tests/adapters/pi-mcp-bridge.test.ts):
460
+ * 1. `this.child = null` — drop stale handle
461
+ * 2. `this.buffer = ""` — discard leftover bytes from old child
462
+ * 3. `this.exited = false` — must precede `start()` + `initialize()`,
463
+ * because `request("initialize", …)`
464
+ * inside `initialize()` re-checks this
465
+ * flag and would otherwise re-enter
466
+ * respawn in an infinite loop
467
+ * 4. `this.initialized = false`
468
+ * 5. `this.start()`
469
+ * 6. `await this.initialize()` — flows through `request()` recursively
309
470
  */
310
471
  async respawn() {
311
- // Drop the dead child handle and clear stream buffer so leftover
312
- // bytes from the previous incarnation don't get parsed as JSON-RPC
313
- // for the new one. Pending map is already cleared by onExit().
314
472
  this.child = null;
315
473
  this.buffer = "";
316
474
  this.exited = false;
@@ -398,6 +556,8 @@ export async function bootstrapMCPTools(pi, serverScript, options = {}) {
398
556
  // for type inference). Empty-object fallback keeps tools that
399
557
  // declare no parameters callable.
400
558
  parameters: tool.inputSchema ?? { type: "object", properties: {} },
559
+ renderCall: createContextModeCallRenderer(tool.name),
560
+ renderResult: createContextModeResultRenderer(tool.name),
401
561
  async execute(_toolCallId, params) {
402
562
  const result = await client.callTool(tool.name, params ?? {});
403
563
  const text = (result.content ?? [])
@@ -20,54 +20,7 @@ export interface LifecycleGuardOptions {
20
20
  onShutdown: () => void;
21
21
  /** Injectable parent-alive check (for testing). Default: ppid-based check. */
22
22
  isParentAlive?: () => boolean;
23
- /**
24
- * Idle shutdown threshold in ms (#565). When the server has handled no
25
- * MCP activity for this long, `onShutdown` fires. `0` disables.
26
- * Default: env `CONTEXT_MODE_IDLE_TIMEOUT_MS`, else 15 minutes.
27
- * Skipped on TTY stdin (interactive dev / OpenCode ts-plugin standalone).
28
- *
29
- * Pair with the returned `recordActivity()` callback — call it on every
30
- * MCP request the server handles so genuinely busy servers never trip.
31
- */
32
- idleTimeoutMs?: number;
33
- /** Test injection — defaults to `Date.now`. */
34
- now?: () => number;
35
23
  }
36
- /**
37
- * Hybrid return type: callable like the original `() => void` cleanup (kept
38
- * for backwards compatibility with #103/#236/#311/#388/#534 test suites),
39
- * and additionally exposes `recordActivity` for the idle-timeout path (#565)
40
- * and `stop` as an explicit alias.
41
- */
42
- export interface LifecycleGuardHandle {
43
- /** Stop the guard. Calling the handle directly is equivalent. */
44
- (): void;
45
- /** Bumps the "last activity" timestamp so the idle timer doesn't fire. */
46
- recordActivity: () => void;
47
- /** Stop the guard. Alias for invoking the handle. */
48
- stop: () => void;
49
- }
50
- /**
51
- * Resolve the idle-shutdown threshold (#565).
52
- *
53
- * OpenCode + KiloCode open a fresh MCP client per session AND per subagent
54
- * task, but never tear them down for the host's lifetime. A host alive for
55
- * a working day accumulates one stdio child per session — observed live at
56
- * 26 children / 1.6 GB RSS under a single `opencode serve` parent.
57
- *
58
- * None of the existing exit paths (ppid poll, grandparent reparent, stdin
59
- * EOF, SIGTERM) fire while the host stays alive. Idle shutdown is the
60
- * structural fix: a server with no work to do should release its memory.
61
- *
62
- * Default 15 min strikes a balance — long enough that a paused
63
- * conversation does not pay a cold-start on every resume, short enough
64
- * that 8 hours of unused sessions do not pin GB of RAM.
65
- *
66
- * Set env to `0` to disable entirely.
67
- *
68
- * Exported for unit-testing.
69
- */
70
- export declare function idleTimeoutForEnv(env?: NodeJS.ProcessEnv): number;
71
24
  /** Injectable dependencies for {@link makeDefaultIsParentAlive}. */
72
25
  export interface IsParentAliveDeps {
73
26
  /** Read the current ppid. Default: `() => process.ppid`. */
@@ -107,9 +60,7 @@ export declare function makeDefaultIsParentAlive(deps?: IsParentAliveDeps): () =
107
60
  */
108
61
  export declare function lifecycleGuardIntervalForEnv(env?: NodeJS.ProcessEnv): number;
109
62
  /**
110
- * Start the lifecycle guard. Returns a handle with `recordActivity` (call
111
- * on every MCP request to keep idle timer from firing) and `stop`.
112
- *
63
+ * Start the lifecycle guard. Returns a cleanup function.
113
64
  * Skipped automatically when stdin is a TTY (e.g. OpenCode ts-plugin).
114
65
  */
115
- export declare function startLifecycleGuard(opts: LifecycleGuardOptions): LifecycleGuardHandle;
66
+ export declare function startLifecycleGuard(opts: LifecycleGuardOptions): () => void;
@@ -14,35 +14,6 @@
14
14
  * Cross-platform: macOS, Linux, Windows.
15
15
  */
16
16
  import { execFileSync } from "node:child_process";
17
- /**
18
- * Resolve the idle-shutdown threshold (#565).
19
- *
20
- * OpenCode + KiloCode open a fresh MCP client per session AND per subagent
21
- * task, but never tear them down for the host's lifetime. A host alive for
22
- * a working day accumulates one stdio child per session — observed live at
23
- * 26 children / 1.6 GB RSS under a single `opencode serve` parent.
24
- *
25
- * None of the existing exit paths (ppid poll, grandparent reparent, stdin
26
- * EOF, SIGTERM) fire while the host stays alive. Idle shutdown is the
27
- * structural fix: a server with no work to do should release its memory.
28
- *
29
- * Default 15 min strikes a balance — long enough that a paused
30
- * conversation does not pay a cold-start on every resume, short enough
31
- * that 8 hours of unused sessions do not pin GB of RAM.
32
- *
33
- * Set env to `0` to disable entirely.
34
- *
35
- * Exported for unit-testing.
36
- */
37
- export function idleTimeoutForEnv(env = process.env) {
38
- const raw = env.CONTEXT_MODE_IDLE_TIMEOUT_MS;
39
- if (raw === undefined)
40
- return 15 * 60 * 1000;
41
- const n = Number.parseInt(raw, 10);
42
- if (!Number.isFinite(n) || n < 0)
43
- return 15 * 60 * 1000;
44
- return n;
45
- }
46
17
  /** Read grandparent PID via `ps -o ppid= -p $PPID`. Returns NaN on failure or Windows. */
47
18
  function readGrandparentPpidImpl() {
48
19
  if (process.platform === "win32")
@@ -124,52 +95,25 @@ export function lifecycleGuardIntervalForEnv(env = process.env) {
124
95
  return 1000;
125
96
  }
126
97
  /**
127
- * Start the lifecycle guard. Returns a handle with `recordActivity` (call
128
- * on every MCP request to keep idle timer from firing) and `stop`.
129
- *
98
+ * Start the lifecycle guard. Returns a cleanup function.
130
99
  * Skipped automatically when stdin is a TTY (e.g. OpenCode ts-plugin).
131
100
  */
132
101
  export function startLifecycleGuard(opts) {
133
102
  const interval = opts.checkIntervalMs ?? lifecycleGuardIntervalForEnv();
134
103
  const check = opts.isParentAlive ?? defaultIsParentAlive;
135
- const idleTimeoutMs = opts.idleTimeoutMs ?? idleTimeoutForEnv();
136
- const now = opts.now ?? Date.now;
137
104
  let stopped = false;
138
- let lastActivity = now();
139
105
  const shutdown = () => {
140
106
  if (stopped)
141
107
  return;
142
108
  stopped = true;
143
109
  opts.onShutdown();
144
110
  };
145
- const recordActivity = () => {
146
- lastActivity = now();
147
- };
148
- // P0: Periodic parent liveness check.
111
+ // P0: Periodic parent liveness check
149
112
  const timer = setInterval(() => {
150
113
  if (!check())
151
114
  shutdown();
152
115
  }, interval);
153
116
  timer.unref();
154
- // P0+: Idle shutdown (#565). Runs on its OWN tick — distinct from the
155
- // 30 s parent-liveness poll — so a 15 min idle timeout actually reacts
156
- // close to 15 min instead of "next 30 s tick after 15 min". Pick the
157
- // tick as min(idleTimeoutMs / 6, 30 s) so a short timeout (e.g. 3 s in
158
- // e2e tests, 60 s in dev) reacts within ~16 % of its window while a
159
- // production 15 min timeout still polls every 30 s (cheap).
160
- //
161
- // Skipped on TTY because interactive dev sessions are expected to
162
- // sit idle between commands, and also when idleTimeoutMs is 0 (env
163
- // opt-out via CONTEXT_MODE_IDLE_TIMEOUT_MS=0).
164
- let idleTimer = null;
165
- if (idleTimeoutMs > 0 && !process.stdin.isTTY) {
166
- const idleTick = Math.max(50, Math.min(Math.floor(idleTimeoutMs / 6), 30_000));
167
- idleTimer = setInterval(() => {
168
- if (now() - lastActivity > idleTimeoutMs)
169
- shutdown();
170
- }, idleTick);
171
- idleTimer.unref();
172
- }
173
117
  // P0: OS signals — terminal close, kill, ctrl+c
174
118
  const signals = ["SIGTERM", "SIGINT"];
175
119
  if (process.platform !== "win32")
@@ -198,19 +142,11 @@ export function startLifecycleGuard(opts) {
198
142
  if (!process.stdin.isTTY) {
199
143
  process.stdin.on("end", onStdinEnd);
200
144
  }
201
- const cleanup = () => {
145
+ return () => {
202
146
  stopped = true;
203
147
  clearInterval(timer);
204
- if (idleTimer)
205
- clearInterval(idleTimer);
206
148
  for (const sig of signals)
207
149
  process.removeListener(sig, shutdown);
208
150
  process.stdin.removeListener("end", onStdinEnd);
209
151
  };
210
- // Hybrid: callable for legacy `const cleanup = startLifecycleGuard(...)`
211
- // sites, with `.recordActivity` / `.stop` properties for the new contract.
212
- const handle = cleanup;
213
- handle.recordActivity = recordActivity;
214
- handle.stop = cleanup;
215
- return handle;
216
152
  }
package/build/server.d.ts CHANGED
@@ -1,6 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import { type SpawnSyncOptions, type SpawnSyncReturns } from "node:child_process";
3
4
  import { ContentStore } from "./store.js";
5
+ import { type PlatformId } from "./adapters/types.js";
6
+ export declare const server: McpServer;
7
+ export interface RegisteredCtxTool {
8
+ name: string;
9
+ config: Record<string, unknown>;
10
+ handler: (args: Record<string, unknown>) => Promise<unknown> | unknown;
11
+ }
12
+ export declare const REGISTERED_CTX_TOOLS: RegisteredCtxTool[];
13
+ export declare function shouldSuppressMcpToolsForNativePluginHost(opts?: {
14
+ embedded?: string;
15
+ platform?: PlatformId;
16
+ settings?: Record<string, unknown> | null;
17
+ }): boolean;
18
+ type ToolContextOverride = {
19
+ projectDir: string;
20
+ sessionId?: string;
21
+ };
22
+ export declare function withProjectDirOverride<T>(projectDir: string | ToolContextOverride, fn: () => Promise<T>): Promise<T>;
4
23
  /**
5
24
  * Build the FK-attribution object passed to every ContentStore.index*() call
6
25
  * in this process. CLAUDE_SESSION_ID is the only MCP-side handle we have on