@telora/daemon 0.14.2 → 0.14.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.
- package/build-info.json +2 -2
- package/dist/queries/schemas.d.ts +4 -4
- package/dist/queries/schemas.js +1 -1
- package/dist/queries/schemas.js.map +1 -1
- package/dist/spawner-lifecycle.d.ts +43 -0
- package/dist/spawner-lifecycle.d.ts.map +1 -0
- package/dist/spawner-lifecycle.js +152 -0
- package/dist/spawner-lifecycle.js.map +1 -0
- package/dist/spawner-stream-handlers.d.ts +67 -0
- package/dist/spawner-stream-handlers.d.ts.map +1 -0
- package/dist/spawner-stream-handlers.js +193 -0
- package/dist/spawner-stream-handlers.js.map +1 -0
- package/dist/spawner.d.ts +15 -36
- package/dist/spawner.d.ts.map +1 -1
- package/dist/spawner.js +29 -327
- package/dist/spawner.js.map +1 -1
- package/dist/strategy-completion-event.d.ts +29 -0
- package/dist/strategy-completion-event.d.ts.map +1 -0
- package/dist/strategy-completion-event.js +69 -0
- package/dist/strategy-completion-event.js.map +1 -0
- package/dist/strategy-completion.d.ts.map +1 -1
- package/dist/strategy-completion.js +27 -11
- package/dist/strategy-completion.js.map +1 -1
- package/dist/strategy-executor.d.ts +8 -45
- package/dist/strategy-executor.d.ts.map +1 -1
- package/dist/strategy-executor.js +22 -423
- package/dist/strategy-executor.js.map +1 -1
- package/dist/strategy-spawn-helpers.d.ts +67 -0
- package/dist/strategy-spawn-helpers.d.ts.map +1 -0
- package/dist/strategy-spawn-helpers.js +160 -0
- package/dist/strategy-spawn-helpers.js.map +1 -0
- package/dist/strategy-team-lifecycle.d.ts +50 -0
- package/dist/strategy-team-lifecycle.d.ts.map +1 -0
- package/dist/strategy-team-lifecycle.js +218 -0
- package/dist/strategy-team-lifecycle.js.map +1 -0
- package/dist/unified-engine-lifecycle.d.ts +36 -0
- package/dist/unified-engine-lifecycle.d.ts.map +1 -0
- package/dist/unified-engine-lifecycle.js +250 -0
- package/dist/unified-engine-lifecycle.js.map +1 -0
- package/dist/unified-shell-config.d.ts +25 -0
- package/dist/unified-shell-config.d.ts.map +1 -1
- package/dist/unified-shell-config.js +45 -0
- package/dist/unified-shell-config.js.map +1 -1
- package/dist/unified-shell-status.d.ts.map +1 -1
- package/dist/unified-shell-status.js +16 -2
- package/dist/unified-shell-status.js.map +1 -1
- package/dist/unified-shell.d.ts +2 -15
- package/dist/unified-shell.d.ts.map +1 -1
- package/dist/unified-shell.js +18 -619
- package/dist/unified-shell.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy spawn helpers - log streams, stream handlers, and process close handling.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from strategy-executor.ts. These are internal helpers used by
|
|
5
|
+
* spawnStrategyTeam() -- not a public API surface.
|
|
6
|
+
*/
|
|
7
|
+
import type { ChildProcess } from 'node:child_process';
|
|
8
|
+
import { createWriteStream } from 'node:fs';
|
|
9
|
+
import type { DaemonConfig, StrategyTeamState } from './types.js';
|
|
10
|
+
import { CompletionDetector } from './completion-detector.js';
|
|
11
|
+
import { ActivityTracker } from './activity-tracker.js';
|
|
12
|
+
import type { SpawnStrategyTeamParams } from './strategy-executor.js';
|
|
13
|
+
import type { ResourceGovernor } from '@telora/daemon-core';
|
|
14
|
+
export interface TeamLogStreams {
|
|
15
|
+
stdoutPath: string;
|
|
16
|
+
stderrPath: string;
|
|
17
|
+
jsonlPath: string;
|
|
18
|
+
stdoutLogStream: ReturnType<typeof createWriteStream>;
|
|
19
|
+
stderrStream: ReturnType<typeof createWriteStream>;
|
|
20
|
+
jsonlStream: ReturnType<typeof createWriteStream>;
|
|
21
|
+
}
|
|
22
|
+
export interface AttachStreamHandlersParams {
|
|
23
|
+
proc: ChildProcess;
|
|
24
|
+
teamState: StrategyTeamState;
|
|
25
|
+
strategyId: string;
|
|
26
|
+
strategyName: string;
|
|
27
|
+
sessionId: string;
|
|
28
|
+
logs: TeamLogStreams;
|
|
29
|
+
}
|
|
30
|
+
export interface AttachStreamHandlersResult {
|
|
31
|
+
completionDetector: CompletionDetector;
|
|
32
|
+
activityTracker: ActivityTracker;
|
|
33
|
+
}
|
|
34
|
+
export interface HandleTeamProcessCloseParams {
|
|
35
|
+
code: number | null;
|
|
36
|
+
signal: string | null;
|
|
37
|
+
strategyName: string;
|
|
38
|
+
strategyId: string;
|
|
39
|
+
resumeId: string | null;
|
|
40
|
+
spawnedAt: number;
|
|
41
|
+
teamState: StrategyTeamState;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
config: DaemonConfig;
|
|
44
|
+
completionDetector: CompletionDetector;
|
|
45
|
+
activityTracker: ActivityTracker;
|
|
46
|
+
logs: TeamLogStreams;
|
|
47
|
+
params: SpawnStrategyTeamParams;
|
|
48
|
+
governor: ResourceGovernor | null;
|
|
49
|
+
spawnStrategyTeam: (params: SpawnStrategyTeamParams) => Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create log file paths and writable streams for a team's output.
|
|
53
|
+
*/
|
|
54
|
+
export declare function setupTeamLogStreams(logDir: string, branchName: string, strategyName: string): TeamLogStreams;
|
|
55
|
+
/**
|
|
56
|
+
* Attach stream parser, completion detector, activity tracker, and event
|
|
57
|
+
* handlers to the spawned team lead process.
|
|
58
|
+
*/
|
|
59
|
+
export declare function attachStreamHandlers(params: AttachStreamHandlersParams): AttachStreamHandlersResult;
|
|
60
|
+
/**
|
|
61
|
+
* Handle the team lead process 'close' event.
|
|
62
|
+
*
|
|
63
|
+
* Detects --resume failures and retries without resume, otherwise
|
|
64
|
+
* runs normal cleanup (governor release, stream close, completion handling).
|
|
65
|
+
*/
|
|
66
|
+
export declare function handleTeamProcessClose(ctx: HandleTeamProcessCloseParams): Promise<void>;
|
|
67
|
+
//# sourceMappingURL=strategy-spawn-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-spawn-helpers.d.ts","sourceRoot":"","sources":["../src/strategy-spawn-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE5C,OAAO,KAAK,EACV,YAAY,EACZ,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAMpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAIxD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IACtD,YAAY,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;IACnD,WAAW,EAAE,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,cAAc,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAClC,iBAAiB,EAAE,CAAC,MAAM,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,cAAc,CAuB5G;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,0BAA0B,GAAG,0BAA0B,CAwDnG;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,4BAA4B,GAAG,OAAO,CAAC,IAAI,CAAC,CAmE7F"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy spawn helpers - log streams, stream handlers, and process close handling.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from strategy-executor.ts. These are internal helpers used by
|
|
5
|
+
* spawnStrategyTeam() -- not a public API surface.
|
|
6
|
+
*/
|
|
7
|
+
import { createWriteStream } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { updateSession, } from './supabase.js';
|
|
10
|
+
import { StreamJsonParser, formatEventForLog } from '@telora/daemon-core';
|
|
11
|
+
import { recordActivity, setNarration } from './heartbeat.js';
|
|
12
|
+
import { CompletionDetector } from './completion-detector.js';
|
|
13
|
+
import { ActivityTracker } from './activity-tracker.js';
|
|
14
|
+
import { getActiveTeams } from './strategy-team-state.js';
|
|
15
|
+
import { handleTeamCompletion } from './strategy-completion.js';
|
|
16
|
+
import { updateStrategyClaudeSessionId } from './queries/strategies.js';
|
|
17
|
+
// ── Functions ───────────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Create log file paths and writable streams for a team's output.
|
|
20
|
+
*/
|
|
21
|
+
export function setupTeamLogStreams(logDir, branchName, strategyName) {
|
|
22
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
23
|
+
const logPrefix = `strategy-${branchName.replace(/\//g, '-')}-${timestamp}`;
|
|
24
|
+
const stdoutPath = join(logDir, `${logPrefix}.stdout.log`);
|
|
25
|
+
const stderrPath = join(logDir, `${logPrefix}.stderr.log`);
|
|
26
|
+
const jsonlPath = join(logDir, `${logPrefix}.stream.jsonl`);
|
|
27
|
+
const stdoutLogStream = createWriteStream(stdoutPath, { mode: 0o600 });
|
|
28
|
+
const stderrStream = createWriteStream(stderrPath, { mode: 0o600 });
|
|
29
|
+
const jsonlStream = createWriteStream(jsonlPath, { mode: 0o600 });
|
|
30
|
+
// Attach error handlers to prevent unhandled 'error' events from crashing the daemon
|
|
31
|
+
stdoutLogStream.on('error', (err) => {
|
|
32
|
+
console.warn(`[strategy-executor] stdout log stream error for "${strategyName}": ${err.message}`);
|
|
33
|
+
});
|
|
34
|
+
stderrStream.on('error', (err) => {
|
|
35
|
+
console.warn(`[strategy-executor] stderr log stream error for "${strategyName}": ${err.message}`);
|
|
36
|
+
});
|
|
37
|
+
jsonlStream.on('error', (err) => {
|
|
38
|
+
console.warn(`[strategy-executor] jsonl log stream error for "${strategyName}": ${err.message}`);
|
|
39
|
+
});
|
|
40
|
+
return { stdoutPath, stderrPath, jsonlPath, stdoutLogStream, stderrStream, jsonlStream };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Attach stream parser, completion detector, activity tracker, and event
|
|
44
|
+
* handlers to the spawned team lead process.
|
|
45
|
+
*/
|
|
46
|
+
export function attachStreamHandlers(params) {
|
|
47
|
+
const { proc, teamState, strategyId, strategyName, sessionId, logs } = params;
|
|
48
|
+
const streamParser = new StreamJsonParser();
|
|
49
|
+
const completionDetector = new CompletionDetector();
|
|
50
|
+
streamParser.attach(proc.stdout);
|
|
51
|
+
completionDetector.attach(streamParser);
|
|
52
|
+
teamState.completionDetector = completionDetector;
|
|
53
|
+
// Attach activity tracker for live activity snapshots
|
|
54
|
+
const activityTracker = new ActivityTracker(sessionId);
|
|
55
|
+
activityTracker.onNarration((text) => setNarration(strategyId, text));
|
|
56
|
+
activityTracker.attach(streamParser);
|
|
57
|
+
// Write raw NDJSON lines to jsonl log file
|
|
58
|
+
streamParser.on('event', (event) => {
|
|
59
|
+
logs.jsonlStream.write(JSON.stringify(event) + '\n');
|
|
60
|
+
});
|
|
61
|
+
// Write human-readable lines to stdout log
|
|
62
|
+
streamParser.on('event', (event) => {
|
|
63
|
+
const line = formatEventForLog(event);
|
|
64
|
+
if (line) {
|
|
65
|
+
logs.stdoutLogStream.write(line + '\n');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
// Track session state from stream events
|
|
69
|
+
streamParser.on('init', (event) => {
|
|
70
|
+
console.log(` [strategy-team] Session initialized (model: ${event.model}, tools: ${event.tools.length})`);
|
|
71
|
+
teamState.claudeSessionId = event.session_id;
|
|
72
|
+
// Persist session ID for --resume support on re-spawn
|
|
73
|
+
updateStrategyClaudeSessionId(strategyId, event.session_id).catch((err) => {
|
|
74
|
+
console.warn(`[strategy-executor] Failed to persist Claude session ID for "${strategyName}":`, err.message);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
streamParser.on('teammate', (event) => {
|
|
78
|
+
if (event.subtype === 'teammate_spawned') {
|
|
79
|
+
console.log(` [strategy-team] Worker spawned: ${event.agent_name}`);
|
|
80
|
+
}
|
|
81
|
+
else if (event.subtype === 'teammate_completed') {
|
|
82
|
+
const status = event.is_error ? 'FAILED' : 'completed';
|
|
83
|
+
console.log(` [strategy-team] Worker ${status}: ${event.agent_name}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
streamParser.on('result', (result) => {
|
|
87
|
+
const cost = result.total_cost_usd?.toFixed(4) ?? '?';
|
|
88
|
+
console.log(` [strategy-team] Result: ${result.is_error ? 'ERROR' : 'Success'}: ${result.num_turns} turns, $${cost}`);
|
|
89
|
+
});
|
|
90
|
+
// Stderr pipes to log
|
|
91
|
+
proc.stderr?.pipe(logs.stderrStream);
|
|
92
|
+
return { completionDetector, activityTracker };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Handle the team lead process 'close' event.
|
|
96
|
+
*
|
|
97
|
+
* Detects --resume failures and retries without resume, otherwise
|
|
98
|
+
* runs normal cleanup (governor release, stream close, completion handling).
|
|
99
|
+
*/
|
|
100
|
+
export async function handleTeamProcessClose(ctx) {
|
|
101
|
+
const { code, signal, strategyName, strategyId, resumeId, spawnedAt, teamState, sessionId, config: cfg, completionDetector, activityTracker, logs, params: spawnParams, governor, spawnStrategyTeam: spawnFn, } = ctx;
|
|
102
|
+
const activeTeams = getActiveTeams();
|
|
103
|
+
console.log(`[strategy-executor] Team lead for "${strategyName}" exited (code: ${code}, signal: ${signal})`);
|
|
104
|
+
// Detect --resume failure: if the process exits quickly with a non-zero
|
|
105
|
+
// code and --resume was used, retry without resume.
|
|
106
|
+
const RESUME_FAILURE_THRESHOLD_MS = 15_000;
|
|
107
|
+
const elapsedMs = Date.now() - spawnedAt;
|
|
108
|
+
if (resumeId && code !== 0 && code !== null && elapsedMs < RESUME_FAILURE_THRESHOLD_MS) {
|
|
109
|
+
console.warn(`[strategy-executor] Resume failed for "${strategyName}" (exited in ${elapsedMs}ms), retrying without resume`);
|
|
110
|
+
// Release governor slot and clean up minimal state
|
|
111
|
+
governor?.releaseSlot('strategy');
|
|
112
|
+
completionDetector.destroy();
|
|
113
|
+
logs.stdoutLogStream.end();
|
|
114
|
+
logs.stderrStream.end();
|
|
115
|
+
logs.jsonlStream.end();
|
|
116
|
+
// Clean up session and team state
|
|
117
|
+
try {
|
|
118
|
+
await updateSession(sessionId, {
|
|
119
|
+
status: 'failed',
|
|
120
|
+
exit_reason: `Resume failed (code ${code}), retrying without resume`,
|
|
121
|
+
exit_category: 'internal',
|
|
122
|
+
ended_at: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (updateErr) {
|
|
126
|
+
console.warn('[strategy-executor] Failed to update session after resume failure:', updateErr.message);
|
|
127
|
+
}
|
|
128
|
+
teamState.phase = 'terminated';
|
|
129
|
+
activeTeams.delete(strategyId);
|
|
130
|
+
// Re-spawn without --resume
|
|
131
|
+
await spawnFn({ ...spawnParams, lastClaudeSessionId: null });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Release governor slot (if governor is configured)
|
|
135
|
+
governor?.releaseSlot('strategy');
|
|
136
|
+
// Null out stdin so post-exit merge logic falls through to the
|
|
137
|
+
// fallback resolution agent instead of sending messages to a dead process.
|
|
138
|
+
teamState.leadStdin = null;
|
|
139
|
+
// Final activity flush before cleanup
|
|
140
|
+
try {
|
|
141
|
+
await activityTracker.finalFlush();
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
console.warn('[strategy-executor] activityTracker.finalFlush failed:', err.message);
|
|
145
|
+
}
|
|
146
|
+
// Clean up streams
|
|
147
|
+
completionDetector.destroy();
|
|
148
|
+
logs.stdoutLogStream.end();
|
|
149
|
+
logs.stderrStream.end();
|
|
150
|
+
logs.jsonlStream.end();
|
|
151
|
+
recordActivity();
|
|
152
|
+
await handleTeamCompletion({
|
|
153
|
+
config: cfg,
|
|
154
|
+
teamState,
|
|
155
|
+
sessionId,
|
|
156
|
+
code,
|
|
157
|
+
signal,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=strategy-spawn-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-spawn-helpers.js","sourceRoot":"","sources":["../src/strategy-spawn-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAKjC,OAAO,EACL,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,6BAA6B,EAAE,MAAM,yBAAyB,CAAC;AA+CxE,2EAA2E;AAE3E;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,UAAkB,EAAE,YAAoB;IAC1F,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACjE,MAAM,SAAS,GAAG,YAAY,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,SAAS,EAAE,CAAC;IAC5E,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,aAAa,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,aAAa,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,SAAS,eAAe,CAAC,CAAC;IAE5D,MAAM,eAAe,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvE,MAAM,YAAY,GAAG,iBAAiB,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpE,MAAM,WAAW,GAAG,iBAAiB,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAElE,qFAAqF;IACrF,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAClC,OAAO,CAAC,IAAI,CAAC,oDAAoD,YAAY,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/B,OAAO,CAAC,IAAI,CAAC,oDAAoD,YAAY,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IACH,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QAC9B,OAAO,CAAC,IAAI,CAAC,mDAAmD,YAAY,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC;AAC3F,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAkC;IACrE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IAE9E,MAAM,YAAY,GAAG,IAAI,gBAAgB,EAAE,CAAC;IAC5C,MAAM,kBAAkB,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAEpD,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,MAAO,CAAC,CAAC;IAClC,kBAAkB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IACxC,SAAS,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAElD,sDAAsD;IACtD,MAAM,eAAe,GAAG,IAAI,eAAe,CAAC,SAAS,CAAC,CAAC;IACvD,eAAe,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,CAAC;IACtE,eAAe,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAErC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IAEH,2CAA2C;IAC3C,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,IAAI,EAAE,CAAC;YACT,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,yCAAyC;IACzC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE;QAChC,OAAO,CAAC,GAAG,CAAC,iDAAiD,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAC3G,SAAS,CAAC,eAAe,GAAG,KAAK,CAAC,UAAU,CAAC;QAC7C,sDAAsD;QACtD,6BAA6B,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACxE,OAAO,CAAC,IAAI,CAAC,gEAAgE,YAAY,IAAI,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;QACzH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,YAAY,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE;QACpC,IAAI,KAAK,CAAC,OAAO,KAAK,kBAAkB,EAAE,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,qCAAqC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACvE,CAAC;aAAM,IAAI,KAAK,CAAC,OAAO,KAAK,oBAAoB,EAAE,CAAC;YAClD,MAAM,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,KAAK,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,YAAY,IAAI,EAAE,CAAC,CAAC;IACzH,CAAC,CAAC,CAAC;IAEH,sBAAsB;IACtB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAErC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,CAAC;AACjD,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,GAAiC;IAC5E,MAAM,EACJ,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAC3D,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,kBAAkB,EAAE,eAAe,EACtE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,OAAO,GAChE,GAAG,GAAG,CAAC;IACR,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,OAAO,CAAC,GAAG,CAAC,sCAAsC,YAAY,mBAAmB,IAAI,aAAa,MAAM,GAAG,CAAC,CAAC;IAE7G,wEAAwE;IACxE,oDAAoD;IACpD,MAAM,2BAA2B,GAAG,MAAM,CAAC;IAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACzC,IAAI,QAAQ,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,SAAS,GAAG,2BAA2B,EAAE,CAAC;QACvF,OAAO,CAAC,IAAI,CAAC,0CAA0C,YAAY,gBAAgB,SAAS,8BAA8B,CAAC,CAAC;QAE5H,mDAAmD;QACnD,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;QAClC,kBAAkB,CAAC,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;QAEvB,kCAAkC;QAClC,IAAI,CAAC;YACH,MAAM,aAAa,CAAC,SAAS,EAAE;gBAC7B,MAAM,EAAE,QAAQ;gBAChB,WAAW,EAAE,uBAAuB,IAAI,4BAA4B;gBACpE,aAAa,EAAE,UAAU;gBACzB,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACnC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,oEAAoE,EAAG,SAAmB,CAAC,OAAO,CAAC,CAAC;QACnH,CAAC;QACD,SAAS,CAAC,KAAK,GAAG,YAAY,CAAC;QAC/B,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAE/B,4BAA4B;QAC5B,MAAM,OAAO,CAAC,EAAE,GAAG,WAAW,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,oDAAoD;IACpD,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,CAAC;IAElC,+DAA+D;IAC/D,2EAA2E;IAC3E,SAAS,CAAC,SAAS,GAAG,IAAI,CAAC;IAE3B,sCAAsC;IACtC,IAAI,CAAC;QAAC,MAAM,eAAe,CAAC,UAAU,EAAE,CAAC;IAAC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QAAC,OAAO,CAAC,IAAI,CAAC,wDAAwD,EAAG,GAAa,CAAC,OAAO,CAAC,CAAC;IAAC,CAAC;IAE3J,mBAAmB;IACnB,kBAAkB,CAAC,OAAO,EAAE,CAAC;IAC7B,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;IACxB,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC;IACvB,cAAc,EAAE,CAAC;IAEjB,MAAM,oBAAoB,CAAC;QACzB,MAAM,EAAE,GAAG;QACX,SAAS;QACT,SAAS;QACT,IAAI;QACJ,MAAM;KACP,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy team lifecycle management - terminate, wait, detect deactivated.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from strategy-executor.ts. These functions manage the lifecycle
|
|
5
|
+
* of running strategy teams (shutdown, deactivation detection, merge checks).
|
|
6
|
+
*/
|
|
7
|
+
import type { DaemonConfig } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Terminate a strategy team.
|
|
10
|
+
*
|
|
11
|
+
* Sends a deactivation message via stdin (if open) before SIGTERM
|
|
12
|
+
* to allow the team lead to process the shutdown gracefully.
|
|
13
|
+
*/
|
|
14
|
+
export declare function terminateTeam(strategyId: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Wait for a team's process to exit (leave activeTeams).
|
|
17
|
+
*
|
|
18
|
+
* Polls activeTeams until the strategy is no longer present,
|
|
19
|
+
* which happens when handleTeamCompletion runs on the 'close' event.
|
|
20
|
+
* If the timeout expires, sends SIGKILL and waits briefly.
|
|
21
|
+
*
|
|
22
|
+
* @param strategyId - Strategy to wait for
|
|
23
|
+
* @param timeoutMs - Max wait time in ms (default 30s)
|
|
24
|
+
* @returns true if team exited within timeout, false if forced
|
|
25
|
+
*/
|
|
26
|
+
export declare function waitForTeamExit(strategyId: string, timeoutMs?: number): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Terminate all active teams.
|
|
29
|
+
*/
|
|
30
|
+
export declare function terminateAllTeams(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Detect strategies that have been deactivated (agent role removed)
|
|
33
|
+
* and shut down their active teams.
|
|
34
|
+
*/
|
|
35
|
+
export declare function detectDeactivatedStrategies(config: DaemonConfig): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Check active teams for newly completed deliveries and merge
|
|
38
|
+
* the strategy branch to integration incrementally.
|
|
39
|
+
*
|
|
40
|
+
* Called from the poll loop. For each active team, queries delivery
|
|
41
|
+
* statuses and triggers a merge when any delivery reaches verify/done
|
|
42
|
+
* that hasn't already been merged mid-flight.
|
|
43
|
+
*
|
|
44
|
+
* After processing merges, checks if ALL active deliveries are terminal
|
|
45
|
+
* and merged. If so, terminates the team proactively since no more work
|
|
46
|
+
* remains. The strategy stays active in the DB -- if new deliveries
|
|
47
|
+
* arrive later, the next poll cycle spawns a fresh team.
|
|
48
|
+
*/
|
|
49
|
+
export declare function checkAndMergeCompletedDeliveries(config: DaemonConfig): Promise<void>;
|
|
50
|
+
//# sourceMappingURL=strategy-team-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-team-lifecycle.d.ts","sourceRoot":"","sources":["../src/strategy-team-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAc/C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CA2CzD;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,SAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAsB7F;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAED;;;GAGG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAsBf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gCAAgC,CACpD,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CA2Ff"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strategy team lifecycle management - terminate, wait, detect deactivated.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from strategy-executor.ts. These functions manage the lifecycle
|
|
5
|
+
* of running strategy teams (shutdown, deactivation detection, merge checks).
|
|
6
|
+
*/
|
|
7
|
+
import { getReadyStrategies, } from './supabase.js';
|
|
8
|
+
import { sendMessage } from '@telora/daemon-core';
|
|
9
|
+
import { clearNarration } from './heartbeat.js';
|
|
10
|
+
import { getActiveTeams } from './strategy-team-state.js';
|
|
11
|
+
import { mergeStrategyBranch, escalateMergeConflict } from './strategy-merge.js';
|
|
12
|
+
import { isStatusAgentActionable, isStatusBlocking } from './stage-classifier.js';
|
|
13
|
+
import { getStrategyDeliveries } from './queries/strategies.js';
|
|
14
|
+
import { configForProduct, findProduct } from './config.js';
|
|
15
|
+
// ── Team lifecycle ──────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Terminate a strategy team.
|
|
18
|
+
*
|
|
19
|
+
* Sends a deactivation message via stdin (if open) before SIGTERM
|
|
20
|
+
* to allow the team lead to process the shutdown gracefully.
|
|
21
|
+
*/
|
|
22
|
+
export function terminateTeam(strategyId) {
|
|
23
|
+
const activeTeams = getActiveTeams();
|
|
24
|
+
const team = activeTeams.get(strategyId);
|
|
25
|
+
if (!team || !team.leadPid)
|
|
26
|
+
return false;
|
|
27
|
+
// Warn if terminating while conflict resolution is in progress
|
|
28
|
+
if (team.resolvingMergeConflict) {
|
|
29
|
+
console.warn(`[strategy-executor] Terminating team "${team.strategyName}" while merge conflict resolution is in progress`);
|
|
30
|
+
}
|
|
31
|
+
console.log(`[strategy-executor] Terminating team for strategy "${team.strategyName}" (phase: ${team.phase})`);
|
|
32
|
+
team.phase = 'shutting_down';
|
|
33
|
+
clearNarration(strategyId);
|
|
34
|
+
try {
|
|
35
|
+
// Send a deactivation message first to let the team lead process it
|
|
36
|
+
if (team.leadStdin) {
|
|
37
|
+
sendMessage(team.leadStdin, 'Pipeline deactivated. Exit now.');
|
|
38
|
+
// Close stdin after a short delay to let the message be processed
|
|
39
|
+
setTimeout(() => {
|
|
40
|
+
try {
|
|
41
|
+
team.leadStdin?.end();
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
console.debug('[strategy-executor] stdin.end() failed (may already be closed):', e.message);
|
|
45
|
+
}
|
|
46
|
+
}, 5000);
|
|
47
|
+
}
|
|
48
|
+
// SIGTERM to the lead process -- it should clean up workers
|
|
49
|
+
process.kill(team.leadPid, 'SIGTERM');
|
|
50
|
+
// Escalate to SIGKILL after timeout
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
if (activeTeams.has(strategyId) && team.leadPid) {
|
|
53
|
+
try {
|
|
54
|
+
process.kill(team.leadPid, 'SIGKILL');
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
console.debug('[strategy-executor] SIGKILL failed (process may have exited):', e.message);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, 30000);
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
console.debug(`[strategy-executor] terminateTeam: process may have already exited:`, e.message);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Wait for a team's process to exit (leave activeTeams).
|
|
70
|
+
*
|
|
71
|
+
* Polls activeTeams until the strategy is no longer present,
|
|
72
|
+
* which happens when handleTeamCompletion runs on the 'close' event.
|
|
73
|
+
* If the timeout expires, sends SIGKILL and waits briefly.
|
|
74
|
+
*
|
|
75
|
+
* @param strategyId - Strategy to wait for
|
|
76
|
+
* @param timeoutMs - Max wait time in ms (default 30s)
|
|
77
|
+
* @returns true if team exited within timeout, false if forced
|
|
78
|
+
*/
|
|
79
|
+
export async function waitForTeamExit(strategyId, timeoutMs = 30000) {
|
|
80
|
+
const activeTeams = getActiveTeams();
|
|
81
|
+
const team = activeTeams.get(strategyId);
|
|
82
|
+
if (!team)
|
|
83
|
+
return true; // Already gone
|
|
84
|
+
const deadline = Date.now() + timeoutMs;
|
|
85
|
+
const pollMs = 500;
|
|
86
|
+
while (Date.now() < deadline) {
|
|
87
|
+
if (!activeTeams.has(strategyId))
|
|
88
|
+
return true;
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, pollMs));
|
|
90
|
+
}
|
|
91
|
+
// Timeout expired — escalate to SIGKILL
|
|
92
|
+
if (team.leadPid) {
|
|
93
|
+
console.warn(`[strategy-executor] Team "${team.strategyName}" did not exit within ${timeoutMs}ms, sending SIGKILL`);
|
|
94
|
+
try {
|
|
95
|
+
process.kill(team.leadPid, 'SIGKILL');
|
|
96
|
+
}
|
|
97
|
+
catch { /* process may already be gone */ }
|
|
98
|
+
// Brief wait for SIGKILL to take effect
|
|
99
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
100
|
+
}
|
|
101
|
+
return !activeTeams.has(strategyId);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Terminate all active teams.
|
|
105
|
+
*/
|
|
106
|
+
export function terminateAllTeams() {
|
|
107
|
+
const activeTeams = getActiveTeams();
|
|
108
|
+
for (const strategyId of activeTeams.keys()) {
|
|
109
|
+
terminateTeam(strategyId);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Detect strategies that have been deactivated (agent role removed)
|
|
114
|
+
* and shut down their active teams.
|
|
115
|
+
*/
|
|
116
|
+
export async function detectDeactivatedStrategies(config) {
|
|
117
|
+
const activeTeams = getActiveTeams();
|
|
118
|
+
if (activeTeams.size === 0)
|
|
119
|
+
return;
|
|
120
|
+
try {
|
|
121
|
+
// Aggregate ready strategies across all configured products
|
|
122
|
+
const allReadyStrategies = [];
|
|
123
|
+
for (const product of config.products) {
|
|
124
|
+
const strategies = await getReadyStrategies(config.organizationId, product.id);
|
|
125
|
+
allReadyStrategies.push(...strategies);
|
|
126
|
+
}
|
|
127
|
+
const activeStrategyIds = new Set(allReadyStrategies.map(s => s.strategy_id));
|
|
128
|
+
for (const [strategyId, team] of activeTeams) {
|
|
129
|
+
if (!activeStrategyIds.has(strategyId) && team.phase !== 'shutting_down' && team.phase !== 'terminated') {
|
|
130
|
+
console.log(`[strategy-executor] Strategy "${team.strategyName}" deactivated -- shutting down team`);
|
|
131
|
+
terminateTeam(strategyId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.warn(`[strategy-executor] Failed to check for deactivated strategies:`, err.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check active teams for newly completed deliveries and merge
|
|
141
|
+
* the strategy branch to integration incrementally.
|
|
142
|
+
*
|
|
143
|
+
* Called from the poll loop. For each active team, queries delivery
|
|
144
|
+
* statuses and triggers a merge when any delivery reaches verify/done
|
|
145
|
+
* that hasn't already been merged mid-flight.
|
|
146
|
+
*
|
|
147
|
+
* After processing merges, checks if ALL active deliveries are terminal
|
|
148
|
+
* and merged. If so, terminates the team proactively since no more work
|
|
149
|
+
* remains. The strategy stays active in the DB -- if new deliveries
|
|
150
|
+
* arrive later, the next poll cycle spawns a fresh team.
|
|
151
|
+
*/
|
|
152
|
+
export async function checkAndMergeCompletedDeliveries(config) {
|
|
153
|
+
const activeTeams = getActiveTeams();
|
|
154
|
+
if (activeTeams.size === 0)
|
|
155
|
+
return;
|
|
156
|
+
for (const [strategyId, team] of activeTeams) {
|
|
157
|
+
// Only check teams that are actively executing
|
|
158
|
+
if (team.phase !== 'executing')
|
|
159
|
+
continue;
|
|
160
|
+
// Skip merge attempts while team lead is resolving a merge conflict
|
|
161
|
+
if (team.resolvingMergeConflict) {
|
|
162
|
+
console.log(`[strategy-executor] Skipping merge check for "${team.strategyName}" -- conflict resolution in progress`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// Use product-scoped config for this team's merge operations
|
|
166
|
+
const teamProduct = findProduct(config, team.productId);
|
|
167
|
+
const teamConfig = teamProduct ? configForProduct(config, teamProduct) : config;
|
|
168
|
+
try {
|
|
169
|
+
const deliveries = await getStrategyDeliveries(strategyId);
|
|
170
|
+
// Find deliveries in verify/done that we haven't merged for yet
|
|
171
|
+
const newlyCompleted = deliveries.filter(d => !isStatusAgentActionable(d.executionStatus ?? '') && !isStatusBlocking(d.executionStatus ?? '')
|
|
172
|
+
&& !team.mergedDeliveryIds.has(d.id));
|
|
173
|
+
if (newlyCompleted.length > 0) {
|
|
174
|
+
const completedNames = newlyCompleted.map(d => d.name).join(', ');
|
|
175
|
+
console.log(`[strategy-executor] ${newlyCompleted.length} delivery(ies) completed mid-flight for "${team.strategyName}": ${completedNames} -- merging to integration`);
|
|
176
|
+
// One merge covers all completed deliveries (same branch)
|
|
177
|
+
const mergeResult = await mergeStrategyBranch(teamConfig, team, team.leadSessionId ?? '', team.branchName);
|
|
178
|
+
if (mergeResult.mergeSucceeded) {
|
|
179
|
+
// Track merged deliveries (git state already reported by mergeStrategyBranch)
|
|
180
|
+
for (const d of newlyCompleted) {
|
|
181
|
+
team.mergedDeliveryIds.add(d.id);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.warn(`[strategy-executor] Mid-flight merge failed for "${team.strategyName}": ${mergeResult.exitReason}`);
|
|
186
|
+
// Escalate merge conflict for each unmerged delivery
|
|
187
|
+
for (const d of newlyCompleted) {
|
|
188
|
+
escalateMergeConflict({
|
|
189
|
+
organizationId: team.organizationId,
|
|
190
|
+
sessionId: team.leadSessionId ?? '',
|
|
191
|
+
deliveryId: d.id,
|
|
192
|
+
deliveryName: d.name,
|
|
193
|
+
branchName: team.branchName,
|
|
194
|
+
integrationBranch: config.integrationBranch,
|
|
195
|
+
mergeError: mergeResult.exitReason,
|
|
196
|
+
}).catch(err => console.warn(`[strategy-executor] escalateMergeConflict failed for ${d.id}:`, err.message));
|
|
197
|
+
}
|
|
198
|
+
// Don't mark as merged -- will retry next poll. Team continues working.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// ── All-done detection ──────────────────────────────────────
|
|
202
|
+
// If any delivery is queued or running, the team still has work.
|
|
203
|
+
// Otherwise the team is idle, waiting for the daemon to push new work.
|
|
204
|
+
const teamWork = deliveries.filter(d => isStatusAgentActionable(d.executionStatus ?? ''));
|
|
205
|
+
if (teamWork.length > 0) {
|
|
206
|
+
console.log(`[strategy-executor] ${teamWork.length} delivery(ies) still queued/running for "${team.strategyName}" -- team continues`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
// No queued/running deliveries -- team is idle, completion detector
|
|
210
|
+
// handles the actual idle transition. Log for observability.
|
|
211
|
+
console.log(`[strategy-executor] All deliveries complete for "${team.strategyName}" -- team is idle`);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
console.warn(`[strategy-executor] Failed to check completed deliveries for "${team.strategyName}":`, err.message);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=strategy-team-lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strategy-team-lifecycle.js","sourceRoot":"","sources":["../src/strategy-team-lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine creation, initialization, and diagnostic signal registration
|
|
3
|
+
* for the unified daemon shell.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from unified-shell.ts -- pure structural refactor, no behavioral
|
|
6
|
+
* changes.
|
|
7
|
+
*/
|
|
8
|
+
import { type ExecutionEngine, type UnifiedConfig, ResourceGovernor } from '@telora/daemon-core';
|
|
9
|
+
import type { UnifiedDaemonOptions } from './unified-shell-config.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create engine instances from config, set up the resource governor, and
|
|
12
|
+
* inject it into each engine. Exits the process if no engines are available.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createEnginesAndGovernor(config: UnifiedConfig, pidFilePath: string): Promise<{
|
|
15
|
+
engines: ExecutionEngine[];
|
|
16
|
+
governor: ResourceGovernor;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Initialize, run crash recovery, and start each engine. On failure, stops
|
|
20
|
+
* any engines that already started, releases the PID lock, and exits.
|
|
21
|
+
*/
|
|
22
|
+
export declare function initializeAndStartEngines(engines: ExecutionEngine[], config: UnifiedConfig, opts: UnifiedDaemonOptions, pidFilePath: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Claim products for this daemon's hostname. Unclaimed products are
|
|
25
|
+
* auto-claimed; products already claimed by another host are skipped.
|
|
26
|
+
*
|
|
27
|
+
* Mutates `config.base.products` in place to contain only claimed products.
|
|
28
|
+
* Uses a direct fetch call to avoid needing the full API client infrastructure
|
|
29
|
+
* (which is initialized per-engine later).
|
|
30
|
+
*/
|
|
31
|
+
export declare function claimProducts(config: UnifiedConfig): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Register SIGUSR1 (status dump) and SIGUSR2 (circuit breaker reset) handlers.
|
|
34
|
+
*/
|
|
35
|
+
export declare function registerDiagnosticSignals(engines: ExecutionEngine[], governor: ResourceGovernor): void;
|
|
36
|
+
//# sourceMappingURL=unified-engine-lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unified-engine-lifecycle.d.ts","sourceRoot":"","sources":["../src/unified-engine-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,aAAa,EAKlB,gBAAgB,EACjB,MAAM,qBAAqB,CAAC;AAW7B,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAOtE;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,aAAa,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,OAAO,EAAE,eAAe,EAAE,CAAC;IAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAwGrE;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,eAAe,EAAE,EAC1B,MAAM,EAAE,aAAa,EACrB,IAAI,EAAE,oBAAoB,EAC1B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC,CAyDf;AAMD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DxE;AAMD;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,eAAe,EAAE,EAC1B,QAAQ,EAAE,gBAAgB,GACzB,IAAI,CAQN"}
|