@stoneforge/smithy 1.1.0 → 1.4.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 +109 -18
- package/dist/cli/commands/agent.js +10 -10
- package/dist/cli/commands/agent.js.map +1 -1
- package/dist/cli/commands/pool.d.ts.map +1 -1
- package/dist/cli/commands/pool.js +33 -16
- package/dist/cli/commands/pool.js.map +1 -1
- package/dist/cli/commands/serve.d.ts +4 -16
- package/dist/cli/commands/serve.d.ts.map +1 -1
- package/dist/cli/commands/serve.js +0 -1
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/director.md +2 -2
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +6 -3
- package/dist/prompts/index.js.map +1 -1
- package/dist/prompts/persistent-worker.md +1 -1
- package/dist/prompts/steward-base.md +4 -4
- package/dist/prompts/steward-recovery.md +113 -0
- package/dist/prompts/worker.md +12 -1
- package/dist/providers/claude/headless.d.ts +2 -0
- package/dist/providers/claude/headless.d.ts.map +1 -1
- package/dist/providers/claude/headless.js +8 -0
- package/dist/providers/claude/headless.js.map +1 -1
- package/dist/providers/claude/index.js +1 -1
- package/dist/providers/claude/index.js.map +1 -1
- package/dist/runtime/session-manager.d.ts +22 -2
- package/dist/runtime/session-manager.d.ts.map +1 -1
- package/dist/runtime/session-manager.js +74 -16
- package/dist/runtime/session-manager.js.map +1 -1
- package/dist/runtime/spawner.d.ts.map +1 -1
- package/dist/runtime/spawner.js +10 -0
- package/dist/runtime/spawner.js.map +1 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +3 -1
- package/dist/server/config.js.map +1 -1
- package/dist/server/daemon-state.d.ts.map +1 -1
- package/dist/server/daemon-state.js +5 -3
- package/dist/server/daemon-state.js.map +1 -1
- package/dist/server/events-websocket.d.ts.map +1 -1
- package/dist/server/events-websocket.js +7 -5
- package/dist/server/events-websocket.js.map +1 -1
- package/dist/server/formatters.d.ts +16 -2
- package/dist/server/formatters.d.ts.map +1 -1
- package/dist/server/formatters.js +23 -2
- package/dist/server/formatters.js.map +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +16 -12
- package/dist/server/index.js.map +1 -1
- package/dist/server/lsp-websocket.d.ts.map +1 -1
- package/dist/server/lsp-websocket.js +10 -8
- package/dist/server/lsp-websocket.js.map +1 -1
- package/dist/server/routes/agents.d.ts.map +1 -1
- package/dist/server/routes/agents.js +81 -15
- package/dist/server/routes/agents.js.map +1 -1
- package/dist/server/routes/daemon.d.ts.map +1 -1
- package/dist/server/routes/daemon.js +6 -4
- package/dist/server/routes/daemon.js.map +1 -1
- package/dist/server/routes/events.d.ts.map +1 -1
- package/dist/server/routes/events.js +4 -2
- package/dist/server/routes/events.js.map +1 -1
- package/dist/server/routes/extensions.d.ts.map +1 -1
- package/dist/server/routes/extensions.js +9 -7
- package/dist/server/routes/extensions.js.map +1 -1
- package/dist/server/routes/index.d.ts +1 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +1 -0
- package/dist/server/routes/index.js.map +1 -1
- package/dist/server/routes/plugins.d.ts.map +1 -1
- package/dist/server/routes/plugins.js +6 -4
- package/dist/server/routes/plugins.js.map +1 -1
- package/dist/server/routes/pools.d.ts.map +1 -1
- package/dist/server/routes/pools.js +26 -7
- package/dist/server/routes/pools.js.map +1 -1
- package/dist/server/routes/scheduler.d.ts.map +1 -1
- package/dist/server/routes/scheduler.js +9 -7
- package/dist/server/routes/scheduler.js.map +1 -1
- package/dist/server/routes/sessions.d.ts.map +1 -1
- package/dist/server/routes/sessions.js +17 -15
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/settings.d.ts +10 -0
- package/dist/server/routes/settings.d.ts.map +1 -0
- package/dist/server/routes/settings.js +65 -0
- package/dist/server/routes/settings.js.map +1 -0
- package/dist/server/routes/tasks.d.ts.map +1 -1
- package/dist/server/routes/tasks.js +54 -31
- package/dist/server/routes/tasks.js.map +1 -1
- package/dist/server/routes/upload.d.ts.map +1 -1
- package/dist/server/routes/upload.js +3 -1
- package/dist/server/routes/upload.js.map +1 -1
- package/dist/server/routes/workflows.d.ts.map +1 -1
- package/dist/server/routes/workflows.js +17 -15
- package/dist/server/routes/workflows.js.map +1 -1
- package/dist/server/routes/workspace-files.d.ts.map +1 -1
- package/dist/server/routes/workspace-files.js +11 -9
- package/dist/server/routes/workspace-files.js.map +1 -1
- package/dist/server/routes/worktrees.d.ts.map +1 -1
- package/dist/server/routes/worktrees.js +6 -4
- package/dist/server/routes/worktrees.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +10 -8
- package/dist/server/server.js.map +1 -1
- package/dist/server/services/lsp-manager.d.ts.map +1 -1
- package/dist/server/services/lsp-manager.js +15 -13
- package/dist/server/services/lsp-manager.js.map +1 -1
- package/dist/server/services.d.ts +2 -2
- package/dist/server/services.d.ts.map +1 -1
- package/dist/server/services.js +41 -13
- package/dist/server/services.js.map +1 -1
- package/dist/server/static.d.ts.map +1 -1
- package/dist/server/static.js +3 -1
- package/dist/server/static.js.map +1 -1
- package/dist/server/websocket.d.ts.map +1 -1
- package/dist/server/websocket.js +6 -4
- package/dist/server/websocket.js.map +1 -1
- package/dist/services/agent-pool-service.d.ts.map +1 -1
- package/dist/services/agent-pool-service.js +7 -8
- package/dist/services/agent-pool-service.js.map +1 -1
- package/dist/services/agent-registry.d.ts +8 -1
- package/dist/services/agent-registry.d.ts.map +1 -1
- package/dist/services/agent-registry.js +27 -4
- package/dist/services/agent-registry.js.map +1 -1
- package/dist/services/dispatch-daemon.d.ts +64 -2
- package/dist/services/dispatch-daemon.d.ts.map +1 -1
- package/dist/services/dispatch-daemon.js +387 -59
- package/dist/services/dispatch-daemon.js.map +1 -1
- package/dist/services/index.d.ts +1 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +6 -11
- package/dist/services/index.js.map +1 -1
- package/dist/services/merge-steward-service.d.ts.map +1 -1
- package/dist/services/merge-steward-service.js +6 -4
- package/dist/services/merge-steward-service.js.map +1 -1
- package/dist/services/settings-service.d.ts +56 -0
- package/dist/services/settings-service.d.ts.map +1 -0
- package/dist/services/settings-service.js +92 -0
- package/dist/services/settings-service.js.map +1 -0
- package/dist/services/steward-scheduler.d.ts +37 -5
- package/dist/services/steward-scheduler.d.ts.map +1 -1
- package/dist/services/steward-scheduler.js +224 -41
- package/dist/services/steward-scheduler.js.map +1 -1
- package/dist/services/task-assignment-service.d.ts.map +1 -1
- package/dist/services/task-assignment-service.js +3 -0
- package/dist/services/task-assignment-service.js.map +1 -1
- package/dist/testing/test-context.d.ts +1 -1
- package/dist/testing/test-context.d.ts.map +1 -1
- package/dist/types/agent-pool.d.ts +4 -0
- package/dist/types/agent-pool.d.ts.map +1 -1
- package/dist/types/agent-pool.js +8 -1
- package/dist/types/agent-pool.js.map +1 -1
- package/dist/types/agent.d.ts +37 -7
- package/dist/types/agent.d.ts.map +1 -1
- package/dist/types/agent.js +2 -2
- package/dist/types/agent.js.map +1 -1
- package/dist/types/role-definition.d.ts +1 -1
- package/dist/types/role-definition.js +1 -1
- package/dist/types/role-definition.js.map +1 -1
- package/dist/types/task-meta.d.ts +6 -0
- package/dist/types/task-meta.d.ts.map +1 -1
- package/dist/types/task-meta.js.map +1 -1
- package/dist/utils/logger.d.ts +66 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +133 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +7 -7
- package/web/assets/{index-R1cylSgw.js → index-8dBly5AJ.js} +697 -437
- package/web/assets/index-CNcjZKzg.css +32 -0
- package/web/assets/{utils-vendor-DaJ2Dubl.js → utils-vendor-B7jOGaxP.js} +1 -1
- package/web/index.html +3 -3
- package/dist/prompts/steward-health.md +0 -39
- package/dist/prompts/steward-ops.md +0 -28
- package/dist/prompts/steward-reminder.md +0 -26
- package/dist/services/health-steward-service.d.ts +0 -446
- package/dist/services/health-steward-service.d.ts.map +0 -1
- package/dist/services/health-steward-service.js +0 -866
- package/dist/services/health-steward-service.js.map +0 -1
- package/web/assets/index-DqP-_E4F.css +0 -32
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings Service
|
|
3
|
+
*
|
|
4
|
+
* Server-side key-value settings persisted to SQLite.
|
|
5
|
+
* Used for workspace-wide configuration that needs to be accessible server-side,
|
|
6
|
+
* such as default executable paths for agent providers.
|
|
7
|
+
*
|
|
8
|
+
* Settings are stored in the `settings` table as JSON-encoded values.
|
|
9
|
+
*/
|
|
10
|
+
import { createLogger } from '../utils/logger.js';
|
|
11
|
+
const logger = createLogger('settings-service');
|
|
12
|
+
/**
|
|
13
|
+
* Well-known setting keys
|
|
14
|
+
*/
|
|
15
|
+
export const SETTING_KEYS = {
|
|
16
|
+
AGENT_DEFAULTS: 'agentDefaults',
|
|
17
|
+
};
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Implementation
|
|
20
|
+
// ============================================================================
|
|
21
|
+
function dbToSetting(row) {
|
|
22
|
+
let parsedValue;
|
|
23
|
+
try {
|
|
24
|
+
parsedValue = JSON.parse(row.value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
parsedValue = row.value;
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
key: row.key,
|
|
31
|
+
value: parsedValue,
|
|
32
|
+
updatedAt: row.updated_at,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_AGENT_DEFAULTS = {
|
|
36
|
+
defaultExecutablePaths: {},
|
|
37
|
+
};
|
|
38
|
+
export function createSettingsService(storage) {
|
|
39
|
+
return {
|
|
40
|
+
getSetting(key) {
|
|
41
|
+
const row = storage.queryOne('SELECT key, value, updated_at FROM settings WHERE key = ?', [key]);
|
|
42
|
+
if (!row)
|
|
43
|
+
return undefined;
|
|
44
|
+
return dbToSetting(row);
|
|
45
|
+
},
|
|
46
|
+
setSetting(key, value) {
|
|
47
|
+
const jsonValue = JSON.stringify(value);
|
|
48
|
+
const updatedAt = new Date().toISOString();
|
|
49
|
+
storage.run('INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at', [key, jsonValue, updatedAt]);
|
|
50
|
+
logger.debug(`Setting updated: ${key}`);
|
|
51
|
+
return {
|
|
52
|
+
key,
|
|
53
|
+
value,
|
|
54
|
+
updatedAt,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
deleteSetting(key) {
|
|
58
|
+
const result = storage.run('DELETE FROM settings WHERE key = ?', [key]);
|
|
59
|
+
return result.changes > 0;
|
|
60
|
+
},
|
|
61
|
+
getAgentDefaults() {
|
|
62
|
+
const setting = this.getSetting(SETTING_KEYS.AGENT_DEFAULTS);
|
|
63
|
+
if (!setting) {
|
|
64
|
+
return { ...DEFAULT_AGENT_DEFAULTS };
|
|
65
|
+
}
|
|
66
|
+
// Validate shape — ensure defaultExecutablePaths exists and is an object
|
|
67
|
+
const value = setting.value;
|
|
68
|
+
const paths = value?.defaultExecutablePaths;
|
|
69
|
+
return {
|
|
70
|
+
defaultExecutablePaths: paths && typeof paths === 'object' && !Array.isArray(paths)
|
|
71
|
+
? paths
|
|
72
|
+
: {},
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
setAgentDefaults(defaults) {
|
|
76
|
+
// Validate that defaultExecutablePaths is a plain object of strings
|
|
77
|
+
const paths = defaults.defaultExecutablePaths ?? {};
|
|
78
|
+
const sanitized = {};
|
|
79
|
+
for (const [provider, path] of Object.entries(paths)) {
|
|
80
|
+
if (typeof path === 'string') {
|
|
81
|
+
sanitized[provider] = path;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const validated = {
|
|
85
|
+
defaultExecutablePaths: sanitized,
|
|
86
|
+
};
|
|
87
|
+
this.setSetting(SETTING_KEYS.AGENT_DEFAULTS, validated);
|
|
88
|
+
return validated;
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=settings-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-service.js","sourceRoot":"","sources":["../../src/services/settings-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { EntityId, Timestamp } from '@stoneforge/core';
|
|
18
18
|
import type { StewardTrigger, CronTrigger, EventTrigger } from '../types/index.js';
|
|
19
|
+
import type { SessionManager } from '../runtime/session-manager.js';
|
|
19
20
|
import type { AgentRegistry, AgentEntity } from './agent-registry.js';
|
|
20
21
|
import type { MergeStewardService } from './merge-steward-service.js';
|
|
21
|
-
import type { HealthStewardService } from './health-steward-service.js';
|
|
22
22
|
import type { DocsStewardService } from './docs-steward-service.js';
|
|
23
23
|
/**
|
|
24
24
|
* Result of a steward execution
|
|
@@ -326,22 +326,54 @@ export declare class StewardSchedulerImpl implements StewardScheduler {
|
|
|
326
326
|
* @param config - Optional configuration
|
|
327
327
|
*/
|
|
328
328
|
export declare function createStewardScheduler(agentRegistry: AgentRegistry, executor: StewardExecutor, config?: StewardSchedulerConfig): StewardScheduler;
|
|
329
|
+
/**
|
|
330
|
+
* Default idle timeout for steward sessions in milliseconds (2 minutes).
|
|
331
|
+
* If a steward session receives no events for this duration, it is
|
|
332
|
+
* assumed to be stuck and will be force-terminated.
|
|
333
|
+
*/
|
|
334
|
+
export declare const STEWARD_SESSION_DEFAULT_IDLE_TIMEOUT_MS = 120000;
|
|
335
|
+
/**
|
|
336
|
+
* Default maximum duration for steward sessions in milliseconds (30 minutes).
|
|
337
|
+
* Steward sessions exceeding this duration are force-terminated regardless
|
|
338
|
+
* of activity, as a safety net.
|
|
339
|
+
*/
|
|
340
|
+
export declare const STEWARD_SESSION_DEFAULT_MAX_DURATION_MS: number;
|
|
329
341
|
/**
|
|
330
342
|
* Dependencies required by the real steward executor.
|
|
331
343
|
*/
|
|
332
344
|
export interface StewardExecutorDeps {
|
|
333
345
|
mergeStewardService: MergeStewardService;
|
|
334
|
-
healthStewardService: HealthStewardService;
|
|
335
346
|
docsStewardService: DocsStewardService;
|
|
347
|
+
sessionManager: SessionManager;
|
|
348
|
+
projectRoot: string;
|
|
349
|
+
/**
|
|
350
|
+
* Optional callback to resolve a playbook (workflow template) by ID.
|
|
351
|
+
* Returns the playbook content as a markdown string for the steward prompt,
|
|
352
|
+
* or undefined if not found. Used when a custom steward has `playbookId` set.
|
|
353
|
+
*/
|
|
354
|
+
resolvePlaybookContent?: (playbookId: string) => Promise<string | undefined>;
|
|
355
|
+
/**
|
|
356
|
+
* Idle timeout in ms for spawned steward sessions (docs, custom).
|
|
357
|
+
* If no session events are received within this window, the session
|
|
358
|
+
* is force-terminated. Default: 120000 (2 minutes).
|
|
359
|
+
*/
|
|
360
|
+
stewardSessionIdleTimeoutMs?: number;
|
|
361
|
+
/**
|
|
362
|
+
* Maximum duration in ms for spawned steward sessions (docs, custom).
|
|
363
|
+
* Sessions exceeding this duration are force-terminated regardless of
|
|
364
|
+
* activity. Default: 1800000 (30 minutes).
|
|
365
|
+
*/
|
|
366
|
+
stewardSessionMaxDurationMs?: number;
|
|
336
367
|
}
|
|
337
368
|
/**
|
|
338
369
|
* Creates a steward executor that dispatches to the appropriate service
|
|
339
370
|
* based on the steward's focus.
|
|
340
371
|
*
|
|
341
372
|
* - 'merge' focus → MergeStewardService.processAllPending()
|
|
342
|
-
* - '
|
|
343
|
-
*
|
|
344
|
-
* -
|
|
373
|
+
* - 'docs' → spawns an agent session via sessionManager
|
|
374
|
+
*
|
|
375
|
+
* Session-based stewards check for an existing active session before spawning
|
|
376
|
+
* to prevent overlapping runs across cron ticks.
|
|
345
377
|
*
|
|
346
378
|
* Each case is wrapped in try/catch so one failing steward doesn't crash
|
|
347
379
|
* the scheduler.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steward-scheduler.d.ts","sourceRoot":"","sources":["../../src/services/steward-scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,YAAY,
|
|
1
|
+
{"version":3,"file":"steward-scheduler.d.ts","sourceRoot":"","sources":["../../src/services/steward-scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
import { EventEmitter } from 'node:events';
|
|
18
18
|
import { createTimestamp } from '@stoneforge/core';
|
|
19
19
|
import { isCronTrigger, isEventTrigger, } from '../types/index.js';
|
|
20
|
+
import { loadRolePrompt } from '../prompts/index.js';
|
|
20
21
|
import { getAgentMetadata } from './agent-registry.js';
|
|
22
|
+
import { createLogger } from '../utils/logger.js';
|
|
23
|
+
const logger = createLogger('steward-scheduler');
|
|
21
24
|
// ============================================================================
|
|
22
25
|
// Cron Schedule Utilities
|
|
23
26
|
// ============================================================================
|
|
@@ -172,6 +175,7 @@ export class StewardSchedulerImpl {
|
|
|
172
175
|
if (this.config.startImmediately) {
|
|
173
176
|
await this.registerAllStewards();
|
|
174
177
|
}
|
|
178
|
+
logger.info(`Started with ${this.cronJobs.size} cron job(s) and ${[...this.eventSubscriptions.values()].flat().length} event subscription(s)`);
|
|
175
179
|
}
|
|
176
180
|
async stop() {
|
|
177
181
|
if (!this.running) {
|
|
@@ -238,6 +242,9 @@ export class StewardSchedulerImpl {
|
|
|
238
242
|
});
|
|
239
243
|
}
|
|
240
244
|
}
|
|
245
|
+
const cronCount = triggers.filter(t => isCronTrigger(t)).length;
|
|
246
|
+
const eventCount = triggers.filter(t => isEventTrigger(t)).length;
|
|
247
|
+
logger.info(`Registered steward '${agent.name}' (${stewardId}) with ${cronCount} cron trigger(s) and ${eventCount} event trigger(s)`);
|
|
241
248
|
this.emitter.emit('steward:registered', stewardId);
|
|
242
249
|
return true;
|
|
243
250
|
}
|
|
@@ -282,6 +289,7 @@ export class StewardSchedulerImpl {
|
|
|
282
289
|
registered++;
|
|
283
290
|
}
|
|
284
291
|
}
|
|
292
|
+
logger.info(`Registered ${registered}/${stewards.length} steward(s)`);
|
|
285
293
|
return registered;
|
|
286
294
|
}
|
|
287
295
|
// ----------------------------------------
|
|
@@ -335,8 +343,8 @@ export class StewardSchedulerImpl {
|
|
|
335
343
|
const agent = await this.agentRegistry.getAgent(sub.stewardId);
|
|
336
344
|
if (agent) {
|
|
337
345
|
// Run execution asynchronously
|
|
338
|
-
this.runExecution(agent, sub.trigger, false, eventData).catch(() => {
|
|
339
|
-
|
|
346
|
+
this.runExecution(agent, sub.trigger, false, eventData).catch((error) => {
|
|
347
|
+
logger.error(`Event-triggered execution failed for steward '${sub.stewardName}':`, error);
|
|
340
348
|
});
|
|
341
349
|
triggered++;
|
|
342
350
|
}
|
|
@@ -449,27 +457,40 @@ export class StewardSchedulerImpl {
|
|
|
449
457
|
}
|
|
450
458
|
scheduleNextRun(job) {
|
|
451
459
|
const nextTime = this.getNextCronTime(job.trigger.schedule);
|
|
452
|
-
if (!nextTime)
|
|
460
|
+
if (!nextTime) {
|
|
461
|
+
logger.warn(`Failed to compute next run time for steward '${job.stewardName}' with schedule '${job.trigger.schedule}'`);
|
|
453
462
|
return;
|
|
463
|
+
}
|
|
454
464
|
job.nextRunAt = nextTime;
|
|
455
465
|
const delayMs = Math.max(0, nextTime.getTime() - Date.now());
|
|
466
|
+
logger.debug(`Scheduled next run for steward '${job.stewardName}' at ${nextTime.toISOString()} (in ${Math.round(delayMs / 1000)}s)`);
|
|
456
467
|
job.intervalId = setTimeout(async () => {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
468
|
+
try {
|
|
469
|
+
if (!this.running)
|
|
470
|
+
return;
|
|
471
|
+
if (job.isRunning) {
|
|
472
|
+
logger.warn(`Skipping overlapping execution for steward '${job.stewardName}'`);
|
|
473
|
+
return; // finally block will schedule the next run
|
|
474
|
+
}
|
|
475
|
+
logger.debug(`Cron firing for steward '${job.stewardName}' (schedule: ${job.trigger.schedule})`);
|
|
476
|
+
const agent = await this.agentRegistry.getAgent(job.stewardId);
|
|
477
|
+
if (!agent) {
|
|
478
|
+
logger.warn(`Agent not found for steward '${job.stewardId}', skipping cron execution`);
|
|
479
|
+
}
|
|
480
|
+
if (agent && this.running) {
|
|
481
|
+
const result = await this.runExecution(agent, job.trigger, false);
|
|
482
|
+
job.lastRunAt = createTimestamp();
|
|
483
|
+
logger.info(`Cron execution completed for steward '${job.stewardName}': success=${result.success}${result.error ? `, error=${result.error}` : ''}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
catch (error) {
|
|
487
|
+
logger.error(`Unhandled error in cron callback for steward '${job.stewardName}':`, error);
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
if (this.running) {
|
|
491
|
+
job.intervalId = undefined;
|
|
492
|
+
this.scheduleNextRun(job);
|
|
493
|
+
}
|
|
473
494
|
}
|
|
474
495
|
}, delayMs);
|
|
475
496
|
}
|
|
@@ -704,14 +725,112 @@ export class StewardSchedulerImpl {
|
|
|
704
725
|
export function createStewardScheduler(agentRegistry, executor, config) {
|
|
705
726
|
return new StewardSchedulerImpl(agentRegistry, executor, config);
|
|
706
727
|
}
|
|
728
|
+
// ============================================================================
|
|
729
|
+
// Steward Executor Factory
|
|
730
|
+
// ============================================================================
|
|
731
|
+
/**
|
|
732
|
+
* Default idle timeout for steward sessions in milliseconds (2 minutes).
|
|
733
|
+
* If a steward session receives no events for this duration, it is
|
|
734
|
+
* assumed to be stuck and will be force-terminated.
|
|
735
|
+
*/
|
|
736
|
+
export const STEWARD_SESSION_DEFAULT_IDLE_TIMEOUT_MS = 120_000;
|
|
737
|
+
/**
|
|
738
|
+
* Default maximum duration for steward sessions in milliseconds (30 minutes).
|
|
739
|
+
* Steward sessions exceeding this duration are force-terminated regardless
|
|
740
|
+
* of activity, as a safety net.
|
|
741
|
+
*/
|
|
742
|
+
export const STEWARD_SESSION_DEFAULT_MAX_DURATION_MS = 30 * 60 * 1000;
|
|
743
|
+
/**
|
|
744
|
+
* Sets up idle timeout and max duration monitoring for a spawned steward session.
|
|
745
|
+
* Watches for session activity via the session manager's event emitter and
|
|
746
|
+
* force-terminates the session if it goes idle or exceeds the max duration.
|
|
747
|
+
*
|
|
748
|
+
* @param sessionId - The session ID to monitor
|
|
749
|
+
* @param sessionEvents - The session's event emitter (from startSession)
|
|
750
|
+
* @param deps - Steward executor dependencies (for session manager and config)
|
|
751
|
+
* @param stewardName - Name of the steward (for logging)
|
|
752
|
+
*/
|
|
753
|
+
function monitorStewardSession(sessionId, sessionEvents, deps, stewardName) {
|
|
754
|
+
const idleTimeoutMs = deps.stewardSessionIdleTimeoutMs ?? STEWARD_SESSION_DEFAULT_IDLE_TIMEOUT_MS;
|
|
755
|
+
const maxDurationMs = deps.stewardSessionMaxDurationMs ?? STEWARD_SESSION_DEFAULT_MAX_DURATION_MS;
|
|
756
|
+
const startTime = Date.now();
|
|
757
|
+
let lastActivityAt = Date.now();
|
|
758
|
+
let cleanedUp = false;
|
|
759
|
+
// Update last activity on any session event
|
|
760
|
+
const onEvent = () => {
|
|
761
|
+
lastActivityAt = Date.now();
|
|
762
|
+
};
|
|
763
|
+
// Clean up watchers
|
|
764
|
+
const cleanup = () => {
|
|
765
|
+
if (cleanedUp)
|
|
766
|
+
return;
|
|
767
|
+
cleanedUp = true;
|
|
768
|
+
clearInterval(checkInterval);
|
|
769
|
+
sessionEvents.removeListener('event', onEvent);
|
|
770
|
+
sessionEvents.removeListener('exit', onExit);
|
|
771
|
+
sessionEvents.removeListener('status', onStatus);
|
|
772
|
+
};
|
|
773
|
+
// Session exited naturally — clean up watchers
|
|
774
|
+
const onExit = () => {
|
|
775
|
+
cleanup();
|
|
776
|
+
};
|
|
777
|
+
// Session status changed to terminated — clean up watchers
|
|
778
|
+
const onStatus = (status) => {
|
|
779
|
+
if (status === 'terminated') {
|
|
780
|
+
cleanup();
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
sessionEvents.on('event', onEvent);
|
|
784
|
+
sessionEvents.on('exit', onExit);
|
|
785
|
+
sessionEvents.on('status', onStatus);
|
|
786
|
+
// Periodically check idle time and max duration
|
|
787
|
+
const checkIntervalMs = Math.min(idleTimeoutMs / 2, 30_000);
|
|
788
|
+
const checkInterval = setInterval(() => {
|
|
789
|
+
const now = Date.now();
|
|
790
|
+
const idleMs = now - lastActivityAt;
|
|
791
|
+
const totalMs = now - startTime;
|
|
792
|
+
if (idleMs > idleTimeoutMs) {
|
|
793
|
+
logger.warn(`Steward '${stewardName}' session ${sessionId} idle for ${Math.round(idleMs / 1000)}s (timeout: ${Math.round(idleTimeoutMs / 1000)}s), force-terminating`);
|
|
794
|
+
cleanup();
|
|
795
|
+
deps.sessionManager.stopSession(sessionId, {
|
|
796
|
+
graceful: false,
|
|
797
|
+
reason: `Steward session idle for ${Math.round(idleMs / 1000)}s (timeout: ${Math.round(idleTimeoutMs / 1000)}s)`,
|
|
798
|
+
}).catch((err) => {
|
|
799
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
800
|
+
if (!msg.includes('not found')) {
|
|
801
|
+
logger.warn(`Failed to stop idle steward session ${sessionId}:`, err);
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (totalMs > maxDurationMs) {
|
|
807
|
+
logger.warn(`Steward '${stewardName}' session ${sessionId} exceeded max duration ${Math.round(maxDurationMs / 1000)}s, force-terminating`);
|
|
808
|
+
cleanup();
|
|
809
|
+
deps.sessionManager.stopSession(sessionId, {
|
|
810
|
+
graceful: false,
|
|
811
|
+
reason: `Steward session exceeded max duration (${Math.round(totalMs / 1000)}s)`,
|
|
812
|
+
}).catch((err) => {
|
|
813
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
814
|
+
if (!msg.includes('not found')) {
|
|
815
|
+
logger.warn(`Failed to stop over-duration steward session ${sessionId}:`, err);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
}, checkIntervalMs);
|
|
820
|
+
// Prevent the interval from keeping the process alive
|
|
821
|
+
if (checkInterval.unref) {
|
|
822
|
+
checkInterval.unref();
|
|
823
|
+
}
|
|
824
|
+
}
|
|
707
825
|
/**
|
|
708
826
|
* Creates a steward executor that dispatches to the appropriate service
|
|
709
827
|
* based on the steward's focus.
|
|
710
828
|
*
|
|
711
829
|
* - 'merge' focus → MergeStewardService.processAllPending()
|
|
712
|
-
* - '
|
|
713
|
-
*
|
|
714
|
-
* -
|
|
830
|
+
* - 'docs' → spawns an agent session via sessionManager
|
|
831
|
+
*
|
|
832
|
+
* Session-based stewards check for an existing active session before spawning
|
|
833
|
+
* to prevent overlapping runs across cron ticks.
|
|
715
834
|
*
|
|
716
835
|
* Each case is wrapped in try/catch so one failing steward doesn't crash
|
|
717
836
|
* the scheduler.
|
|
@@ -742,55 +861,119 @@ export function createStewardExecutor(deps) {
|
|
|
742
861
|
};
|
|
743
862
|
}
|
|
744
863
|
}
|
|
745
|
-
case '
|
|
864
|
+
case 'docs': {
|
|
746
865
|
try {
|
|
747
|
-
const
|
|
866
|
+
const stewardId = steward.id;
|
|
867
|
+
const activeSession = deps.sessionManager.getActiveSession(stewardId);
|
|
868
|
+
if (activeSession) {
|
|
869
|
+
return {
|
|
870
|
+
success: true,
|
|
871
|
+
output: `Steward '${steward.name}' already has active session ${activeSession.id}, skipping`,
|
|
872
|
+
durationMs: Date.now() - startTime,
|
|
873
|
+
itemsProcessed: 0,
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
const roleResult = loadRolePrompt('steward', focus, {
|
|
877
|
+
projectRoot: deps.projectRoot,
|
|
878
|
+
});
|
|
879
|
+
const initialPrompt = roleResult?.prompt ?? '';
|
|
880
|
+
const { session, events } = await deps.sessionManager.startSession(stewardId, {
|
|
881
|
+
workingDirectory: deps.projectRoot,
|
|
882
|
+
initialPrompt,
|
|
883
|
+
interactive: false,
|
|
884
|
+
});
|
|
885
|
+
// Monitor session for idle timeout and max duration
|
|
886
|
+
monitorStewardSession(session.id, events, deps, steward.name);
|
|
748
887
|
return {
|
|
749
888
|
success: true,
|
|
750
|
-
output: `
|
|
889
|
+
output: `Spawned ${focus} steward session ${session.id}`,
|
|
751
890
|
durationMs: Date.now() - startTime,
|
|
752
|
-
itemsProcessed:
|
|
891
|
+
itemsProcessed: 1,
|
|
753
892
|
};
|
|
754
893
|
}
|
|
755
894
|
catch (error) {
|
|
756
895
|
return {
|
|
757
896
|
success: false,
|
|
758
897
|
error: error instanceof Error ? error.message : String(error),
|
|
759
|
-
output:
|
|
898
|
+
output: `${focus} steward '${steward.name}' failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
760
899
|
durationMs: Date.now() - startTime,
|
|
761
900
|
itemsProcessed: 0,
|
|
762
901
|
};
|
|
763
902
|
}
|
|
764
903
|
}
|
|
765
|
-
case '
|
|
904
|
+
case 'custom': {
|
|
766
905
|
try {
|
|
767
|
-
const
|
|
906
|
+
const stewardId = steward.id;
|
|
907
|
+
const activeSession = deps.sessionManager.getActiveSession(stewardId);
|
|
908
|
+
if (activeSession) {
|
|
909
|
+
return {
|
|
910
|
+
success: true,
|
|
911
|
+
output: `Steward '${steward.name}' already has active session ${activeSession.id}, skipping`,
|
|
912
|
+
durationMs: Date.now() - startTime,
|
|
913
|
+
itemsProcessed: 0,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
// Build prompt from steward base + custom playbook
|
|
917
|
+
// Resolve playbook content: prefer playbookId (template reference), fall back to inline playbook
|
|
918
|
+
let playbook;
|
|
919
|
+
if (metadata?.playbookId && deps.resolvePlaybookContent) {
|
|
920
|
+
try {
|
|
921
|
+
playbook = await deps.resolvePlaybookContent(metadata.playbookId);
|
|
922
|
+
if (!playbook) {
|
|
923
|
+
logger.warn(`Custom steward '${steward.name}': playbook template ${metadata.playbookId} not found, falling back to inline playbook`);
|
|
924
|
+
playbook = metadata?.playbook;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
catch (err) {
|
|
928
|
+
logger.warn(`Custom steward '${steward.name}': failed to resolve playbook template ${metadata.playbookId}: ${err}, falling back to inline playbook`);
|
|
929
|
+
playbook = metadata?.playbook;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
playbook = metadata?.playbook;
|
|
934
|
+
}
|
|
935
|
+
if (!playbook) {
|
|
936
|
+
return {
|
|
937
|
+
success: false,
|
|
938
|
+
error: 'Custom steward has no playbook configured',
|
|
939
|
+
output: `Custom steward '${steward.name}' has no playbook configured`,
|
|
940
|
+
durationMs: Date.now() - startTime,
|
|
941
|
+
itemsProcessed: 0,
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
// Load the steward base prompt for shared context
|
|
945
|
+
const roleResult = loadRolePrompt('steward', undefined, {
|
|
946
|
+
projectRoot: deps.projectRoot,
|
|
947
|
+
});
|
|
948
|
+
const basePrompt = roleResult?.prompt ?? '';
|
|
949
|
+
// Combine base steward prompt with the custom playbook
|
|
950
|
+
const initialPrompt = basePrompt
|
|
951
|
+
? `${basePrompt}\n\n---\n\n## Custom Steward Playbook\n\n${playbook}`
|
|
952
|
+
: `## Custom Steward Playbook\n\n${playbook}`;
|
|
953
|
+
const { session, events } = await deps.sessionManager.startSession(stewardId, {
|
|
954
|
+
workingDirectory: deps.projectRoot,
|
|
955
|
+
initialPrompt,
|
|
956
|
+
interactive: false,
|
|
957
|
+
});
|
|
958
|
+
// Monitor session for idle timeout and max duration
|
|
959
|
+
monitorStewardSession(session.id, events, deps, steward.name);
|
|
768
960
|
return {
|
|
769
961
|
success: true,
|
|
770
|
-
output: `
|
|
962
|
+
output: `Spawned custom steward session ${session.id}`,
|
|
771
963
|
durationMs: Date.now() - startTime,
|
|
772
|
-
itemsProcessed:
|
|
964
|
+
itemsProcessed: 1,
|
|
773
965
|
};
|
|
774
966
|
}
|
|
775
967
|
catch (error) {
|
|
776
968
|
return {
|
|
777
969
|
success: false,
|
|
778
970
|
error: error instanceof Error ? error.message : String(error),
|
|
779
|
-
output: `
|
|
971
|
+
output: `Custom steward '${steward.name}' failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
780
972
|
durationMs: Date.now() - startTime,
|
|
781
973
|
itemsProcessed: 0,
|
|
782
974
|
};
|
|
783
975
|
}
|
|
784
976
|
}
|
|
785
|
-
case 'reminder':
|
|
786
|
-
case 'ops':
|
|
787
|
-
// These focus types don't have dedicated services yet
|
|
788
|
-
return {
|
|
789
|
-
success: true,
|
|
790
|
-
output: `Steward focus '${focus}' has no automated service — requires agent session`,
|
|
791
|
-
durationMs: Date.now() - startTime,
|
|
792
|
-
itemsProcessed: 0,
|
|
793
|
-
};
|
|
794
977
|
default:
|
|
795
978
|
return {
|
|
796
979
|
success: false,
|