@ddtcorex/dsh-maestro-supervisor 0.2.0 → 0.3.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.
@@ -0,0 +1,14 @@
1
+ export interface DebugAgentOpts {
2
+ reportPath: string;
3
+ health: {
4
+ error?: string;
5
+ httpCode?: number;
6
+ };
7
+ attempts?: number;
8
+ cooldownMs?: number;
9
+ }
10
+ export declare function runDebugAgent(opts: DebugAgentOpts): Promise<{
11
+ fixed: boolean;
12
+ reason: string;
13
+ }>;
14
+ export declare function _resetDebugAgentForTest(): void;
@@ -0,0 +1,23 @@
1
+ let lastRun = 0;
2
+ let attempts = 0;
3
+ const MAX_ATTEMPTS = 3;
4
+ export async function runDebugAgent(opts) {
5
+ const now = Date.now();
6
+ const cooldownMs = opts.cooldownMs ?? 10 * 60 * 1000;
7
+ if (now - lastRun < cooldownMs) {
8
+ return { fixed: false, reason: 'cooldown' };
9
+ }
10
+ if (attempts >= MAX_ATTEMPTS) {
11
+ return { fixed: false, reason: 'max attempts' };
12
+ }
13
+ lastRun = now;
14
+ attempts++;
15
+ // Stub: Phase 3 full LLM wiring will use systematic-debugging preset
16
+ // For now just log and return not fixed — supervisor will still resume sessions
17
+ // Real implementation: spawn subagent via @ddtcorex/dsh-maestro-notifier or openai
18
+ return { fixed: false, reason: `stub — would debug ${opts.reportPath} (attempt ${attempts})` };
19
+ }
20
+ export function _resetDebugAgentForTest() {
21
+ lastRun = 0;
22
+ attempts = 0;
23
+ }
@@ -0,0 +1,5 @@
1
+ export interface ResumeResult {
2
+ scanned: number;
3
+ interrupted: string[];
4
+ }
5
+ export declare function findInterrupted(dshHome?: string): Promise<ResumeResult>;
package/lib/resume.js ADDED
@@ -0,0 +1,41 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import * as os from 'node:os';
4
+ export async function findInterrupted(dshHome) {
5
+ const home = dshHome ?? path.join(os.homedir(), '.dsh');
6
+ const sessionsRoot = path.join(home, 'sessions');
7
+ let scanned = 0;
8
+ const interrupted = [];
9
+ try {
10
+ const groups = fs.readdirSync(sessionsRoot, { withFileTypes: true });
11
+ for (const g of groups) {
12
+ if (!g.isDirectory())
13
+ continue;
14
+ const groupPath = path.join(sessionsRoot, g.name);
15
+ const sessions = fs.readdirSync(groupPath, { withFileTypes: true });
16
+ for (const s of sessions) {
17
+ if (!s.isDirectory())
18
+ continue;
19
+ scanned++;
20
+ const zstdPath = path.join(groupPath, s.name, 'session.jsonl.zstd');
21
+ const jsonlPath = path.join(groupPath, s.name, 'session.jsonl');
22
+ try {
23
+ let content = '';
24
+ if (fs.existsSync(zstdPath)) {
25
+ const { execSync } = await import('node:child_process');
26
+ content = execSync(`zstd -d -c ${JSON.stringify(zstdPath)} 2>/dev/null | tail -5`, { encoding: 'utf-8' });
27
+ }
28
+ else if (fs.existsSync(jsonlPath)) {
29
+ content = fs.readFileSync(jsonlPath, 'utf-8').slice(-5000);
30
+ }
31
+ if (content.toLowerCase().includes('interrupted')) {
32
+ interrupted.push(`${g.name}/${s.name}`);
33
+ }
34
+ }
35
+ catch { }
36
+ }
37
+ }
38
+ }
39
+ catch { }
40
+ return { scanned, interrupted };
41
+ }
package/lib/supervisor.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { runDebugAgent } from './debug-agent.js';
2
+ import { findInterrupted } from './resume.js';
1
3
  export class Supervisor {
2
4
  deps;
3
5
  lastRollback = 0;
@@ -20,6 +22,16 @@ export class Supervisor {
20
22
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
21
23
  const reportPath = await this.deps.writeReport({ ts, health, action: `degraded — ${health.error ?? 'plugin'}` }).catch(() => '');
22
24
  await this.deps.notify(`DEGRADED: ${health.error ?? 'plugin'} (report: ${reportPath})`).catch(() => { });
25
+ // Phase 3: fire-and-forget debug + resume (never block tick, skip in test)
26
+ if (!process.env.VITEST) {
27
+ void runDebugAgent({ reportPath, health }).catch(() => { });
28
+ setTimeout(() => {
29
+ findInterrupted().then(r => {
30
+ if (r.interrupted.length)
31
+ this.deps.notify(`RESUME: ${r.interrupted.length} interrupted sessions (${r.interrupted.slice(0, 3).join(', ')})`).catch(() => { });
32
+ }).catch(() => { });
33
+ }, 0);
34
+ }
23
35
  }
24
36
  catch { }
25
37
  return;
@@ -53,6 +65,15 @@ export class Supervisor {
53
65
  const reportPath = await this.deps.writeReport({ ts, health, action: `rollback — ${health.error ?? 'down'}` }).catch(() => '');
54
66
  await this.deps.rollback();
55
67
  await this.deps.notify(`CRASH detected → rollback (report: ${reportPath}, error: ${health.error ?? 'down'})`).catch(() => { });
68
+ if (!process.env.VITEST) {
69
+ void runDebugAgent({ reportPath, health }).catch(() => { });
70
+ setTimeout(() => {
71
+ findInterrupted().then(r => {
72
+ if (r.interrupted.length)
73
+ this.deps.notify(`RESUME: ${r.interrupted.length} interrupted sessions`).catch(() => { });
74
+ }).catch(() => { });
75
+ }, 0);
76
+ }
56
77
  }
57
78
  finally {
58
79
  this.rollingBack = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddtcorex/dsh-maestro-supervisor",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Supervisor daemon for DSH Web resilience — auto-detect crashes, rollback to LKG, report",
5
5
  "type": "module",
6
6
  "bin": {