@herjarsa/omo-meta-governor 0.14.4 → 0.17.0

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/README.md CHANGED
@@ -141,7 +141,8 @@ Enabled when `meta_governor.enabled: true` in config.
141
141
  "mode": "message",
142
142
  "minActionForMessage": "warn",
143
143
  "maxInterventionsPerSession": 3,
144
- "respectDoneSignal": true
144
+ "respectDoneSignal": true,
145
+ "phaseAwareDoneSignal": true // v0.15.0: multi-phase plan support
145
146
  }
146
147
  }
147
148
  }
@@ -154,7 +155,172 @@ Enabled when `meta_governor.enabled: true` in config.
154
155
  | `mode` | `"message"` | How to inject: `"silent"`, `"message"`, or `"system"` |
155
156
  | `minActionForMessage` | `"warn"` | Minimum action: `"warn"`, `"escalate"`, or `"stop"` |
156
157
  | `maxInterventionsPerSession` | `3` | Hard cap on injections per session |
157
- | `respectDoneSignal` | `true` | Stop injecting after DONE + Oracle verified |
158
+ | `respectDoneSignal` | `true` | Stop injecting after terminal signal + Oracle verified |
159
+ | `phaseAwareDoneSignal` | `false` | **v0.15.0**: when `true`, only `<promise>PLAN-COMPLETE</promise>` latches intervention. DONE/PHASE-N-COMPLETE are per-phase hints. Recommended for multi-phase plans. |
160
+
161
+ ### Multi-phase plans (v0.15.0)
162
+
163
+ For work plans with multiple phases (e.g. Sisyphus/Prometheus work plans),
164
+ configure `phaseAwareDoneSignal: true` and emit `<promise>PLAN-COMPLETE</promise>`
165
+ only when the **entire** plan is verified done by Oracle. The new markers:
166
+
167
+ | Marker | Effect |
168
+ |--------|--------|
169
+ | `<promise>DONE</promise>` | Per-phase hint. Logged but does NOT latch intervention (when `phaseAwareDoneSignal: true`). |
170
+ | `<promise>PHASE-N-COMPLETE</promise>` | Per-phase hint (e.g. `<promise>PHASE-1-COMPLETE</promise>`). Same as DONE — logged, does NOT latch. |
171
+ | `<promise>PLAN-COMPLETE</promise>` | Terminal. Latches intervention when Oracle has verified. |
172
+
173
+ **Migration**: existing v0.10.0–v0.14.x users keep working without changes (default
174
+ `phaseAwareDoneSignal: false` preserves the legacy single-task behavior). Set the
175
+ flag to `true` and switch your terminal marker to `PLAN-COMPLETE` to enable
176
+ multi-phase governance.
177
+
178
+ ## v0.16.0 — Audit remediation: memory hygiene, dead code, tool coverage, CI
179
+
180
+ v0.16.0 closes the 50+ findings from the multi-front audit at `.omo/ulw-research/20260727-000530/plan-audit-v0.15.0.md`. The release is **additive in behavior, no breaking API changes** for users — only internal cleanup, dead code removal, and CI hardening.
181
+
182
+ ### Highlights
183
+
184
+ #### Memory hygiene (F1)
185
+
186
+ - **`AuditStateCache`** (`src/audit-state-cache.ts`) — TTL+LRU bounded cache (100 entries, 1h TTL) replaces the bare `Map` that accumulated audit state without bounds. Stale sessions are evicted automatically.
187
+ - **`TTLQueue`** (`src/ttl-queue.ts`) — TTL-based expiration for `pendingBotFeedback` and `pendingViolations` queues. Previously unbounded.
188
+ - Removed dynamic `require("node:fs")` inside `shouldInjectPlanReminder` — replaced with static ESM imports (no more runtime module resolution failures).
189
+
190
+ #### Dead code elimination (F2)
191
+
192
+ - `takeAnyDecision()` — deprecated; removed from the active governance pipeline.
193
+ - `systemInjection` — now awaited eagerly instead of fire-and-forget, eliminating a silent failure route.
194
+ - `logToFile` in `graph-sync.ts` — wired to the real JSONL file logger (was a no-op stub).
195
+ - Plugin version — derived from `package.json` at runtime instead of hardcoded "0.13.0" (closes the version-drift bug where `omo_health` reported stale versions).
196
+
197
+ #### Tool bug fixes (F3)
198
+
199
+ - **AFT checkpoint/undo**: args split on whitespace broke names with spaces. Rewrote arg construction with proper quoting.
200
+ - **AFT subcommand**: now uses `options.projectDir` instead of `process.cwd()`.
201
+ - **graphify binary override**: `omo_path` / `omo_explain` honored the `graphifyBin` option (was hardcoded).
202
+ - **`as never` cast** on `setClient` → proper runtime guard that validates client shape.
203
+ - **`session-bridge`**: replaced module-level `_client` with `AsyncLocalStorage` for per-request isolation. Concurrent sessions no longer race on the same client reference.
204
+
205
+ #### Test coverage (F4)
206
+
207
+ - 22 tests covering all 15 custom tools (`src/custom-tools.test.ts`). Previously the entire public tool surface had zero test coverage.
208
+ - 12 tests for `decision-store` (previously untested).
209
+
210
+ #### Type/token pipeline (F5)
211
+
212
+ - `token-predictor` refactor: dead code (`delegate`/`switch-model`) removed; output is now informational-only as designed.
213
+ - Type alignment across `types.ts`, `token-predictor.ts`, `orchestrator.ts`.
214
+
215
+ #### CI matrix (F6)
216
+
217
+ - `bun run typecheck` now runs on **macos-latest** and **windows-latest** (was Ubuntu-only).
218
+ - Removed `package-lock.json` (bun project — canonical is `bun.lock`).
219
+ - Secret redaction layer in `logToFile` (JWT, OpenAI keys, Bearer tokens, GitHub PATs, generic key:value patterns).
220
+ - Implementation plan renamed `IMPLEMENTATION_PLAN.md` → `ARCHITECTURE.md`.
221
+
222
+ #### Final refactors (F7)
223
+
224
+ - Score formula documented (header doc with full formula spec).
225
+ - **NaN guard** in `score()` — defaults to neutral continue when `iterationRatio` or `ambient.iteration/maxIterations` produce NaN.
226
+ - `ACTION_SEVERITY` keyed by `DecisionHandlerOutput["action"]` union literal (was bare `Record<string, number>`).
227
+ - `projectHasCodegraph` / `projectHasGraphify` IIFE booleans replaced with lookup-time calls to `graphRetrieval.hasCodegraphDir(cwd)`.
228
+ - `extractConcepts` includes file basename for FTS lookup by tool/file name.
229
+ - Backup graph-sync uses `triggerReindex` (was `triggerCodegraphSync`) — reindexes both codegraph AND graphify backends.
230
+
231
+ ### Test & build status
232
+
233
+ - **495/495 tests pass** (up from 487 in v0.15.0/0.15.1).
234
+ - `bun run typecheck` clean.
235
+ - `bun build.ts` clean (0.34 MB dist).
236
+ - `npm pack --dry-run` validated (no forbidden artifacts).
237
+
238
+ ### Migration
239
+
240
+ No user action required. All changes are internal. The default `phaseAwareDoneSignal` is still `false` for backward compatibility; the v0.15.0 multi-phase behavior is preserved when explicitly enabled.
241
+
242
+ ### Deferred to v0.17.0
243
+
244
+ - F5.1 — wiring `escalate` action to a real dispatcher (Oracle is recommended but not yet wired).
245
+ - F5.4 — `maxLessonsPerSession` enforcement (config field exists but is not enforced).
246
+ - F3.6 — Bridge tools lying about delivery (5 tools still return "dispatched" without polling). Recommend the user explicitly request this if delivery verification is critical.
247
+
248
+
249
+
250
+
251
+ ## v0.17.0 — Wire escalate to Oracle, enforce lesson cap, verify bridge delivery
252
+
253
+ v0.17.0 closes the 3 deferred items from the v0.16.0 audit: **F5.1** (escalate → Oracle), **F5.4** (`maxLessonsPerSession` enforcement), and **F3.6** (bridge tool delivery verification).
254
+
255
+ ### Highlights
256
+
257
+ #### F5.1 — Escalate action now fires Oracle (v0.17.0)
258
+
259
+ When the scoring engine produces an `escalate` action with target `oracle`, the plugin's `tool.execute.after` hook now fires a `session.prompt()` instructing the LLM to invoke `task(subagent_type=oracle)`. The prompt includes the decision reasoning, evidence count, and a verification pass directive. New `buildEscalationPrompt()` function in `session-bridge.ts` is the pure prompt builder (testable in isolation). User-targeted escalations get a separate prompt asking the LLM to summarize for human input.
260
+
261
+ ```ts
262
+ // Decision flow when score lands in escalate band:
263
+ score ≤ -escalateThreshold (default -0.6)
264
+ → decision.action = "escalate"
265
+ → decision.shouldEscalateTo = "oracle" (or "user" for grave deviations)
266
+ → plugin fires session.prompt with buildEscalationPrompt(...)
267
+ → LLM invokes Oracle (or summarizes for user)
268
+ → Oracle verifies → oracleInvoked=true → governance continues
269
+ ```
270
+
271
+ #### F5.4 — `maxLessonsPerSession` is now enforced
272
+
273
+ The cap (default 20) was a config field that was never enforced. v0.17.0 adds:
274
+ - `currentLessonCount` on `LearnFromOutcomeInput` and `MetaGovernorInput`
275
+ - `lessonCount` tracked in per-session `AuditState`
276
+ - `observeAndLearn()` short-circuits when `currentLessonCount >= maxLessonsPerSession`
277
+ - The orchestrator increments `sessionState.lessonCount` after each successful save
278
+ - **Cap semantics: inclusive** — when count equals cap, no more lessons are saved
279
+
280
+ #### F3.6 — Bridge tool delivery verification
281
+
282
+ The 5 bridge tools (`omo_remember`, `omo_recall_mcp`, `omo_rule`, `omo_history`, `omo_note`) previously returned "dispatched" after the `session.prompt()` was queued — without verifying the LLM actually called the MCP tool. v0.17.0 adds:
283
+
284
+ - **New `PendingDeliveryRegistry` module** (`src/delivery-registry.ts`) — tracks pending dispatches per session with TTL-based cleanup.
285
+ - **`tool.execute.after` hook** marks deliveries when a matching MCP tool call is observed.
286
+ - **All 5 bridge tools** now report `deliveryStatus: "delivered" | "pending"` in their tool result and metadata, and briefly poll (1.5s) for fast deliveries.
287
+ - When the LLM follows the prompt, the tool returns immediately with `"delivered"`. When it doesn't, the tool returns `"pending"` and the entry expires silently after 10s.
288
+
289
+ ```ts
290
+ // Bridge tool result metadata now includes:
291
+ {
292
+ tool: "omo_remember",
293
+ ok: true,
294
+ deliveryStatus: "delivered" | "pending",
295
+ messageID: "...",
296
+ durationMs: 1234,
297
+ contentLength: 256
298
+ }
299
+ ```
300
+
301
+ ### Test & build status
302
+
303
+ - **514/514 tests pass** (up from 495 in v0.16.0 — 5 + 4 + 10 new tests across F5.4, F5.1, F3.6).
304
+ - `bun run typecheck` clean.
305
+ - `bun build.ts` clean (0.34 MB dist).
306
+ - `npm pack --dry-run` validated.
307
+
308
+ ### Migration
309
+
310
+ No user action required. All changes are internal or additive:
311
+ - `deliveryStatus` is an additive metadata field — existing consumers ignore it.
312
+ - `maxLessonsPerSession` is now actually enforced — if you have sessions that previously saved more than 20 lessons (e.g. from before the cap was added), this may surprise you. Bump the cap in your config if needed.
313
+ - `escalate` action now actively fires Oracle — this is the first version where Oracle is auto-invoked, not just manually invoked by the LLM.
314
+
315
+ ### Audit roadmap (status as of v0.17.0)
316
+
317
+ | Release | Status | Scope |
318
+ |---------|--------|-------|
319
+ | v0.15.1 (F0) | ✅ Shipped | Hotfix self-dep + npm pack gate |
320
+ | v0.16.0 (F1-F7) | ✅ Shipped | Memory hygiene, dead code, tool coverage, CI |
321
+ | v0.17.0 (deferred) | ✅ Shipped | F5.1 escalate, F5.4 cap, F3.6 delivery verify |
322
+
323
+ All audit findings are now closed. Future work focuses on new features and user-driven feedback.
158
324
 
159
325
  ## Auto-upgrade (v0.12.0)
160
326
 
@@ -0,0 +1,43 @@
1
+ /**
2
+ * TTL-based + LRU-bounded cache for per-session audit state.
3
+ *
4
+ * v0.15.0: closed the C1/H16 audit findings — `auditSessions` was an
5
+ * unbounded Map that grew indefinitely. Replaced with a class that
6
+ *
7
+ * 1. Caps total entries (default 100, configurable).
8
+ * 2. Evicts the least-recently-accessed entry when the cap is hit.
9
+ * 3. TTLs each entry (default 1 hour, configurable). On read, expired
10
+ * entries are dropped.
11
+ *
12
+ * The cache is intentionally synchronous (Map-backed) — the audit
13
+ * state mutations happen on the plugin's main thread between hooks
14
+ * and are not shared across the event loop, so no locking is needed.
15
+ *
16
+ * v0.16.0 also documents: Bun's runtime is single-threaded, so a read
17
+ * followed by an `await` cannot be interrupted by another write. This
18
+ * means we do NOT need per-session mutex for the audit state. See
19
+ * plugin.ts `// v0.16.0 concurrency model` comment block.
20
+ */
21
+ export interface AuditStateCacheConfig {
22
+ /** Max number of entries. Default 100. */
23
+ readonly maxEntries?: number;
24
+ /** Time-to-live per entry in milliseconds. Default 3_600_000 (1h). */
25
+ readonly ttlMs?: number;
26
+ /** Optional clock injection for testing. Returns ms since epoch. */
27
+ readonly now?: () => number;
28
+ }
29
+ export declare class AuditStateCache<V> {
30
+ private readonly store;
31
+ private readonly maxEntries;
32
+ private readonly ttlMs;
33
+ private readonly now;
34
+ constructor(config?: AuditStateCacheConfig);
35
+ get(key: string): V | undefined;
36
+ has(key: string): boolean;
37
+ set(key: string, value: V): void;
38
+ delete(key: string): boolean;
39
+ size(): number;
40
+ clear(): void;
41
+ /** Evict the entry with the oldest lastAccessAtMs. No-op if empty. */
42
+ private evictOldest;
43
+ }
package/dist/config.d.ts CHANGED
@@ -51,6 +51,8 @@ export interface MetaGovernorPluginConfig {
51
51
  maxInterventionsPerSession?: number;
52
52
  /** v0.10.0: stop injecting after <promise>DONE</promise> + Oracle verified. */
53
53
  respectDoneSignal?: boolean;
54
+ /** v0.15.0: split per-phase hint from terminal signal. See types.ts. */
55
+ phaseAwareDoneSignal?: boolean;
54
56
  };
55
57
  /** Sisyphus protocol enforcement config. */
56
58
  protocolEnforcement?: {
@@ -23,6 +23,29 @@ import type { SqliteBackend } from "./sqlite-backend";
23
23
  import type { GraphRetrieval } from "./graph-retrieval";
24
24
  import type { MetricsCollector } from "./metrics";
25
25
  import { type CodeGraphTools } from "./codegraph-tools";
26
+ /**
27
+ * Module-level reference to the PendingDeliveryRegistry. The plugin
28
+ * factory sets this once at startup. Bridge tools call it via
29
+ * onDispatch and pollForDelivery.
30
+ */
31
+ declare let pendingRegistryRef: {
32
+ register(input: {
33
+ sessionID: string;
34
+ mcpTool: string;
35
+ mcpArgs: Record<string, unknown>;
36
+ ttlMs?: number;
37
+ }): string;
38
+ awaitDelivery(input: {
39
+ sessionID: string;
40
+ mcpTool: string;
41
+ timeoutMs?: number;
42
+ }): Promise<"delivered" | "expired">;
43
+ } | null;
44
+ /**
45
+ * Called by the plugin factory at startup to inject the delivery registry.
46
+ * Exposed as a setter so we don't need to thread it through every tool deps.
47
+ */
48
+ export declare function setPendingDeliveryRegistry(registry: typeof pendingRegistryRef): void;
26
49
  export interface OmoSearchDeps {
27
50
  graphRetrieval: GraphRetrieval;
28
51
  cwd: string;
@@ -111,6 +134,14 @@ export declare function buildOmoImpactTool(deps: OmoImpactDeps): {
111
134
  }, context: ToolContext): Promise<ToolResult>;
112
135
  };
113
136
  export interface OmoRememberDeps {
137
+ /** Optional callback invoked after a successful prompt dispatch.
138
+ * Used by the plugin to register the pending delivery in the registry
139
+ * so the bridge tool can verify the LLM actually called the MCP tool. */
140
+ onDispatch?: (input: {
141
+ sessionID: string;
142
+ mcpTool: string;
143
+ mcpArgs: Record<string, unknown>;
144
+ }) => void;
114
145
  }
115
146
  /**
116
147
  * Build the `omo_remember` tool. Persists a fact/observation/lesson to
@@ -122,18 +153,26 @@ export declare function buildOmoRememberTool(deps: OmoRememberDeps): {
122
153
  content: import("zod").ZodString;
123
154
  concepts: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
124
155
  type: import("zod").ZodOptional<import("zod").ZodEnum<{
125
- pattern: "pattern";
126
156
  fact: "fact";
127
157
  observation: "observation";
158
+ pattern: "pattern";
128
159
  }>>;
129
160
  };
130
161
  execute(args: {
131
162
  content: string;
132
163
  concepts?: string[] | undefined;
133
- type?: "pattern" | "fact" | "observation" | undefined;
164
+ type?: "fact" | "observation" | "pattern" | undefined;
134
165
  }, context: ToolContext): Promise<ToolResult>;
135
166
  };
136
167
  export interface OmoRecallMcpDeps {
168
+ /** Optional callback invoked after a successful prompt dispatch.
169
+ * Used by the plugin to register the pending delivery in the registry
170
+ * so the bridge tool can verify the LLM actually called the MCP tool. */
171
+ onDispatch?: (input: {
172
+ sessionID: string;
173
+ mcpTool: string;
174
+ mcpArgs: Record<string, unknown>;
175
+ }) => void;
137
176
  }
138
177
  /**
139
178
  * Build the `omo_recall_mcp` tool. Searches AgentMemory by sending a
@@ -155,6 +194,14 @@ export declare function buildOmoRecallMcpTool(deps: OmoRecallMcpDeps): {
155
194
  }, context: ToolContext): Promise<ToolResult>;
156
195
  };
157
196
  export interface OmoRuleDeps {
197
+ /** Optional callback invoked after a successful prompt dispatch.
198
+ * Used by the plugin to register the pending delivery in the registry
199
+ * so the bridge tool can verify the LLM actually called the MCP tool. */
200
+ onDispatch?: (input: {
201
+ sessionID: string;
202
+ mcpTool: string;
203
+ mcpArgs: Record<string, unknown>;
204
+ }) => void;
158
205
  }
159
206
  /**
160
207
  * Build the `omo_rule` tool. Saves a durable rule to Magic Context that
@@ -165,20 +212,28 @@ export declare function buildOmoRuleTool(deps: OmoRuleDeps): {
165
212
  description: string;
166
213
  args: {
167
214
  category: import("zod").ZodEnum<{
168
- PROJECT_RULES: "PROJECT_RULES";
169
215
  ARCHITECTURE: "ARCHITECTURE";
170
- CONSTRAINTS: "CONSTRAINTS";
171
216
  CONFIG_VALUES: "CONFIG_VALUES";
217
+ CONSTRAINTS: "CONSTRAINTS";
172
218
  NAMING: "NAMING";
219
+ PROJECT_RULES: "PROJECT_RULES";
173
220
  }>;
174
221
  content: import("zod").ZodString;
175
222
  };
176
223
  execute(args: {
177
- category: "PROJECT_RULES" | "ARCHITECTURE" | "CONSTRAINTS" | "CONFIG_VALUES" | "NAMING";
224
+ category: "ARCHITECTURE" | "CONFIG_VALUES" | "CONSTRAINTS" | "NAMING" | "PROJECT_RULES";
178
225
  content: string;
179
226
  }, context: ToolContext): Promise<ToolResult>;
180
227
  };
181
228
  export interface OmoHistoryDeps {
229
+ /** Optional callback invoked after a successful prompt dispatch.
230
+ * Used by the plugin to register the pending delivery in the registry
231
+ * so the bridge tool can verify the LLM actually called the MCP tool. */
232
+ onDispatch?: (input: {
233
+ sessionID: string;
234
+ mcpTool: string;
235
+ mcpArgs: Record<string, unknown>;
236
+ }) => void;
182
237
  }
183
238
  /**
184
239
  * Build the `omo_history` tool. Searches git commit history and prior
@@ -189,16 +244,24 @@ export declare function buildOmoHistoryTool(deps: OmoHistoryDeps): {
189
244
  args: {
190
245
  query: import("zod").ZodString;
191
246
  sources: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<{
192
- message: "message";
193
247
  git_commit: "git_commit";
248
+ message: "message";
194
249
  }>>>;
195
250
  };
196
251
  execute(args: {
197
252
  query: string;
198
- sources?: ("message" | "git_commit")[] | undefined;
253
+ sources?: ("git_commit" | "message")[] | undefined;
199
254
  }, context: ToolContext): Promise<ToolResult>;
200
255
  };
201
256
  export interface OmoNoteDeps {
257
+ /** Optional callback invoked after a successful prompt dispatch.
258
+ * Used by the plugin to register the pending delivery in the registry
259
+ * so the bridge tool can verify the LLM actually called the MCP tool. */
260
+ onDispatch?: (input: {
261
+ sessionID: string;
262
+ mcpTool: string;
263
+ mcpArgs: Record<string, unknown>;
264
+ }) => void;
202
265
  }
203
266
  /**
204
267
  * Build the `omo_note` tool. Writes a session-scoped working note via
@@ -266,8 +329,7 @@ export declare function buildOmoOutlineTool(deps: OmoOutlineDeps): {
266
329
  target: string;
267
330
  }, context: ToolContext): Promise<ToolResult>;
268
331
  };
269
- export interface OmoCheckpointDeps {
270
- }
332
+ export type OmoCheckpointDeps = Record<string, never>;
271
333
  /**
272
334
  * Build the `omo_checkpoint` tool. Creates a named AFT checkpoint so the
273
335
  * user can revert to a known good state. Uses `aft safety checkpoint --name <name>`.
@@ -281,8 +343,7 @@ export declare function buildOmoCheckpointTool(_deps: OmoCheckpointDeps): {
281
343
  name: string;
282
344
  }, context: ToolContext): Promise<ToolResult>;
283
345
  };
284
- export interface OmoUndoDeps {
285
- }
346
+ export type OmoUndoDeps = Record<string, never>;
286
347
  /**
287
348
  * Build the `omo_undo` tool. Reverts tracked files to the most recent AFT
288
349
  * checkpoint. Uses `aft safety undo`.
@@ -292,3 +353,4 @@ export declare function buildOmoUndoTool(_deps: OmoUndoDeps): {
292
353
  args: {};
293
354
  execute(args: Record<string, never>, context: ToolContext): Promise<ToolResult>;
294
355
  };
356
+ export {};
@@ -25,9 +25,10 @@ export declare function takeDecision(sessionID: string): DecisionHandlerOutput |
25
25
  */
26
26
  export declare function hasDecision(sessionID: string): boolean;
27
27
  /**
28
- * Take any pending decision across all sessions.
29
- * Useful for hooks that do not receive a sessionID.
30
- * Returns the first pending decision found, or undefined if none.
28
+ * @deprecated v0.16.0: this function can leak decisions across sessions.
29
+ * Use takeDecision(sessionID) instead — the messages.transform hook now
30
+ * derives the sessionID from the last outgoing message (see plugin.ts).
31
+ * Will be removed in v0.18.0.
31
32
  */
32
33
  export declare function takeAnyDecision(): DecisionHandlerOutput | undefined;
33
34
  /**
@@ -0,0 +1,83 @@
1
+ /**
2
+ * v0.17.0 (F3.6): PendingDeliveryRegistry — track bridge tool dispatches
3
+ * and verify they were actually delivered by the LLM via the matching
4
+ * MCP tool call in `tool.execute.after`.
5
+ *
6
+ * Why this exists (v0.14.0 pivot):
7
+ * session-bridge uses `session.prompt()` to instruct the LLM to call an
8
+ * MCP tool (e.g. agentmemory_memory_save). The LLM may or may not follow
9
+ * the instruction. Previously, the bridge tool returned "dispatched" on
10
+ * `session.prompt()` success — but that just means the prompt was queued,
11
+ * not that the MCP tool was actually called. This registry gives us a
12
+ * way to verify delivery within a short window.
13
+ *
14
+ * Design:
15
+ * - Per-session pending entries (Map) — small, bounded, TTL-cleaned.
16
+ * - `register()` when a bridge tool dispatches a prompt.
17
+ * - `markDelivered()` when tool.execute.after sees a matching tool call.
18
+ * - `awaitDelivery()` is a brief async poll (~2-3s) used by bridge tools
19
+ * to surface fast deliveries. Slow deliveries are still tracked.
20
+ */
21
+ export interface PendingDelivery {
22
+ /** Unique id (randomUUID) so callers can correlate. */
23
+ readonly id: string;
24
+ readonly sessionID: string;
25
+ /** The MCP tool the bridge instructed the LLM to call. */
26
+ readonly mcpTool: string;
27
+ /** Hash of mcpArgs to match the eventual tool call. */
28
+ readonly mcpArgsHash: string;
29
+ /** When registered (ms since epoch). */
30
+ readonly registeredAt: number;
31
+ /** TTL in ms — after this, the entry is considered expired. */
32
+ readonly ttlMs: number;
33
+ }
34
+ export type DeliveryStatus = "pending" | "delivered" | "expired";
35
+ export declare class PendingDeliveryRegistry {
36
+ private readonly entries;
37
+ /** When mcpArgs match is disabled (no mcpArgs available on observed call) */
38
+ private deliveredCount;
39
+ private expiredCount;
40
+ /**
41
+ * Register a pending delivery. Returns a random id for correlation.
42
+ */
43
+ register(input: {
44
+ sessionID: string;
45
+ mcpTool: string;
46
+ mcpArgs: Record<string, unknown>;
47
+ ttlMs?: number;
48
+ }): string;
49
+ /**
50
+ * Mark a pending delivery as delivered. Returns the matching id if
51
+ * found, or null if no pending entry matches.
52
+ */
53
+ markDelivered(input: {
54
+ sessionID: string;
55
+ mcpTool: string;
56
+ mcpArgs?: unknown;
57
+ }): string | null;
58
+ /**
59
+ * Brief async poll: wait for the matching delivery, up to `timeoutMs`.
60
+ * Resolves with the status when the delivery is verified or expires.
61
+ */
62
+ awaitDelivery(input: {
63
+ sessionID: string;
64
+ mcpTool: string;
65
+ timeoutMs?: number;
66
+ }): Promise<DeliveryStatus>;
67
+ /**
68
+ * Return current stats: pending + cumulative delivered + cumulative expired.
69
+ */
70
+ getStats(): {
71
+ pending: number;
72
+ delivered: number;
73
+ expired: number;
74
+ };
75
+ /**
76
+ * Clear all entries for a session. Useful on session end.
77
+ */
78
+ clearSession(sessionID: string): void;
79
+ /**
80
+ * Remove expired entries. Called internally on register/mark/await.
81
+ */
82
+ private cleanup;
83
+ }
@@ -50,6 +50,8 @@ export interface InvokeOptions {
50
50
  graphifyBin?: string;
51
51
  /** Override timeout for this call. */
52
52
  timeoutMs?: number;
53
+ /** v0.16.0: project working directory. Defaults to process.cwd(). */
54
+ projectDir?: string;
53
55
  }
54
56
  /** Deterministic hash for a query string. Used as cache key suffix. */
55
57
  export declare function hashQuery(query: string): string;
@@ -129,6 +129,8 @@ export declare function isGraphifyHookInstalled(projectDir: string): Promise<boo
129
129
  * Best-effort: never throws, returns a structured result.
130
130
  */
131
131
  export declare function triggerCodegraphSync(projectDir: string): Promise<GraphSyncResult>;
132
+ import type { LogLevel } from "./file-logger";
133
+ export declare function logToFile(level: LogLevel, msg: string): Promise<void>;
132
134
  /**
133
135
  * Compare two semver strings. Returns true if `latest` is strictly greater
134
136
  * than `installed`. Handles X.Y.Z with optional pre-release suffix (-rc.1,