steroids-cli 0.9.42 → 0.10.13
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/dist/cli/base-command.d.ts +16 -0
- package/dist/cli/base-command.d.ts.map +1 -0
- package/dist/cli/base-command.js +42 -0
- package/dist/cli/base-command.js.map +1 -0
- package/dist/cli/version-check.d.ts.map +1 -1
- package/dist/cli/version-check.js +28 -2
- package/dist/cli/version-check.js.map +1 -1
- package/dist/commands/disputes.js +15 -30
- package/dist/commands/disputes.js.map +1 -1
- package/dist/commands/gc.d.ts +1 -1
- package/dist/commands/gc.d.ts.map +1 -1
- package/dist/commands/gc.js +49 -64
- package/dist/commands/gc.js.map +1 -1
- package/dist/commands/git.d.ts.map +1 -1
- package/dist/commands/git.js +5 -8
- package/dist/commands/git.js.map +1 -1
- package/dist/commands/health-stuck.js +2 -6
- package/dist/commands/health-stuck.js.map +1 -1
- package/dist/commands/health.d.ts.map +1 -1
- package/dist/commands/health.js +2 -6
- package/dist/commands/health.js.map +1 -1
- package/dist/commands/llm.d.ts.map +1 -1
- package/dist/commands/llm.js +4 -12
- package/dist/commands/llm.js.map +1 -1
- package/dist/commands/locks.js +12 -24
- package/dist/commands/locks.js.map +1 -1
- package/dist/commands/loop-phases.d.ts +12 -4
- package/dist/commands/loop-phases.d.ts.map +1 -1
- package/dist/commands/loop-phases.js +163 -111
- package/dist/commands/loop-phases.js.map +1 -1
- package/dist/commands/purge.d.ts.map +1 -1
- package/dist/commands/purge.js +11 -22
- package/dist/commands/purge.js.map +1 -1
- package/dist/commands/runners-list.d.ts.map +1 -1
- package/dist/commands/runners-list.js +11 -30
- package/dist/commands/runners-list.js.map +1 -1
- package/dist/commands/runners-parallel.d.ts.map +1 -1
- package/dist/commands/runners-parallel.js +3 -7
- package/dist/commands/runners-parallel.js.map +1 -1
- package/dist/commands/runners.js +2 -9
- package/dist/commands/runners.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +5 -8
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/sections-commands.d.ts.map +1 -1
- package/dist/commands/sections-commands.js +12 -24
- package/dist/commands/sections-commands.js.map +1 -1
- package/dist/commands/sections.js +6 -12
- package/dist/commands/sections.js.map +1 -1
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.d.ts.map +1 -0
- package/dist/commands/skills.js +181 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/tasks-reset.d.ts +3 -0
- package/dist/commands/tasks-reset.d.ts.map +1 -0
- package/dist/commands/tasks-reset.js +200 -0
- package/dist/commands/tasks-reset.js.map +1 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +57 -67
- package/dist/commands/tasks.js.map +1 -1
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +3 -1
- package/dist/commands/web.js.map +1 -1
- package/dist/config/loader.d.ts +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/database/connection.d.ts +10 -1
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +32 -6
- package/dist/database/connection.js.map +1 -1
- package/dist/database/queries.d.ts +18 -0
- package/dist/database/queries.d.ts.map +1 -1
- package/dist/database/queries.js +91 -20
- package/dist/database/queries.js.map +1 -1
- package/dist/database/schema.d.ts +2 -2
- package/dist/database/schema.d.ts.map +1 -1
- package/dist/database/schema.js +8 -0
- package/dist/database/schema.js.map +1 -1
- package/dist/git/status.d.ts.map +1 -1
- package/dist/git/status.js +13 -2
- package/dist/git/status.js.map +1 -1
- package/dist/git/submission-resolution.d.ts.map +1 -1
- package/dist/git/submission-resolution.js +3 -7
- package/dist/git/submission-resolution.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/migrations/backfill.d.ts +3 -0
- package/dist/migrations/backfill.d.ts.map +1 -0
- package/dist/migrations/backfill.js +53 -0
- package/dist/migrations/backfill.js.map +1 -0
- package/dist/migrations/runner.d.ts.map +1 -1
- package/dist/migrations/runner.js +9 -0
- package/dist/migrations/runner.js.map +1 -1
- package/dist/orchestrator/base-runner.d.ts +14 -0
- package/dist/orchestrator/base-runner.d.ts.map +1 -0
- package/dist/orchestrator/base-runner.js +57 -0
- package/dist/orchestrator/base-runner.js.map +1 -0
- package/dist/orchestrator/coder.d.ts +4 -22
- package/dist/orchestrator/coder.d.ts.map +1 -1
- package/dist/orchestrator/coder.js +136 -183
- package/dist/orchestrator/coder.js.map +1 -1
- package/dist/orchestrator/coordinator.d.ts.map +1 -1
- package/dist/orchestrator/coordinator.js +1 -0
- package/dist/orchestrator/coordinator.js.map +1 -1
- package/dist/orchestrator/fallback-handler.d.ts +3 -23
- package/dist/orchestrator/fallback-handler.d.ts.map +1 -1
- package/dist/orchestrator/fallback-handler.js +40 -357
- package/dist/orchestrator/fallback-handler.js.map +1 -1
- package/dist/orchestrator/history-manager.d.ts +15 -0
- package/dist/orchestrator/history-manager.d.ts.map +1 -0
- package/dist/orchestrator/history-manager.js +93 -0
- package/dist/orchestrator/history-manager.js.map +1 -0
- package/dist/orchestrator/post-coder.d.ts.map +1 -1
- package/dist/orchestrator/post-coder.js +16 -24
- package/dist/orchestrator/post-coder.js.map +1 -1
- package/dist/orchestrator/post-reviewer.d.ts.map +1 -1
- package/dist/orchestrator/post-reviewer.js +25 -37
- package/dist/orchestrator/post-reviewer.js.map +1 -1
- package/dist/orchestrator/reviewer.d.ts +5 -29
- package/dist/orchestrator/reviewer.d.ts.map +1 -1
- package/dist/orchestrator/reviewer.js +193 -255
- package/dist/orchestrator/reviewer.js.map +1 -1
- package/dist/orchestrator/signal-parser.d.ts +32 -0
- package/dist/orchestrator/signal-parser.d.ts.map +1 -0
- package/dist/orchestrator/signal-parser.js +73 -0
- package/dist/orchestrator/signal-parser.js.map +1 -0
- package/dist/orchestrator/types.d.ts +9 -13
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/parallel/clone.d.ts.map +1 -1
- package/dist/parallel/clone.js +37 -2
- package/dist/parallel/clone.js.map +1 -1
- package/dist/parallel/merge-conflict-attempts.d.ts.map +1 -1
- package/dist/parallel/merge-conflict-attempts.js +4 -12
- package/dist/parallel/merge-conflict-attempts.js.map +1 -1
- package/dist/parallel/merge-conflict.d.ts.map +1 -1
- package/dist/parallel/merge-conflict.js +3 -10
- package/dist/parallel/merge-conflict.js.map +1 -1
- package/dist/parallel/merge-sealing.d.ts +0 -3
- package/dist/parallel/merge-sealing.d.ts.map +1 -1
- package/dist/parallel/merge-sealing.js +5 -9
- package/dist/parallel/merge-sealing.js.map +1 -1
- package/dist/prompts/coder.d.ts +1 -1
- package/dist/prompts/coder.d.ts.map +1 -1
- package/dist/prompts/coder.js +66 -119
- package/dist/prompts/coder.js.map +1 -1
- package/dist/prompts/prompt-helpers.d.ts +4 -0
- package/dist/prompts/prompt-helpers.d.ts.map +1 -1
- package/dist/prompts/prompt-helpers.js +35 -0
- package/dist/prompts/prompt-helpers.js.map +1 -1
- package/dist/prompts/reviewer.d.ts.map +1 -1
- package/dist/prompts/reviewer.js +1 -3
- package/dist/prompts/reviewer.js.map +1 -1
- package/dist/providers/claude.d.ts.map +1 -1
- package/dist/providers/claude.js +9 -1
- package/dist/providers/claude.js.map +1 -1
- package/dist/providers/codex.d.ts.map +1 -1
- package/dist/providers/codex.js +10 -1
- package/dist/providers/codex.js.map +1 -1
- package/dist/providers/gemini.d.ts +9 -48
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +133 -101
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/interface.d.ts +9 -1
- package/dist/providers/interface.d.ts.map +1 -1
- package/dist/providers/interface.js +11 -1
- package/dist/providers/interface.js.map +1 -1
- package/dist/providers/invocation-logger.d.ts +2 -0
- package/dist/providers/invocation-logger.d.ts.map +1 -1
- package/dist/providers/invocation-logger.js +3 -3
- package/dist/providers/invocation-logger.js.map +1 -1
- package/dist/providers/mistral.d.ts.map +1 -1
- package/dist/providers/mistral.js +12 -1
- package/dist/providers/mistral.js.map +1 -1
- package/dist/providers/ping.d.ts +5 -0
- package/dist/providers/ping.d.ts.map +1 -0
- package/dist/providers/ping.js +35 -0
- package/dist/providers/ping.js.map +1 -0
- package/dist/providers/registry.d.ts +4 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +18 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/runners/activity-log.d.ts.map +1 -1
- package/dist/runners/activity-log.js +12 -36
- package/dist/runners/activity-log.js.map +1 -1
- package/dist/runners/credit-pause.d.ts +2 -3
- package/dist/runners/credit-pause.d.ts.map +1 -1
- package/dist/runners/credit-pause.js +25 -40
- package/dist/runners/credit-pause.js.map +1 -1
- package/dist/runners/daemon.d.ts +0 -8
- package/dist/runners/daemon.d.ts.map +1 -1
- package/dist/runners/daemon.js +23 -76
- package/dist/runners/daemon.js.map +1 -1
- package/dist/runners/global-db.d.ts +14 -1
- package/dist/runners/global-db.d.ts.map +1 -1
- package/dist/runners/global-db.js +123 -59
- package/dist/runners/global-db.js.map +1 -1
- package/dist/runners/orchestrator-loop.d.ts.map +1 -1
- package/dist/runners/orchestrator-loop.js +2 -6
- package/dist/runners/orchestrator-loop.js.map +1 -1
- package/dist/runners/projects.d.ts +2 -32
- package/dist/runners/projects.d.ts.map +1 -1
- package/dist/runners/projects.js +23 -103
- package/dist/runners/projects.js.map +1 -1
- package/dist/runners/wakeup-checks.d.ts +0 -3
- package/dist/runners/wakeup-checks.d.ts.map +1 -1
- package/dist/runners/wakeup-checks.js +13 -26
- package/dist/runners/wakeup-checks.js.map +1 -1
- package/dist/runners/wakeup-timing.d.ts.map +1 -1
- package/dist/runners/wakeup-timing.js +4 -12
- package/dist/runners/wakeup-timing.js.map +1 -1
- package/dist/runners/wakeup.d.ts +1 -1
- package/dist/runners/wakeup.d.ts.map +1 -1
- package/dist/runners/wakeup.js +394 -359
- package/dist/runners/wakeup.js.map +1 -1
- package/dist/utils/tokens.d.ts +14 -0
- package/dist/utils/tokens.d.ts.map +1 -0
- package/dist/utils/tokens.js +62 -0
- package/dist/utils/tokens.js.map +1 -0
- package/migrations/004_add_section_dependencies.sql +1 -1
- package/migrations/017_add_invocation_runner_id.sql +8 -0
- package/migrations/018_expand_audit_columns.sql +13 -0
- package/migrations/019_add_task_start_sha.sql +9 -0
- package/migrations/manifest.json +25 -1
- package/package.json +4 -3
- package/dist/orchestrator/schemas.d.ts +0 -26
- package/dist/orchestrator/schemas.d.ts.map +0 -1
- package/dist/orchestrator/schemas.js +0 -154
- package/dist/orchestrator/schemas.js.map +0 -1
- package/dist/parallel/merge.test.d.ts +0 -5
- package/dist/parallel/merge.test.d.ts.map +0 -1
- package/dist/parallel/merge.test.js +0 -322
- package/dist/parallel/merge.test.js.map +0 -1
package/dist/runners/wakeup.js
CHANGED
|
@@ -24,6 +24,8 @@ Object.defineProperty(exports, "hasActiveParallelSessionForProject", { enumerabl
|
|
|
24
24
|
const wakeup_runner_js_1 = require("./wakeup-runner.js");
|
|
25
25
|
const wakeup_timing_js_1 = require("./wakeup-timing.js");
|
|
26
26
|
Object.defineProperty(exports, "getLastWakeupTime", { enumerable: true, get: function () { return wakeup_timing_js_1.getLastWakeupTime; } });
|
|
27
|
+
// In-memory mutex to prevent concurrent wakeup cycles in the same process
|
|
28
|
+
let isWakeupRunning = false;
|
|
27
29
|
/**
|
|
28
30
|
* Main wake-up function
|
|
29
31
|
* Called by cron every minute to ensure runners are healthy
|
|
@@ -37,216 +39,252 @@ async function wakeup(options = {}) {
|
|
|
37
39
|
if (!quiet)
|
|
38
40
|
console.log(msg);
|
|
39
41
|
};
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (isWakeupRunning) {
|
|
43
|
+
log('Wakeup cycle already running (in-memory lock), skipping.');
|
|
44
|
+
return [{ action: 'skipped', reason: 'Wakeup cycle already running' }];
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
const global = (0, global_db_js_1.openGlobalDatabase)();
|
|
46
|
+
isWakeupRunning = true;
|
|
46
47
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
if (!(0, global_db_js_1.getDaemonActiveStatus)()) {
|
|
49
|
+
log('Daemon paused (is_active=false), skipping wakeup logic.');
|
|
50
|
+
return [{ action: 'skipped', reason: 'Daemon is paused' }];
|
|
51
|
+
}
|
|
52
|
+
// Record wakeup invocation time (even for dry runs)
|
|
53
|
+
if (!dryRun) {
|
|
54
|
+
(0, wakeup_timing_js_1.recordWakeupTime)();
|
|
55
|
+
}
|
|
56
|
+
// Step 1: Clean up stale runners first
|
|
57
|
+
return (0, global_db_js_1.withGlobalDatabase)(async (globalDb) => {
|
|
58
|
+
const global = { db: globalDb };
|
|
59
|
+
try {
|
|
60
|
+
const staleRunners = (0, heartbeat_js_1.findStaleRunners)(global.db);
|
|
61
|
+
if (staleRunners.length > 0) {
|
|
62
|
+
log(`Found ${staleRunners.length} stale runner(s), cleaning up...`);
|
|
63
|
+
if (!dryRun) {
|
|
64
|
+
for (const runner of staleRunners) {
|
|
65
|
+
if (runner.pid) {
|
|
66
|
+
(0, wakeup_runner_js_1.killProcess)(runner.pid);
|
|
67
|
+
}
|
|
68
|
+
global.db.prepare(`UPDATE workstreams
|
|
57
69
|
SET runner_id = NULL,
|
|
58
70
|
lease_expires_at = datetime('now')
|
|
59
71
|
WHERE runner_id = ?`).run(runner.id);
|
|
60
|
-
|
|
72
|
+
global.db.prepare('DELETE FROM runners WHERE id = ?').run(runner.id);
|
|
73
|
+
}
|
|
61
74
|
}
|
|
75
|
+
results.push({
|
|
76
|
+
action: 'cleaned',
|
|
77
|
+
reason: `Cleaned ${staleRunners.length} stale runner(s)`,
|
|
78
|
+
staleRunners: staleRunners.length,
|
|
79
|
+
});
|
|
62
80
|
}
|
|
63
|
-
results.push({
|
|
64
|
-
action: 'cleaned',
|
|
65
|
-
reason: `Cleaned ${staleRunners.length} stale runner(s)`,
|
|
66
|
-
staleRunners: staleRunners.length,
|
|
67
|
-
});
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const releasedLeases = global.db.prepare(`UPDATE workstreams
|
|
82
|
+
catch {
|
|
83
|
+
// ignore global DB issues; wakeup will still attempt per-project checks
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const releasedLeases = global.db.prepare(`UPDATE workstreams
|
|
75
87
|
SET runner_id = NULL,
|
|
76
88
|
lease_expires_at = NULL
|
|
77
89
|
WHERE status = 'running'
|
|
78
90
|
AND lease_expires_at IS NOT NULL
|
|
79
91
|
AND lease_expires_at <= datetime('now')`).run().changes;
|
|
80
|
-
|
|
81
|
-
|
|
92
|
+
if (releasedLeases > 0) {
|
|
93
|
+
log(`Released ${releasedLeases} expired workstream lease(s)`);
|
|
94
|
+
}
|
|
82
95
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// ignore lease cleanup issues in wakeup
|
|
86
|
-
}
|
|
87
|
-
// Step 2: Clean zombie lock if present
|
|
88
|
-
const lockStatus = (0, lock_js_1.checkLockStatus)();
|
|
89
|
-
if (lockStatus.isZombie && lockStatus.pid) {
|
|
90
|
-
log(`Found zombie lock (PID: ${lockStatus.pid}), cleaning...`);
|
|
91
|
-
if (!dryRun) {
|
|
92
|
-
(0, lock_js_1.removeLock)();
|
|
96
|
+
catch {
|
|
97
|
+
// ignore lease cleanup issues in wakeup
|
|
93
98
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
log(`Checking ${registeredProjects.length} registered project(s)...`);
|
|
108
|
-
// Step 4: Check each project and start runners as needed
|
|
109
|
-
for (const project of registeredProjects) {
|
|
110
|
-
// Skip if project directory doesn't exist
|
|
111
|
-
if (!(0, node_fs_1.existsSync)(project.path)) {
|
|
112
|
-
log(`Skipping ${project.path}: directory not found`);
|
|
99
|
+
// Step 2: Clean zombie lock if present
|
|
100
|
+
const lockStatus = (0, lock_js_1.checkLockStatus)();
|
|
101
|
+
if (lockStatus.isZombie && lockStatus.pid) {
|
|
102
|
+
log(`Found zombie lock (PID: ${lockStatus.pid}), cleaning...`);
|
|
103
|
+
if (!dryRun) {
|
|
104
|
+
(0, lock_js_1.removeLock)();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Step 3: Get all registered projects from global registry
|
|
108
|
+
const registeredProjects = (0, projects_js_1.getRegisteredProjects)(false); // enabled only
|
|
109
|
+
if (registeredProjects.length === 0) {
|
|
110
|
+
log('No registered projects found');
|
|
111
|
+
log('Run "steroids projects add <path>" to register a project');
|
|
113
112
|
results.push({
|
|
114
113
|
action: 'none',
|
|
115
|
-
reason: '
|
|
116
|
-
|
|
114
|
+
reason: 'No registered projects',
|
|
115
|
+
pendingTasks: 0,
|
|
117
116
|
});
|
|
118
|
-
|
|
117
|
+
return results;
|
|
119
118
|
}
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
log(`Checking ${registeredProjects.length} registered project(s)...`);
|
|
120
|
+
// Step 4: Check each project and start runners as needed
|
|
121
|
+
for (const project of registeredProjects) {
|
|
122
|
+
// Skip if project directory doesn't exist
|
|
123
|
+
if (!(0, node_fs_1.existsSync)(project.path)) {
|
|
124
|
+
log(`Skipping ${project.path}: directory not found`);
|
|
125
|
+
results.push({
|
|
126
|
+
action: 'none',
|
|
127
|
+
reason: 'Directory not found',
|
|
128
|
+
projectPath: project.path,
|
|
129
|
+
});
|
|
130
|
+
continue;
|
|
128
131
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
// Phase 6 (live monitoring): best-effort retention cleanup of invocation activity logs.
|
|
133
|
+
// This is safe to run even if the project has no pending tasks.
|
|
134
|
+
let deletedInvocationLogs = 0;
|
|
135
|
+
try {
|
|
136
|
+
const cleanup = (0, invocation_logs_js_1.cleanupInvocationLogs)(project.path, { retentionDays: 7, dryRun });
|
|
137
|
+
deletedInvocationLogs = cleanup.deletedFiles;
|
|
138
|
+
if (cleanup.deletedFiles > 0 && !quiet) {
|
|
139
|
+
log(`Cleaned ${cleanup.deletedFiles} old invocation log(s) in ${project.path}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Ignore cleanup errors; wakeup must remain robust.
|
|
144
|
+
}
|
|
145
|
+
let recoveredActions = 0;
|
|
146
|
+
let skippedRecoveryDueToSafetyLimit = false;
|
|
147
|
+
let sanitisedActions = 0;
|
|
138
148
|
try {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
149
|
+
const { db: projectDb, close: closeProjectDb } = (0, connection_js_1.openDatabase)(project.path);
|
|
150
|
+
try {
|
|
151
|
+
const sanitiseSummary = (0, wakeup_sanitise_js_1.runPeriodicSanitiseForProject)(global.db, projectDb, project.path, dryRun);
|
|
152
|
+
sanitisedActions = (0, wakeup_sanitise_js_1.sanitisedActionCount)(sanitiseSummary);
|
|
153
|
+
if (sanitisedActions > 0 && !quiet) {
|
|
154
|
+
log(`Sanitised ${sanitisedActions} stale item(s) in ${project.path}`);
|
|
155
|
+
}
|
|
156
|
+
// Step 4a: Recover stuck tasks (best-effort) before deciding whether to (re)start a runner.
|
|
157
|
+
// This is what unblocks orphaned/infinite-hang scenarios without manual intervention.
|
|
158
|
+
const config = (0, loader_js_1.loadConfig)(project.path);
|
|
159
|
+
const recovery = await (0, stuck_task_recovery_js_1.recoverStuckTasks)({
|
|
160
|
+
projectPath: project.path,
|
|
161
|
+
projectDb,
|
|
162
|
+
globalDb: global.db,
|
|
163
|
+
config,
|
|
164
|
+
dryRun,
|
|
165
|
+
});
|
|
166
|
+
recoveredActions = recovery.actions.length;
|
|
167
|
+
skippedRecoveryDueToSafetyLimit = recovery.skippedDueToSafetyLimit;
|
|
168
|
+
if (recoveredActions > 0 && !quiet) {
|
|
169
|
+
log(`Recovered ${recoveredActions} stuck item(s) in ${project.path}`);
|
|
170
|
+
}
|
|
171
|
+
if (skippedRecoveryDueToSafetyLimit && !quiet) {
|
|
172
|
+
log(`Skipping auto-recovery in ${project.path}: safety limit hit (maxIncidentsPerHour)`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
closeProjectDb();
|
|
143
177
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// If sanitise/recovery can't run (DB missing/corrupt), we still proceed with runner checks.
|
|
181
|
+
}
|
|
182
|
+
// Check for pending work after sanitise/recovery
|
|
183
|
+
const hasWork = await (0, wakeup_checks_js_1.projectHasPendingWork)(project.path);
|
|
184
|
+
if (!hasWork) {
|
|
185
|
+
const noWorkReason = sanitisedActions > 0
|
|
186
|
+
? `No pending tasks after sanitise (${sanitisedActions} action(s))`
|
|
187
|
+
: 'No pending tasks';
|
|
188
|
+
log(`Skipping ${project.path}: ${noWorkReason.toLowerCase()}`);
|
|
189
|
+
results.push({
|
|
190
|
+
action: 'none',
|
|
191
|
+
reason: noWorkReason,
|
|
148
192
|
projectPath: project.path,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
193
|
+
recoveredActions,
|
|
194
|
+
skippedRecoveryDueToSafetyLimit,
|
|
195
|
+
deletedInvocationLogs,
|
|
196
|
+
sanitisedActions,
|
|
153
197
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
const projectConfig = (0, loader_js_1.loadConfig)(project.path);
|
|
201
|
+
// Check global provider backoffs
|
|
202
|
+
const coderProvider = projectConfig.ai?.coder?.provider;
|
|
203
|
+
const reviewerProvider = projectConfig.ai?.reviewer?.provider;
|
|
204
|
+
const providersToCheck = [coderProvider, reviewerProvider].filter(Boolean);
|
|
205
|
+
let isBackedOff = false;
|
|
206
|
+
let backedOffProvider = '';
|
|
207
|
+
let remainingMs = 0;
|
|
208
|
+
for (const provider of providersToCheck) {
|
|
209
|
+
const ms = (0, global_db_js_1.getProviderBackoffRemainingMs)(provider);
|
|
210
|
+
if (ms > 0) {
|
|
211
|
+
isBackedOff = true;
|
|
212
|
+
backedOffProvider = provider;
|
|
213
|
+
remainingMs = ms;
|
|
214
|
+
break;
|
|
161
215
|
}
|
|
162
216
|
}
|
|
163
|
-
|
|
164
|
-
|
|
217
|
+
if (isBackedOff) {
|
|
218
|
+
const remainingMinutes = Math.ceil(remainingMs / 60000);
|
|
219
|
+
log(`Skipping ${project.path}: Provider '${backedOffProvider}' is in backoff for ${remainingMinutes}m`);
|
|
220
|
+
results.push({
|
|
221
|
+
action: 'skipped',
|
|
222
|
+
reason: `Provider '${backedOffProvider}' backed off for ${remainingMinutes}m`,
|
|
223
|
+
projectPath: project.path,
|
|
224
|
+
});
|
|
225
|
+
continue;
|
|
165
226
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
skippedRecoveryDueToSafetyLimit,
|
|
183
|
-
deletedInvocationLogs,
|
|
184
|
-
sanitisedActions,
|
|
185
|
-
});
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
const projectConfig = (0, loader_js_1.loadConfig)(project.path);
|
|
189
|
-
const parallelEnabled = projectConfig.runners?.parallel?.enabled === true;
|
|
190
|
-
const configuredMaxClonesRaw = Number(projectConfig.runners?.parallel?.maxClones);
|
|
191
|
-
const configuredMaxClones = Number.isFinite(configuredMaxClonesRaw) && configuredMaxClonesRaw > 0
|
|
192
|
-
? configuredMaxClonesRaw
|
|
193
|
-
: 3;
|
|
194
|
-
// Skip projects currently executing a parallel session before attempting recovery/startup.
|
|
195
|
-
// This prevents parallel runners from being interfered with by a cron-managed runner.
|
|
196
|
-
if ((0, wakeup_checks_js_1.hasActiveParallelSessionForProject)(project.path)) {
|
|
197
|
-
let retrySummary = '';
|
|
198
|
-
let skipForParallelSession = true;
|
|
199
|
-
let scaledDown = 0;
|
|
200
|
-
let resumed = 0;
|
|
201
|
-
let wouldScaleDown = 0;
|
|
202
|
-
let wouldResume = 0;
|
|
203
|
-
const activeSessions = global.db
|
|
204
|
-
.prepare(`SELECT id
|
|
227
|
+
const parallelEnabled = projectConfig.runners?.parallel?.enabled === true;
|
|
228
|
+
const configuredMaxClonesRaw = Number(projectConfig.runners?.parallel?.maxClones);
|
|
229
|
+
const configuredMaxClones = Number.isFinite(configuredMaxClonesRaw) && configuredMaxClonesRaw > 0
|
|
230
|
+
? configuredMaxClonesRaw
|
|
231
|
+
: 3;
|
|
232
|
+
// Skip projects currently executing a parallel session before attempting recovery/startup.
|
|
233
|
+
// This prevents parallel runners from being interfered with by a cron-managed runner.
|
|
234
|
+
if ((0, wakeup_checks_js_1.hasActiveParallelSessionForProject)(project.path)) {
|
|
235
|
+
let retrySummary = '';
|
|
236
|
+
let skipForParallelSession = true;
|
|
237
|
+
let scaledDown = 0;
|
|
238
|
+
let resumed = 0;
|
|
239
|
+
let wouldScaleDown = 0;
|
|
240
|
+
let wouldResume = 0;
|
|
241
|
+
const activeSessions = global.db
|
|
242
|
+
.prepare(`SELECT id
|
|
205
243
|
FROM parallel_sessions
|
|
206
244
|
WHERE project_path = ?
|
|
207
245
|
AND status NOT IN ('completed', 'failed', 'aborted', 'blocked_validation', 'blocked_recovery')`)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
246
|
+
.all(project.path);
|
|
247
|
+
// Config-aware mode reconciliation (parallel -> single):
|
|
248
|
+
// if parallel is disabled, convert only when the active parallel runners
|
|
249
|
+
// are idle to avoid interrupting in-flight tasks.
|
|
250
|
+
if (!parallelEnabled && activeSessions.length > 0) {
|
|
251
|
+
const sessionRunners = activeSessions.flatMap((session) => global.db
|
|
252
|
+
.prepare(`SELECT id, pid, status, current_task_id
|
|
215
253
|
FROM runners
|
|
216
254
|
WHERE parallel_session_id = ?
|
|
217
255
|
AND status != 'stopped'
|
|
218
256
|
AND heartbeat_at > datetime('now', '-5 minutes')`)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
.all(session.id));
|
|
258
|
+
const hasBusyRunner = sessionRunners.some((runner) => (runner.status ?? '').toLowerCase() !== 'idle' || !!runner.current_task_id);
|
|
259
|
+
if (hasBusyRunner) {
|
|
260
|
+
const reason = 'Parallel->single mode switch pending (active workstream runner busy)';
|
|
261
|
+
log(`Skipping ${project.path}: ${reason.toLowerCase()}`);
|
|
262
|
+
results.push({
|
|
263
|
+
action: dryRun ? 'would_start' : 'none',
|
|
264
|
+
reason,
|
|
265
|
+
projectPath: project.path,
|
|
266
|
+
deletedInvocationLogs,
|
|
267
|
+
});
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (dryRun) {
|
|
271
|
+
const reason = 'Would recycle idle parallel session to apply single-runner mode';
|
|
272
|
+
log(`Would reconcile ${project.path}: ${reason.toLowerCase()}`);
|
|
273
|
+
results.push({
|
|
274
|
+
action: 'would_start',
|
|
275
|
+
reason,
|
|
276
|
+
projectPath: project.path,
|
|
277
|
+
deletedInvocationLogs,
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
for (const runner of sessionRunners) {
|
|
282
|
+
if (runner.pid)
|
|
283
|
+
(0, wakeup_runner_js_1.killProcess)(runner.pid);
|
|
284
|
+
global.db.prepare('DELETE FROM runners WHERE id = ?').run(runner.id);
|
|
285
|
+
}
|
|
286
|
+
for (const session of activeSessions) {
|
|
287
|
+
global.db.prepare(`UPDATE workstreams
|
|
250
288
|
SET status = 'aborted',
|
|
251
289
|
runner_id = NULL,
|
|
252
290
|
lease_expires_at = NULL,
|
|
@@ -256,34 +294,34 @@ async function wakeup(options = {}) {
|
|
|
256
294
|
completed_at = COALESCE(completed_at, datetime('now'))
|
|
257
295
|
WHERE session_id = ?
|
|
258
296
|
AND status NOT IN ('completed', 'failed', 'aborted')`).run(session.id);
|
|
259
|
-
|
|
297
|
+
global.db.prepare(`UPDATE parallel_sessions
|
|
260
298
|
SET status = 'aborted',
|
|
261
299
|
completed_at = COALESCE(completed_at, datetime('now'))
|
|
262
300
|
WHERE id = ?`).run(session.id);
|
|
301
|
+
}
|
|
302
|
+
skipForParallelSession = false;
|
|
303
|
+
retrySummary = ', recycled idle parallel session to apply single-runner mode';
|
|
263
304
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
for (const session of activeSessions) {
|
|
268
|
-
const sessionRunners = global.db
|
|
269
|
-
.prepare(`SELECT id, pid, status, current_task_id
|
|
305
|
+
for (const session of activeSessions) {
|
|
306
|
+
const sessionRunners = global.db
|
|
307
|
+
.prepare(`SELECT id, pid, status, current_task_id
|
|
270
308
|
FROM runners
|
|
271
309
|
WHERE parallel_session_id = ?
|
|
272
310
|
AND status != 'stopped'
|
|
273
311
|
AND heartbeat_at > datetime('now', '-5 minutes')
|
|
274
312
|
ORDER BY started_at DESC, heartbeat_at DESC`)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
else {
|
|
283
|
-
if (idleCandidate.pid) {
|
|
284
|
-
(0, wakeup_runner_js_1.killProcess)(idleCandidate.pid);
|
|
313
|
+
.all(session.id);
|
|
314
|
+
if (sessionRunners.length > configuredMaxClones) {
|
|
315
|
+
const idleCandidate = sessionRunners.find((r) => (r.status ?? '').toLowerCase() === 'idle' && !r.current_task_id);
|
|
316
|
+
if (idleCandidate) {
|
|
317
|
+
if (dryRun) {
|
|
318
|
+
wouldScaleDown += 1;
|
|
285
319
|
}
|
|
286
|
-
|
|
320
|
+
else {
|
|
321
|
+
if (idleCandidate.pid) {
|
|
322
|
+
(0, wakeup_runner_js_1.killProcess)(idleCandidate.pid);
|
|
323
|
+
}
|
|
324
|
+
global.db.prepare(`UPDATE workstreams
|
|
287
325
|
SET runner_id = NULL,
|
|
288
326
|
lease_expires_at = datetime('now', '+5 minutes'),
|
|
289
327
|
next_retry_at = datetime('now', '+5 minutes'),
|
|
@@ -291,14 +329,14 @@ async function wakeup(options = {}) {
|
|
|
291
329
|
last_reconciled_at = datetime('now')
|
|
292
330
|
WHERE session_id = ?
|
|
293
331
|
AND runner_id = ?`).run(session.id, idleCandidate.id);
|
|
294
|
-
|
|
295
|
-
|
|
332
|
+
global.db.prepare('DELETE FROM runners WHERE id = ?').run(idleCandidate.id);
|
|
333
|
+
scaledDown += 1;
|
|
334
|
+
}
|
|
296
335
|
}
|
|
297
336
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
.prepare(`SELECT id
|
|
337
|
+
else if (sessionRunners.length < configuredMaxClones) {
|
|
338
|
+
const throttled = global.db
|
|
339
|
+
.prepare(`SELECT id
|
|
302
340
|
FROM workstreams
|
|
303
341
|
WHERE session_id = ?
|
|
304
342
|
AND status = 'running'
|
|
@@ -307,79 +345,79 @@ async function wakeup(options = {}) {
|
|
|
307
345
|
AND last_reconcile_action = 'concurrency_throttle'
|
|
308
346
|
ORDER BY last_reconciled_at ASC
|
|
309
347
|
LIMIT 1`)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
348
|
+
.get(session.id);
|
|
349
|
+
if (throttled) {
|
|
350
|
+
if (dryRun) {
|
|
351
|
+
wouldResume += 1;
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
global.db.prepare(`UPDATE workstreams
|
|
317
355
|
SET lease_expires_at = datetime('now'),
|
|
318
356
|
next_retry_at = datetime('now'),
|
|
319
357
|
last_reconcile_action = 'concurrency_resume',
|
|
320
358
|
last_reconciled_at = datetime('now')
|
|
321
359
|
WHERE id = ?`).run(throttled.id);
|
|
322
|
-
|
|
360
|
+
resumed += 1;
|
|
361
|
+
}
|
|
323
362
|
}
|
|
324
363
|
}
|
|
325
364
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
365
|
+
if (!dryRun) {
|
|
366
|
+
const recovery = (0, wakeup_reconcile_js_1.reconcileParallelSessionRecovery)(global.db, project.path);
|
|
367
|
+
if (recovery.workstreamsToRestart.length > 0) {
|
|
368
|
+
for (const ws of recovery.workstreamsToRestart) {
|
|
369
|
+
(0, wakeup_runner_js_1.restartWorkstreamRunner)(ws);
|
|
370
|
+
}
|
|
371
|
+
retrySummary += `, restarted ${recovery.workstreamsToRestart.length} workstream runner(s)`;
|
|
372
|
+
}
|
|
373
|
+
if (recovery.blockedWorkstreams > 0) {
|
|
374
|
+
retrySummary += `, blocked ${recovery.blockedWorkstreams} workstream(s)`;
|
|
375
|
+
}
|
|
376
|
+
if (scaledDown > 0) {
|
|
377
|
+
retrySummary += `, scaled down ${scaledDown} idle runner(s) to maxClones=${configuredMaxClones}`;
|
|
378
|
+
}
|
|
379
|
+
if (resumed > 0) {
|
|
380
|
+
retrySummary += `, resumed ${resumed} throttled workstream(s)`;
|
|
381
|
+
}
|
|
382
|
+
// Re-check activity after recovery. If reconciliation cleared stale
|
|
383
|
+
// session state for this project, continue to normal startup logic.
|
|
384
|
+
if (!(0, wakeup_checks_js_1.hasActiveParallelSessionForProject)(project.path)) {
|
|
385
|
+
skipForParallelSession = false;
|
|
386
|
+
if (retrySummary.length > 0) {
|
|
387
|
+
retrySummary += ', session state reconciled';
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
retrySummary = ', session state reconciled';
|
|
391
|
+
}
|
|
332
392
|
}
|
|
333
|
-
retrySummary += `, restarted ${recovery.workstreamsToRestart.length} workstream runner(s)`;
|
|
334
|
-
}
|
|
335
|
-
if (recovery.blockedWorkstreams > 0) {
|
|
336
|
-
retrySummary += `, blocked ${recovery.blockedWorkstreams} workstream(s)`;
|
|
337
|
-
}
|
|
338
|
-
if (scaledDown > 0) {
|
|
339
|
-
retrySummary += `, scaled down ${scaledDown} idle runner(s) to maxClones=${configuredMaxClones}`;
|
|
340
|
-
}
|
|
341
|
-
if (resumed > 0) {
|
|
342
|
-
retrySummary += `, resumed ${resumed} throttled workstream(s)`;
|
|
343
393
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
skipForParallelSession = false;
|
|
348
|
-
if (retrySummary.length > 0) {
|
|
349
|
-
retrySummary += ', session state reconciled';
|
|
394
|
+
else {
|
|
395
|
+
if (wouldScaleDown > 0) {
|
|
396
|
+
retrySummary += `, would scale down ${wouldScaleDown} idle runner(s) to maxClones=${configuredMaxClones}`;
|
|
350
397
|
}
|
|
351
|
-
|
|
352
|
-
retrySummary
|
|
398
|
+
if (wouldResume > 0) {
|
|
399
|
+
retrySummary += `, would resume ${wouldResume} throttled workstream(s)`;
|
|
353
400
|
}
|
|
354
401
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (wouldScaleDown > 0) {
|
|
358
|
-
retrySummary += `, would scale down ${wouldScaleDown} idle runner(s) to maxClones=${configuredMaxClones}`;
|
|
402
|
+
if (!skipForParallelSession) {
|
|
403
|
+
log(`Reconciled stale parallel session for ${project.path}; proceeding with startup`);
|
|
359
404
|
}
|
|
360
|
-
|
|
361
|
-
|
|
405
|
+
else {
|
|
406
|
+
log(`Skipping ${project.path}: active parallel session in progress${retrySummary}`);
|
|
407
|
+
results.push({
|
|
408
|
+
action: 'none',
|
|
409
|
+
reason: `Parallel session already running${retrySummary}`,
|
|
410
|
+
projectPath: project.path,
|
|
411
|
+
deletedInvocationLogs,
|
|
412
|
+
});
|
|
413
|
+
continue;
|
|
362
414
|
}
|
|
363
415
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
results.push({
|
|
370
|
-
action: 'none',
|
|
371
|
-
reason: `Parallel session already running${retrySummary}`,
|
|
372
|
-
projectPath: project.path,
|
|
373
|
-
deletedInvocationLogs,
|
|
374
|
-
});
|
|
375
|
-
continue;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
// Config-aware mode reconciliation:
|
|
379
|
-
// if parallel is enabled but an idle standalone runner is active, recycle it
|
|
380
|
-
// so wakeup applies current parallel settings without manual restart.
|
|
381
|
-
const activeStandaloneRunner = global.db
|
|
382
|
-
.prepare(`SELECT id, pid, status, current_task_id
|
|
416
|
+
// Config-aware mode reconciliation:
|
|
417
|
+
// if parallel is enabled but an idle standalone runner is active, recycle it
|
|
418
|
+
// so wakeup applies current parallel settings without manual restart.
|
|
419
|
+
const activeStandaloneRunner = global.db
|
|
420
|
+
.prepare(`SELECT id, pid, status, current_task_id
|
|
383
421
|
FROM runners
|
|
384
422
|
WHERE project_path = ?
|
|
385
423
|
AND parallel_session_id IS NULL
|
|
@@ -387,113 +425,114 @@ async function wakeup(options = {}) {
|
|
|
387
425
|
AND heartbeat_at > datetime('now', '-5 minutes')
|
|
388
426
|
ORDER BY heartbeat_at DESC
|
|
389
427
|
LIMIT 1`)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
428
|
+
.get(project.path);
|
|
429
|
+
if (activeStandaloneRunner && parallelEnabled) {
|
|
430
|
+
const isIdle = (activeStandaloneRunner.status ?? '').toLowerCase() === 'idle' && !activeStandaloneRunner.current_task_id;
|
|
431
|
+
if (isIdle) {
|
|
432
|
+
if (dryRun) {
|
|
433
|
+
log(`Would recycle idle standalone runner for ${project.path} to apply parallel mode`);
|
|
434
|
+
results.push({
|
|
435
|
+
action: 'would_start',
|
|
436
|
+
reason: 'Would restart idle runner to apply parallel mode',
|
|
437
|
+
projectPath: project.path,
|
|
438
|
+
deletedInvocationLogs,
|
|
439
|
+
});
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
if (activeStandaloneRunner.pid) {
|
|
443
|
+
(0, wakeup_runner_js_1.killProcess)(activeStandaloneRunner.pid);
|
|
444
|
+
}
|
|
445
|
+
global.db.prepare('DELETE FROM runners WHERE id = ?').run(activeStandaloneRunner.id);
|
|
446
|
+
const restartResult = (0, wakeup_runner_js_1.startRunner)(project.path);
|
|
447
|
+
if (restartResult) {
|
|
448
|
+
results.push({
|
|
449
|
+
action: 'restarted',
|
|
450
|
+
reason: 'Restarted idle runner to apply parallel mode',
|
|
451
|
+
pid: restartResult.pid,
|
|
452
|
+
projectPath: project.path,
|
|
453
|
+
deletedInvocationLogs,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
results.push({
|
|
458
|
+
action: 'none',
|
|
459
|
+
reason: 'Failed to restart idle runner for parallel mode',
|
|
460
|
+
projectPath: project.path,
|
|
461
|
+
deletedInvocationLogs,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
402
464
|
continue;
|
|
403
465
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
results.push({
|
|
420
|
-
action: 'none',
|
|
421
|
-
reason: 'Failed to restart idle runner for parallel mode',
|
|
422
|
-
projectPath: project.path,
|
|
423
|
-
deletedInvocationLogs,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
466
|
+
}
|
|
467
|
+
// Skip if project already has an active runner (after recovery, which may have killed/removed it).
|
|
468
|
+
if ((0, wakeup_checks_js_1.hasActiveRunnerForProject)(project.path)) {
|
|
469
|
+
log(`Skipping ${project.path}: runner already active`);
|
|
470
|
+
results.push({
|
|
471
|
+
action: 'none',
|
|
472
|
+
reason: recoveredActions > 0
|
|
473
|
+
? `Runner already active (recovered ${recoveredActions} stuck item(s))`
|
|
474
|
+
: 'Runner already active',
|
|
475
|
+
projectPath: project.path,
|
|
476
|
+
recoveredActions,
|
|
477
|
+
skippedRecoveryDueToSafetyLimit,
|
|
478
|
+
deletedInvocationLogs,
|
|
479
|
+
sanitisedActions,
|
|
480
|
+
});
|
|
426
481
|
continue;
|
|
427
482
|
}
|
|
483
|
+
// Start runner for this project
|
|
484
|
+
const willParallel = parallelEnabled;
|
|
485
|
+
log(`Starting ${willParallel ? 'parallel session' : 'runner'} for: ${project.path}`);
|
|
486
|
+
if (dryRun) {
|
|
487
|
+
results.push({
|
|
488
|
+
action: 'would_start',
|
|
489
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); would start runner (dry-run)` : `Would start runner (dry-run)`,
|
|
490
|
+
projectPath: project.path,
|
|
491
|
+
recoveredActions,
|
|
492
|
+
skippedRecoveryDueToSafetyLimit,
|
|
493
|
+
deletedInvocationLogs,
|
|
494
|
+
sanitisedActions,
|
|
495
|
+
});
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const startResult = (0, wakeup_runner_js_1.startRunner)(project.path);
|
|
499
|
+
if (startResult) {
|
|
500
|
+
const mode = startResult.parallel ? 'parallel session' : 'runner';
|
|
501
|
+
results.push({
|
|
502
|
+
action: 'started',
|
|
503
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); started ${mode}` : `Started ${mode}`,
|
|
504
|
+
pid: startResult.pid,
|
|
505
|
+
projectPath: project.path,
|
|
506
|
+
recoveredActions,
|
|
507
|
+
skippedRecoveryDueToSafetyLimit,
|
|
508
|
+
deletedInvocationLogs,
|
|
509
|
+
sanitisedActions,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
results.push({
|
|
514
|
+
action: 'none',
|
|
515
|
+
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); failed to start runner` : 'Failed to start runner',
|
|
516
|
+
projectPath: project.path,
|
|
517
|
+
recoveredActions,
|
|
518
|
+
skippedRecoveryDueToSafetyLimit,
|
|
519
|
+
deletedInvocationLogs,
|
|
520
|
+
sanitisedActions,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
428
523
|
}
|
|
429
|
-
//
|
|
430
|
-
if (
|
|
431
|
-
log(`Skipping ${project.path}: runner already active`);
|
|
432
|
-
results.push({
|
|
433
|
-
action: 'none',
|
|
434
|
-
reason: recoveredActions > 0
|
|
435
|
-
? `Runner already active (recovered ${recoveredActions} stuck item(s))`
|
|
436
|
-
: 'Runner already active',
|
|
437
|
-
projectPath: project.path,
|
|
438
|
-
recoveredActions,
|
|
439
|
-
skippedRecoveryDueToSafetyLimit,
|
|
440
|
-
deletedInvocationLogs,
|
|
441
|
-
sanitisedActions,
|
|
442
|
-
});
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
// Start runner for this project
|
|
446
|
-
const willParallel = parallelEnabled;
|
|
447
|
-
log(`Starting ${willParallel ? 'parallel session' : 'runner'} for: ${project.path}`);
|
|
448
|
-
if (dryRun) {
|
|
449
|
-
results.push({
|
|
450
|
-
action: 'would_start',
|
|
451
|
-
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); would start runner (dry-run)` : `Would start runner (dry-run)`,
|
|
452
|
-
projectPath: project.path,
|
|
453
|
-
recoveredActions,
|
|
454
|
-
skippedRecoveryDueToSafetyLimit,
|
|
455
|
-
deletedInvocationLogs,
|
|
456
|
-
sanitisedActions,
|
|
457
|
-
});
|
|
458
|
-
continue;
|
|
459
|
-
}
|
|
460
|
-
const startResult = (0, wakeup_runner_js_1.startRunner)(project.path);
|
|
461
|
-
if (startResult) {
|
|
462
|
-
const mode = startResult.parallel ? 'parallel session' : 'runner';
|
|
463
|
-
results.push({
|
|
464
|
-
action: 'started',
|
|
465
|
-
reason: recoveredActions > 0 ? `Recovered ${recoveredActions} stuck item(s); started ${mode}` : `Started ${mode}`,
|
|
466
|
-
pid: startResult.pid,
|
|
467
|
-
projectPath: project.path,
|
|
468
|
-
recoveredActions,
|
|
469
|
-
skippedRecoveryDueToSafetyLimit,
|
|
470
|
-
deletedInvocationLogs,
|
|
471
|
-
sanitisedActions,
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
524
|
+
// If no specific results, add a summary
|
|
525
|
+
if (results.length === 0) {
|
|
475
526
|
results.push({
|
|
476
527
|
action: 'none',
|
|
477
|
-
reason:
|
|
478
|
-
projectPath: project.path,
|
|
479
|
-
recoveredActions,
|
|
480
|
-
skippedRecoveryDueToSafetyLimit,
|
|
481
|
-
deletedInvocationLogs,
|
|
482
|
-
sanitisedActions,
|
|
528
|
+
reason: 'No action needed',
|
|
483
529
|
});
|
|
484
530
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (results.length === 0) {
|
|
488
|
-
results.push({
|
|
489
|
-
action: 'none',
|
|
490
|
-
reason: 'No action needed',
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
return results;
|
|
531
|
+
return results;
|
|
532
|
+
});
|
|
494
533
|
}
|
|
495
534
|
finally {
|
|
496
|
-
|
|
535
|
+
isWakeupRunning = false;
|
|
497
536
|
}
|
|
498
537
|
}
|
|
499
538
|
/**
|
|
@@ -502,8 +541,7 @@ async function wakeup(options = {}) {
|
|
|
502
541
|
async function checkWakeupNeeded() {
|
|
503
542
|
const lockStatus = (0, lock_js_1.checkLockStatus)();
|
|
504
543
|
if (lockStatus.locked && lockStatus.pid) {
|
|
505
|
-
|
|
506
|
-
try {
|
|
544
|
+
return (0, global_db_js_1.withGlobalDatabase)(async (db) => {
|
|
507
545
|
const staleRunners = (0, heartbeat_js_1.findStaleRunners)(db);
|
|
508
546
|
if (staleRunners.length > 0) {
|
|
509
547
|
return {
|
|
@@ -512,10 +550,7 @@ async function checkWakeupNeeded() {
|
|
|
512
550
|
};
|
|
513
551
|
}
|
|
514
552
|
return { needed: false, reason: 'Runner is healthy' };
|
|
515
|
-
}
|
|
516
|
-
finally {
|
|
517
|
-
close();
|
|
518
|
-
}
|
|
553
|
+
});
|
|
519
554
|
}
|
|
520
555
|
if (lockStatus.isZombie) {
|
|
521
556
|
return { needed: true, reason: 'Zombie lock needs cleanup' };
|