@ddtcorex/dsh-maestro-supervisor 0.7.2 → 0.7.4

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.
@@ -5,12 +5,10 @@ export interface DebugAgentOpts {
5
5
  httpCode?: number;
6
6
  };
7
7
  cooldownMs?: number;
8
- fetchLLM?: (prompt: string) => Promise<string>;
9
8
  exec?: (cmd: string, opts?: any) => string;
10
9
  readFile?: (path: string) => string;
11
10
  writeFile?: (path: string, content: string) => void;
12
11
  dryBoot?: () => Promise<boolean>;
13
- getCredentials?: () => Promise<string | null>;
14
12
  }
15
13
  export declare function runDebugAgent(opts: DebugAgentOpts): Promise<{
16
14
  fixed: boolean;
@@ -16,27 +16,13 @@ export async function runDebugAgent(opts) {
16
16
  const readFile = opts.readFile ?? defaultReadFile;
17
17
  const writeFile = opts.writeFile ?? defaultWriteFile;
18
18
  const dryBoot = opts.dryBoot ?? defaultDryBoot;
19
- const fetchLLM = opts.fetchLLM ?? defaultFetchLLM;
20
- // Gather context
21
- let report = '';
22
- try {
23
- report = readFile(opts.reportPath);
24
- }
25
- catch { }
26
19
  const err = opts.health.error ?? '';
27
- let gitDiff = '';
28
- try {
29
- gitDiff = exec('git diff --stat 2>&1 | head -30', { timeout: 5000 });
30
- }
31
- catch {
32
- gitDiff = '';
33
- }
34
- // Deterministic auto-fix for known patterns before LLM
20
+ // Deterministic auto-fix for known patterns
35
21
  try {
36
22
  await autoFixKnownPatterns(err, exec, readFile, writeFile);
37
23
  }
38
24
  catch { }
39
- // Phase 1: reproduce — dryBoot check (if transient, already fixed)
25
+ // Reproduce — dryBoot check (if transient, already fixed)
40
26
  try {
41
27
  const ok = await dryBoot();
42
28
  if (ok) {
@@ -44,36 +30,8 @@ export async function runDebugAgent(opts) {
44
30
  }
45
31
  }
46
32
  catch { }
47
- // Phase 2 & 3: LLM systematic-debugging (only if dryBoot failed)
48
- const prompt = buildPrompt({ report, err, gitDiff, reportPath: opts.reportPath });
49
- try {
50
- const llmResponse = await fetchLLM(prompt);
51
- // Try to apply LLM-suggested fix if it contains file+content
52
- const applied = tryApplyLLMFix(llmResponse, writeFile, exec);
53
- // Verify after apply
54
- try {
55
- exec('pnpm verify --silent 2>&1 | head -20', { timeout: 15000 });
56
- }
57
- catch { }
58
- const ok2 = await dryBoot().catch(() => false);
59
- if (ok2) {
60
- const summary = extractSummary(llmResponse);
61
- return { fixed: true, reason: `LLM fixed (attempt ${attempts}): ${summary}` };
62
- }
63
- // LLM responded but still not fixed
64
- if (applied) {
65
- return { fixed: false, reason: `LLM applied fix but dry-boot still fails (attempt ${attempts}) — ${extractSummary(llmResponse).slice(0, 120)}` };
66
- }
67
- }
68
- catch (e) {
69
- const msg = e?.message ?? String(e);
70
- // If LLM unavailable, fall through to manual reason
71
- if (msg.includes('no api key') || msg.includes('LLM')) {
72
- return { fixed: false, reason: `would debug ${opts.reportPath} (attempt ${attempts}) — LLM not configured: ${msg}` };
73
- }
74
- return { fixed: false, reason: `would debug ${opts.reportPath} (attempt ${attempts}) — LLM error: ${msg}` };
75
- }
76
- return { fixed: false, reason: `would debug ${opts.reportPath} (attempt ${attempts}) — LLM not wired, manual fix needed` };
33
+ // No LLM auto-debug: deterministic auto-fix + dry-boot already tried — hand off to a human.
34
+ return { fixed: false, reason: `would debug ${opts.reportPath} (attempt ${attempts}) — manual fix needed (deterministic auto-fix + dry-boot already tried)` };
77
35
  }
78
36
  async function autoFixKnownPatterns(err, exec, readFile, writeFile) {
79
37
  const { resolveHarnessRoot } = await import('./paths.js');
@@ -129,78 +87,6 @@ async function autoFixKnownPatterns(err, exec, readFile, writeFile) {
129
87
  return;
130
88
  }
131
89
  }
132
- function buildPrompt(ctx) {
133
- return [
134
- 'systematic-debugging — DSH Web crash auto-fix',
135
- '',
136
- 'Phase 1: Root Cause Investigation. Read error carefully, reproduce, check recent changes.',
137
- `Report: ${ctx.reportPath}`,
138
- `Health error: ${ctx.err}`,
139
- `Report snippet: ${ctx.report.slice(0, 2000)}`,
140
- `Git diff: ${ctx.gitDiff.slice(0, 1500)}`,
141
- '',
142
- 'Task: Propose minimal single-file fix. Respond as JSON: {"analysis":"...","file":"<abs path>","content":"<full file content or patch>"}',
143
- 'If unsure, explain analysis and suggest manual step.',
144
- ].join('\n');
145
- }
146
- function tryApplyLLMFix(response, writeFile, exec) {
147
- try {
148
- // First try to extract inner JSON if response is OpenAI wrapper (choices) — unwrap message.content
149
- let candidate = response;
150
- try {
151
- const outer = JSON.parse(response);
152
- if (outer.choices?.[0]?.message?.content)
153
- candidate = outer.choices[0].message.content;
154
- else if (outer.output) {
155
- // openai-responses wrapper: find message with output_text
156
- const msg = outer.output.find((o) => o.type === 'message' && o.content?.[0]?.text);
157
- if (msg)
158
- candidate = msg.content[0].text;
159
- }
160
- }
161
- catch { }
162
- // Also handle case where response is already the inner JSON string or contains it
163
- const jsonMatch = candidate.match(/\{[\s\S]*\}/);
164
- if (!jsonMatch)
165
- return false;
166
- const obj = JSON.parse(jsonMatch[0]);
167
- if (obj.file && obj.content) {
168
- writeFile(obj.file, obj.content);
169
- // try to verify after write
170
- try {
171
- exec(`pnpm --dir ${obj.file.split('/packages/')[0] || '.'} verify --silent 2>&1 | head -10`, { timeout: 15000 });
172
- }
173
- catch { }
174
- return true;
175
- }
176
- }
177
- catch { }
178
- return false;
179
- }
180
- function extractSummary(response) {
181
- try {
182
- let candidate = response;
183
- try {
184
- const outer = JSON.parse(response);
185
- if (outer.choices?.[0]?.message?.content)
186
- candidate = outer.choices[0].message.content;
187
- else if (outer.output) {
188
- const msg = outer.output.find((o) => o.type === 'message' && o.content?.[0]?.text);
189
- if (msg)
190
- candidate = msg.content[0].text;
191
- }
192
- }
193
- catch { }
194
- const m = candidate.match(/\{[\s\S]*\}/);
195
- if (m) {
196
- const obj = JSON.parse(m[0]);
197
- if (obj.analysis)
198
- return obj.analysis.slice(0, 200);
199
- }
200
- }
201
- catch { }
202
- return response.slice(0, 200).replace(/\n/g, ' ');
203
- }
204
90
  // ---------- defaults ----------
205
91
  function defaultExec(cmd, opts) {
206
92
  const { execSync } = require('node:child_process');
@@ -252,228 +138,6 @@ async function defaultDryBoot() {
252
138
  }
253
139
  }
254
140
  }
255
- async function defaultFetchLLM(prompt) {
256
- const cfg = await resolveLLMConfig();
257
- if (!cfg.key)
258
- throw new Error('no api key — set DEEPSEEK_API_KEY / OMNI_ROUTE_API_KEY / OPENCODE_GO_API_KEY / OPENAI_API_KEY or ~/.dsh/.credentials.yaml or AI_API_KEY');
259
- // Build URLs from cfg.url (may be base or full endpoint). Support any provider/model — try completions then responses.
260
- const base = cfg.url.replace(/\/v1\/(chat\/completions|responses).*/, '/v1').replace(/\/$/, '');
261
- const completionsUrl = cfg.url.includes('/chat/completions') ? cfg.url : (cfg.url.includes('/responses') ? cfg.url.replace('/responses', '/chat/completions') : `${base}/chat/completions`);
262
- const responsesUrl = cfg.url.includes('/responses') ? cfg.url : `${base}/responses`;
263
- // Try openai-completions first (works for deepseek-v4, openai, openrouter, omni-route completions)
264
- try {
265
- const res = await fetch(completionsUrl, {
266
- method: 'POST',
267
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${cfg.key}` },
268
- body: JSON.stringify({
269
- model: cfg.model,
270
- temperature: 0.2,
271
- ...(cfg.reasoningEffort ? { reasoning_effort: cfg.reasoningEffort, reasoningEffort: cfg.reasoningEffort } : {}),
272
- messages: [
273
- { role: 'system', content: 'You are a systematic-debugging agent for DSH Web resilience. Follow the 4 phases: root cause, pattern analysis, hypothesis, implementation. Always propose minimal single-file fix.' },
274
- { role: 'user', content: prompt },
275
- ],
276
- }),
277
- });
278
- if (res.ok) {
279
- const j = await res.json();
280
- const content = j.choices?.[0]?.message?.content;
281
- if (content)
282
- return content;
283
- // Some providers return different shape but still ok — return stringified
284
- return JSON.stringify(j);
285
- }
286
- // If completions fails, fall through to responses for providers that use openai-responses (e.g. opencode-go muse-spark)
287
- const txt = await res.text().catch(() => '');
288
- // Only fall through for 4xx/5xx that likely indicate wrong API — otherwise throw
289
- if (res.status >= 400 && res.status < 600) {
290
- // try responses as fallback for any provider/model
291
- }
292
- else {
293
- throw new Error(`LLM ${res.status} ${txt}`);
294
- }
295
- }
296
- catch (e) {
297
- // Network error — if completionsUrl was tried and failed, try responses as generic fallback
298
- if (!e.message?.includes('LLM ')) {
299
- // will try responses below
300
- }
301
- else {
302
- throw e;
303
- }
304
- }
305
- // Fallback: try openai-responses (used by opencode-go muse-spark, minimax-m3, qwen3.7, etc.) — works for any provider that supports it
306
- const res2 = await fetch(responsesUrl, {
307
- method: 'POST',
308
- headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${cfg.key}` },
309
- body: JSON.stringify({
310
- model: cfg.model,
311
- input: [
312
- { role: 'system', content: 'You are a systematic-debugging agent for DSH Web resilience. Follow the 4 phases: root cause, pattern analysis, hypothesis, implementation. Always propose minimal single-file fix.' },
313
- { role: 'user', content: prompt },
314
- ],
315
- max_output_tokens: 4000,
316
- ...(cfg.reasoningEffort ? { reasoning: { effort: cfg.reasoningEffort } } : { reasoning: { effort: 'low' } }),
317
- }),
318
- });
319
- if (!res2.ok)
320
- throw new Error(`LLM ${res2.status} ${await res2.text().catch(() => '')}`);
321
- const j2 = await res2.json();
322
- // Extract text from responses output (opencode-go) or completions fallback
323
- if (j2.output) {
324
- const msg = j2.output.find((o) => o.type === 'message' && o.content?.[0]?.text);
325
- if (msg)
326
- return msg.content[0].text;
327
- }
328
- return j2.choices?.[0]?.message?.content ?? JSON.stringify(j2);
329
- }
330
- async function resolveLLMConfig() {
331
- // Custom AI provider: env overrides, then settings.yaml (llm-pi-ai providers, DeepSeek suggested setup), then settings.json, then defaults
332
- // Supports any provider/model the user configures via DeepSeek harness (opencode-go, omni-route, deepseek, openrouter, etc.)
333
- let url = process.env.AI_API_URL ??
334
- process.env.DEAPSEEK_API_URL ??
335
- process.env.DEEPSEEK_API_URL ??
336
- process.env.OPENAI_API_BASE ??
337
- process.env.OMNI_ROUTE_API_URL ??
338
- process.env.OPENCODE_API_URL ??
339
- null;
340
- // Model: AI_MODEL / DEEPSEEK_MODEL / settings.json domains.supervisor.model -> domains.review.model / default
341
- // Supervisor has its own picker; falls back to review model for backward compat, then DSH default.
342
- let model = process.env.AI_MODEL ?? process.env.DEEPSEEK_MODEL ?? process.env.OPENAI_MODEL ?? null;
343
- // reasoningEffort: env first, then supervisor -> review, then settings.yaml fallback
344
- let reasoningEffort = (process.env.AI_REASONING_EFFORT ?? process.env.REASONING_EFFORT ?? null) ?? undefined;
345
- if (!model || !reasoningEffort) {
346
- try {
347
- const { readFileSync } = await import('node:fs');
348
- const { homedir } = await import('node:os');
349
- const settingsPath = `${homedir()}/.dsh/maestro/settings.json`;
350
- const raw = readFileSync(settingsPath, 'utf-8');
351
- const j = JSON.parse(raw);
352
- // Prefer supervisor model, fall back to review model (so old installs keep working)
353
- if (!model) {
354
- const sup = j?.domains?.supervisor?.model?.model ?? j?.domains?.supervisor?.model;
355
- const rev = j?.domains?.review?.model?.model ?? j?.domains?.review?.model;
356
- let m = null;
357
- if (typeof sup === 'string')
358
- m = sup;
359
- else if (sup?.model && typeof sup.model === 'string')
360
- m = sup.model;
361
- else if (typeof rev === 'string')
362
- m = rev;
363
- else if (rev?.model && typeof rev.model === 'string')
364
- m = rev.model;
365
- if (typeof m === 'string' && m.trim() !== '')
366
- model = m;
367
- }
368
- if (!reasoningEffort) {
369
- // config-lib validator: model is {provider, model, reasoningEffort?} inside domains.supervisor.model / domains.review.model
370
- // Support both nested object and flat legacy fallback for future compat
371
- const supObj = j?.domains?.supervisor?.model;
372
- const revObj = j?.domains?.review?.model;
373
- const supEff = typeof supObj === 'object' && supObj !== null ? supObj.reasoningEffort : undefined;
374
- const supFlat = j?.domains?.supervisor?.reasoningEffort;
375
- const revEff = typeof revObj === 'object' && revObj !== null ? revObj.reasoningEffort : undefined;
376
- const revFlat = j?.domains?.review?.reasoningEffort;
377
- const candidate = supEff ?? supFlat ?? revEff ?? revFlat;
378
- if (typeof candidate === 'string' && candidate.trim() !== '')
379
- reasoningEffort = candidate.trim();
380
- }
381
- }
382
- catch { }
383
- }
384
- if (!model)
385
- model = 'deepseek-chat';
386
- // Also try settings.yaml for reasoningEffort if still missing (e.g., llm-pi-ai default reasoning)
387
- if (!reasoningEffort) {
388
- try {
389
- const { readFileSync } = await import('node:fs');
390
- const { homedir } = await import('node:os');
391
- const yamlPath = `${homedir()}/.dsh/settings.yaml`;
392
- const yaml = readFileSync(yamlPath, 'utf-8');
393
- // Look for reasoningEffort near the model id in provider blocks (future-proof; currently not in yaml)
394
- const re = new RegExp(`id:\\s*${model}[\\s\\S]{0,200}reasoningEffort:\\s*(\\S+)`, 'm');
395
- const m = yaml.match(re);
396
- if (m) {
397
- const v = m[1].trim().replace(/^["']|["']$/g, '');
398
- if (v)
399
- reasoningEffort = v;
400
- }
401
- }
402
- catch { }
403
- }
404
- // If url not set via env, try to resolve from ~/.dsh/settings.yaml llm-pi-ai providers (DeepSeek suggested setup)
405
- if (!url) {
406
- try {
407
- const { readFileSync } = await import('node:fs');
408
- const { homedir } = await import('node:os');
409
- const yamlPath = `${homedir()}/.dsh/settings.yaml`;
410
- const yaml = readFileSync(yamlPath, 'utf-8');
411
- // Find provider that owns the current model (e.g. muse-spark -> opencode-go)
412
- // settings.yaml structure: llm-pi-ai: providers: <provider>: { baseURL, models: [{id}] }
413
- // Use regex to extract provider blocks
414
- const providerBlocks = [...yaml.matchAll(/^ {4}(\S+):\s*\n([\s\S]*?)(?=^ {4}\S+:|\n\S)/gm)];
415
- for (const [, provider, block] of providerBlocks) {
416
- if (block.includes(`id: ${model}`)) {
417
- const m = block.match(/baseURL:\s*(\S+)/);
418
- if (m) {
419
- url = m[1].trim();
420
- break;
421
- }
422
- }
423
- }
424
- // Also check agent-default-model provider
425
- if (!url) {
426
- const defM = yaml.match(/agent-default-model:\s*\n\s*provider:\s*(\S+)/);
427
- if (defM) {
428
- const prov = defM[1].trim();
429
- const provBlock = yaml.match(new RegExp(`^ {4}${prov}:\\s*\\n([\\s\\S]*?)(?=^ {4}\\S+:|\\n\\S)`, 'm'));
430
- if (provBlock) {
431
- const m = provBlock[1].match(/baseURL:\s*(\S+)/);
432
- if (m)
433
- url = m[1].trim();
434
- }
435
- }
436
- }
437
- }
438
- catch { }
439
- }
440
- if (!url)
441
- url = 'https://api.deepseek.com/v1/chat/completions';
442
- // Key: AI_API_KEY / DEEPSEEK_API_KEY / OMNI_ROUTE_API_KEY / OPENCODE_GO_API_KEY / OPENAI_API_KEY
443
- let key = process.env.AI_API_KEY ??
444
- process.env.DEEPSEEK_API_KEY ??
445
- process.env.OMNI_ROUTE_API_KEY ??
446
- process.env.OPENCODE_GO_API_KEY ??
447
- process.env.OPENAI_API_KEY ??
448
- null;
449
- if (!key) {
450
- try {
451
- const { readFileSync } = await import('node:fs');
452
- const { homedir } = await import('node:os');
453
- const p = `${homedir()}/.dsh/.credentials.yaml`;
454
- const content = readFileSync(p, 'utf-8');
455
- const keys = ['AI_API_KEY', 'DEEPSEEK_API_KEY', 'OMNI_ROUTE_API_KEY', 'OPENCODE_GO_API_KEY', 'OPENAI_API_KEY'];
456
- for (const k of keys) {
457
- const m = content.match(new RegExp(`${k}:\\s*["']?([^"'\\n]+)["']?`));
458
- if (m) {
459
- key = m[1].trim();
460
- break;
461
- }
462
- }
463
- }
464
- catch { }
465
- }
466
- // If url is base without /v1/chat/completions, append
467
- let finalUrl = url;
468
- if (!url.includes('/v1/') && !url.includes('/chat/completions')) {
469
- finalUrl = url.replace(/\/$/, '') + '/v1/chat/completions';
470
- }
471
- return { key, url: finalUrl, model, ...(reasoningEffort ? { reasoningEffort } : {}) };
472
- }
473
- async function resolveApiKey() {
474
- const cfg = await resolveLLMConfig();
475
- return cfg.key;
476
- }
477
141
  export function _resetDebugAgentForTest() {
478
142
  lastRun = 0;
479
143
  attempts = 0;
package/lib/plugin.d.ts CHANGED
@@ -7,11 +7,64 @@
7
7
  */
8
8
  import { findInterrupted as defaultFindInterrupted, findDanglingOpenTurns as defaultFindDanglingOpenTurns } from './resume.js';
9
9
  import type { RestartIntent } from './intents.js';
10
+ import { runSessionHealthCheck } from './session-health.js';
11
+ export * from './resume-tools.js';
10
12
  export declare const inject: readonly ["sessions", "agents", "connection", "tools", "skills"];
11
13
  export interface SupervisorPluginConfig {
12
14
  autoResumeWithin?: number | string;
13
15
  autoResumeEnabled?: boolean;
16
+ sessionLogRoot?: string;
17
+ resumeCoreToolPolicy?: ResumeCoreToolPolicy;
14
18
  }
19
+ /**
20
+ * C2 — mitigation policy when a resumed session's post-resume probe (C1)
21
+ * reports a core tool (bash) missing from its SCOPED tool view:
22
+ * - 'warn': notify the operator once + inject a "System:" inventory message
23
+ * into the session telling the model which tools it CAN still call
24
+ * (default — the loss is real, but the session may still be usable).
25
+ * - 'park': additionally record the session id in the park set (exposed via
26
+ * maestro_resume_tool_health / /dsh-maestro-supervisor-resume-tool-health)
27
+ * and flag the notify with "(manual reopen required)" — the operator must
28
+ * reopen the session fresh because the core-tool surface is not guaranteed.
29
+ */
30
+ export type ResumeCoreToolPolicy = 'warn' | 'park';
31
+ /**
32
+ * Core tools that must be visible on a resumed session. Part C targets the
33
+ * post-restart bash loss (`Error: unknown tool "bash"`); extend this list to
34
+ * widen the probe (e.g. 'cordis_inspect_query').
35
+ */
36
+ export declare const CRITICAL_TOOLS: readonly ["bash"];
37
+ /** Minimal ToolRegistry surface the resume tool-view probe reads. */
38
+ export interface ToolsLike {
39
+ get?(name: string, scope?: unknown): unknown;
40
+ schemas?(scope?: unknown): {
41
+ name?: string;
42
+ }[];
43
+ }
44
+ /** Caller-visible result of the post-resume tool-view probe. */
45
+ export interface ToolViewProbe {
46
+ missing: string[];
47
+ visible: number;
48
+ }
49
+ export type ToolViewProbeFn = (tools: ToolsLike | undefined, scope: string, logger?: {
50
+ info?: (msg: string) => void;
51
+ }) => ToolViewProbe;
52
+ export type ToolScopeResolver = (ctx: any, sessionId: string) => string;
53
+ /** Default: the resumed agent's tool scope is its top-level session id. */
54
+ export declare const defaultResolveToolScope: ToolScopeResolver;
55
+ /**
56
+ * Snapshot one session's visible tool view for the journal: which CRITICAL_TOOLS
57
+ * are missing from the SCOPED registry (not the global view) and how many tools
58
+ * are visible. When the tools service is absent or lacks `get`, the probe is
59
+ * skipped and reports no missing tools. The log line is the Part D trigger —
60
+ * `bash=false` at resume marks the loss the moment it happens.
61
+ * @param tools - the harness ToolRegistry service, or undefined when unavailable.
62
+ * @param scope - the session's tool scope (defaults to the top-level session id).
63
+ * @param logger - optional ctx logger; the probe writes its line when present.
64
+ */
65
+ export declare function probeToolView(tools: ToolsLike | undefined, scope: string, logger?: {
66
+ info?: (msg: string) => void;
67
+ }): ToolViewProbe;
15
68
  export declare function runAutoResume(ctx: any, opts?: {
16
69
  findInterrupted?: typeof defaultFindInterrupted;
17
70
  findDanglingOpenTurns?: typeof defaultFindDanglingOpenTurns;
@@ -21,10 +74,17 @@ export declare function runAutoResume(ctx: any, opts?: {
21
74
  export declare function resumeInterrupted(ctx: any, ids: string[], deps?: {
22
75
  readIntent?: (id: string) => RestartIntent | undefined;
23
76
  consumeIntent?: (id: string) => void;
77
+ probeToolView?: ToolViewProbeFn;
78
+ resolveToolScope?: ToolScopeResolver;
79
+ notify?: (line: string) => Promise<void>;
80
+ injectSessionMessage?: (sessionId: string, content: string) => unknown;
81
+ config?: SupervisorPluginConfig;
24
82
  }): Promise<string[]>;
25
83
  export declare function createResumeRpcHandler(ctx: any, opts?: {
26
84
  resumeInterrupted?: typeof resumeInterrupted;
27
85
  config?: SupervisorPluginConfig;
86
+ notify?: (line: string) => Promise<void>;
87
+ injectSessionMessage?: (sessionId: string, content: string) => unknown;
28
88
  }): (endpoint: string, payload: unknown, _signal: AbortSignal) => Promise<{
29
89
  ok: boolean;
30
90
  value: import("./resume.js").ResumeResult;
@@ -43,4 +103,37 @@ export declare function createResumeRpcHandler(ctx: any, opts?: {
43
103
  };
44
104
  error?: undefined;
45
105
  }>;
106
+ /**
107
+ * Loopback RPC handler for /dsh-maestro-supervisor-session-health. Runs the
108
+ * A1 session-log health check over the resolved root with repair on and
109
+ * quarantine off (mirrors the safe-restart pre-flight — single-frame logs get
110
+ * re-encoded, corrupt logs stay in place and are only counted). Same
111
+ * `{ ok, value | error }` envelope shape as the resume handler.
112
+ */
113
+ export declare function createSessionHealthRpcHandler(ctx: any, deps?: {
114
+ run?: typeof runSessionHealthCheck;
115
+ config?: SupervisorPluginConfig;
116
+ }): (_endpoint: string, payload: unknown, _signal: AbortSignal) => Promise<{
117
+ ok: boolean;
118
+ value: {
119
+ fixed: number;
120
+ quarantined: number;
121
+ remaining: number;
122
+ };
123
+ error?: undefined;
124
+ } | {
125
+ ok: boolean;
126
+ error: {
127
+ code: string;
128
+ message: any;
129
+ };
130
+ value?: undefined;
131
+ }>;
132
+ /**
133
+ * Register the session-health RPC handle (loopback authority) and the
134
+ * maestro_session_health host tool. Fail-safe like the other registrations:
135
+ * any registration error is logged, never thrown, and the returned disposer
136
+ * unregisters everything that did succeed.
137
+ */
138
+ export declare function registerSessionHealthService(ctx: any, config?: SupervisorPluginConfig): () => void;
46
139
  export declare function apply(ctx: any, config?: SupervisorPluginConfig): void;