@covibes/zeroshot 5.4.0 → 5.4.1
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/LICENSE +1 -1
- package/README.md +8 -465
- package/cli/index.js +101 -5231
- package/package.json +16 -130
- package/CHANGELOG.md +0 -537
- package/cli/commands/providers.js +0 -149
- package/cli/lib/first-run.js +0 -211
- package/cli/lib/update-checker.js +0 -281
- package/cli/message-formatter-utils.js +0 -75
- package/cli/message-formatters-normal.js +0 -275
- package/cli/message-formatters-watch.js +0 -181
- package/cluster-templates/base-templates/debug-workflow.json +0 -375
- package/cluster-templates/base-templates/full-workflow.json +0 -581
- package/cluster-templates/base-templates/single-worker.json +0 -63
- package/cluster-templates/base-templates/worker-validator.json +0 -208
- package/cluster-templates/conductor-bootstrap.json +0 -124
- package/docker/zeroshot-cluster/Dockerfile +0 -152
- package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
- package/lib/completion.js +0 -174
- package/lib/docker-config.js +0 -220
- package/lib/git-remote-utils.js +0 -165
- package/lib/id-detector.js +0 -56
- package/lib/provider-defaults.js +0 -62
- package/lib/provider-detection.js +0 -59
- package/lib/provider-names.js +0 -57
- package/lib/settings/claude-auth.js +0 -78
- package/lib/settings.js +0 -503
- package/lib/stream-json-parser.js +0 -10
- package/scripts/fix-node-pty-permissions.js +0 -75
- package/scripts/record-demo.sh +0 -279
- package/scripts/setup-merge-queue.sh +0 -170
- package/scripts/test-install.sh +0 -40
- package/scripts/validate-templates.js +0 -100
- package/src/agent/agent-config.js +0 -246
- package/src/agent/agent-context-builder.js +0 -444
- package/src/agent/agent-hook-executor.js +0 -599
- package/src/agent/agent-lifecycle.js +0 -743
- package/src/agent/agent-stuck-detector.js +0 -256
- package/src/agent/agent-task-executor.js +0 -1690
- package/src/agent/agent-trigger-evaluator.js +0 -67
- package/src/agent/output-extraction.js +0 -217
- package/src/agent/output-reformatter.js +0 -175
- package/src/agent/schema-utils.js +0 -146
- package/src/agent-wrapper.js +0 -579
- package/src/agents/git-pusher-template.js +0 -285
- package/src/attach/attach-client.js +0 -438
- package/src/attach/attach-server.js +0 -543
- package/src/attach/index.js +0 -35
- package/src/attach/protocol.js +0 -220
- package/src/attach/ring-buffer.js +0 -121
- package/src/attach/socket-discovery.js +0 -267
- package/src/claude-task-runner.js +0 -574
- package/src/config-router.js +0 -80
- package/src/config-validator.js +0 -1925
- package/src/copy-worker.js +0 -43
- package/src/input-helpers.js +0 -65
- package/src/isolation-manager.js +0 -1471
- package/src/issue-providers/README.md +0 -305
- package/src/issue-providers/azure-devops-provider.js +0 -273
- package/src/issue-providers/base-provider.js +0 -232
- package/src/issue-providers/github-provider.js +0 -179
- package/src/issue-providers/gitlab-provider.js +0 -241
- package/src/issue-providers/index.js +0 -196
- package/src/issue-providers/jira-provider.js +0 -239
- package/src/ledger.js +0 -631
- package/src/lib/safe-exec.js +0 -88
- package/src/logic-engine.js +0 -223
- package/src/message-bus-bridge.js +0 -139
- package/src/message-bus.js +0 -249
- package/src/name-generator.js +0 -232
- package/src/orchestrator.js +0 -2815
- package/src/preflight.js +0 -639
- package/src/process-metrics.js +0 -608
- package/src/providers/anthropic/cli-builder.js +0 -73
- package/src/providers/anthropic/index.js +0 -204
- package/src/providers/anthropic/models.js +0 -23
- package/src/providers/anthropic/output-parser.js +0 -177
- package/src/providers/base-provider.js +0 -251
- package/src/providers/capabilities.js +0 -60
- package/src/providers/google/cli-builder.js +0 -55
- package/src/providers/google/index.js +0 -116
- package/src/providers/google/models.js +0 -24
- package/src/providers/google/output-parser.js +0 -101
- package/src/providers/index.js +0 -91
- package/src/providers/openai/cli-builder.js +0 -133
- package/src/providers/openai/index.js +0 -136
- package/src/providers/openai/models.js +0 -21
- package/src/providers/openai/output-parser.js +0 -143
- package/src/providers/opencode/cli-builder.js +0 -42
- package/src/providers/opencode/index.js +0 -103
- package/src/providers/opencode/models.js +0 -55
- package/src/providers/opencode/output-parser.js +0 -122
- package/src/schemas/sub-cluster.js +0 -185
- package/src/status-footer.js +0 -1026
- package/src/sub-cluster-wrapper.js +0 -601
- package/src/task-runner.js +0 -30
- package/src/template-resolver.js +0 -372
- package/src/tui/CHANGES.txt +0 -133
- package/src/tui/LAYOUT.md +0 -261
- package/src/tui/README.txt +0 -192
- package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
- package/src/tui/data-poller.js +0 -349
- package/src/tui/demo.js +0 -208
- package/src/tui/formatters.js +0 -129
- package/src/tui/index.js +0 -193
- package/src/tui/keybindings.js +0 -324
- package/src/tui/layout.js +0 -334
- package/src/tui/renderer.js +0 -184
- package/task-lib/attachable-watcher.js +0 -309
- package/task-lib/claude-recovery.js +0 -156
- package/task-lib/commands/clean.js +0 -50
- package/task-lib/commands/episodes.js +0 -105
- package/task-lib/commands/get-log-path.js +0 -23
- package/task-lib/commands/kill.js +0 -32
- package/task-lib/commands/list.js +0 -105
- package/task-lib/commands/logs.js +0 -453
- package/task-lib/commands/resume.js +0 -42
- package/task-lib/commands/run.js +0 -57
- package/task-lib/commands/schedule.js +0 -105
- package/task-lib/commands/scheduler-cmd.js +0 -96
- package/task-lib/commands/schedules.js +0 -148
- package/task-lib/commands/status.js +0 -44
- package/task-lib/commands/unschedule.js +0 -16
- package/task-lib/completion.js +0 -9
- package/task-lib/config.js +0 -8
- package/task-lib/name-generator.js +0 -230
- package/task-lib/package.json +0 -3
- package/task-lib/runner.js +0 -201
- package/task-lib/scheduler.js +0 -252
- package/task-lib/store.js +0 -529
- package/task-lib/tui/formatters.js +0 -215
- package/task-lib/tui/index.js +0 -197
- package/task-lib/tui/layout.js +0 -111
- package/task-lib/tui/renderer.js +0 -129
- package/task-lib/tui.js +0 -425
- package/task-lib/watcher.js +0 -226
package/task-lib/runner.js
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import { fork } from 'child_process';
|
|
2
|
-
import { join, dirname } from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import { LOGS_DIR } from './config.js';
|
|
5
|
-
import { addTask, generateId, ensureDirs } from './store.js';
|
|
6
|
-
import { createRequire } from 'module';
|
|
7
|
-
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
const { loadSettings } = require('../lib/settings.js');
|
|
10
|
-
const { normalizeProviderName } = require('../lib/provider-names');
|
|
11
|
-
const { getProvider } = require('../src/providers');
|
|
12
|
-
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
-
|
|
15
|
-
export async function spawnTask(prompt, options = {}) {
|
|
16
|
-
ensureDirs();
|
|
17
|
-
|
|
18
|
-
const id = generateId();
|
|
19
|
-
const logFile = join(LOGS_DIR, `${id}.log`);
|
|
20
|
-
const cwd = options.cwd || process.cwd();
|
|
21
|
-
|
|
22
|
-
const settings = loadSettings();
|
|
23
|
-
const { providerName, provider, providerSettings, levelOverrides } = resolveProviderContext(
|
|
24
|
-
options,
|
|
25
|
-
settings
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const outputFormat = resolveOutputFormat(options);
|
|
29
|
-
const jsonSchema = resolveJsonSchema(options, outputFormat);
|
|
30
|
-
const modelSpec = resolveModelSpec(options, provider, providerSettings, levelOverrides);
|
|
31
|
-
|
|
32
|
-
const cliFeatures = await provider.getCliFeatures();
|
|
33
|
-
const commandSpec = provider.buildCommand(prompt, {
|
|
34
|
-
modelSpec,
|
|
35
|
-
outputFormat,
|
|
36
|
-
jsonSchema,
|
|
37
|
-
cwd,
|
|
38
|
-
autoApprove: true,
|
|
39
|
-
cliFeatures,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const finalArgs = resolveFinalArgs(commandSpec, providerName, options);
|
|
43
|
-
const task = buildTaskRecord({
|
|
44
|
-
id,
|
|
45
|
-
prompt,
|
|
46
|
-
cwd,
|
|
47
|
-
options,
|
|
48
|
-
logFile,
|
|
49
|
-
providerName,
|
|
50
|
-
modelSpec,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
addTask(task);
|
|
54
|
-
|
|
55
|
-
const watcherConfig = buildWatcherConfig(
|
|
56
|
-
outputFormat,
|
|
57
|
-
jsonSchema,
|
|
58
|
-
options,
|
|
59
|
-
providerName,
|
|
60
|
-
commandSpec
|
|
61
|
-
);
|
|
62
|
-
const watcherScript = resolveWatcherScript(options);
|
|
63
|
-
spawnWatcher({
|
|
64
|
-
watcherScript,
|
|
65
|
-
id,
|
|
66
|
-
cwd,
|
|
67
|
-
logFile,
|
|
68
|
-
finalArgs,
|
|
69
|
-
watcherConfig,
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return task;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function resolveProviderContext(options, settings) {
|
|
76
|
-
const providerName = normalizeProviderName(
|
|
77
|
-
options.provider || settings.defaultProvider || 'claude'
|
|
78
|
-
);
|
|
79
|
-
const provider = getProvider(providerName);
|
|
80
|
-
const providerSettings = settings.providerSettings?.[providerName] || {};
|
|
81
|
-
const levelOverrides = providerSettings.levelOverrides || {};
|
|
82
|
-
return { providerName, provider, providerSettings, levelOverrides };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function resolveOutputFormat(options) {
|
|
86
|
-
return options.outputFormat || 'stream-json';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function resolveJsonSchema(options, outputFormat) {
|
|
90
|
-
let jsonSchema = options.jsonSchema || null;
|
|
91
|
-
if (jsonSchema && outputFormat !== 'json') {
|
|
92
|
-
console.warn('Warning: --json-schema requires --output-format json, ignoring schema');
|
|
93
|
-
jsonSchema = null;
|
|
94
|
-
}
|
|
95
|
-
return jsonSchema;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function resolveModelSpec(options, provider, providerSettings, levelOverrides) {
|
|
99
|
-
if (options.model) {
|
|
100
|
-
return {
|
|
101
|
-
model: options.model,
|
|
102
|
-
reasoningEffort: options.reasoningEffort,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const level = options.modelLevel || providerSettings.defaultLevel || provider.getDefaultLevel();
|
|
107
|
-
let modelSpec = provider.resolveModelSpec(level, levelOverrides);
|
|
108
|
-
if (options.reasoningEffort) {
|
|
109
|
-
modelSpec = { ...modelSpec, reasoningEffort: options.reasoningEffort };
|
|
110
|
-
}
|
|
111
|
-
return modelSpec;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function resolveFinalArgs(commandSpec, providerName, options) {
|
|
115
|
-
const finalArgs = [...commandSpec.args];
|
|
116
|
-
if (providerName === 'claude') {
|
|
117
|
-
const promptIndex = finalArgs.length - 1;
|
|
118
|
-
if (options.resume) {
|
|
119
|
-
finalArgs.splice(promptIndex, 0, '--resume', options.resume);
|
|
120
|
-
} else if (options.continue) {
|
|
121
|
-
finalArgs.splice(promptIndex, 0, '--continue');
|
|
122
|
-
}
|
|
123
|
-
} else if (options.resume || options.continue) {
|
|
124
|
-
console.warn('Warning: resume/continue is only supported for Claude CLI; ignoring.');
|
|
125
|
-
}
|
|
126
|
-
return finalArgs;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function buildTaskRecord({ id, prompt, cwd, options, logFile, providerName, modelSpec }) {
|
|
130
|
-
return {
|
|
131
|
-
id,
|
|
132
|
-
prompt: prompt.slice(0, 200) + (prompt.length > 200 ? '...' : ''),
|
|
133
|
-
fullPrompt: prompt,
|
|
134
|
-
cwd,
|
|
135
|
-
status: 'running',
|
|
136
|
-
pid: null,
|
|
137
|
-
sessionId: options.resume || options.sessionId || null,
|
|
138
|
-
logFile,
|
|
139
|
-
createdAt: new Date().toISOString(),
|
|
140
|
-
updatedAt: new Date().toISOString(),
|
|
141
|
-
exitCode: null,
|
|
142
|
-
error: null,
|
|
143
|
-
provider: providerName,
|
|
144
|
-
model: modelSpec?.model || null,
|
|
145
|
-
// Schedule reference (if spawned by scheduler)
|
|
146
|
-
scheduleId: options.scheduleId || null,
|
|
147
|
-
// Attach support
|
|
148
|
-
socketPath: null,
|
|
149
|
-
attachable: false,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function buildWatcherConfig(outputFormat, jsonSchema, options, providerName, commandSpec) {
|
|
154
|
-
return {
|
|
155
|
-
outputFormat,
|
|
156
|
-
jsonSchema,
|
|
157
|
-
silentJsonOutput: options.silentJsonOutput || false,
|
|
158
|
-
provider: providerName,
|
|
159
|
-
command: commandSpec.binary,
|
|
160
|
-
env: commandSpec.env || {},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function resolveWatcherScript(options) {
|
|
165
|
-
const useAttachable = options.attachable !== false && !options.jsonSchema;
|
|
166
|
-
return useAttachable ? join(__dirname, 'attachable-watcher.js') : join(__dirname, 'watcher.js');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function spawnWatcher({ watcherScript, id, cwd, logFile, finalArgs, watcherConfig }) {
|
|
170
|
-
const watcher = fork(
|
|
171
|
-
watcherScript,
|
|
172
|
-
[id, cwd, logFile, JSON.stringify(finalArgs), JSON.stringify(watcherConfig)],
|
|
173
|
-
{
|
|
174
|
-
detached: true,
|
|
175
|
-
stdio: 'ignore',
|
|
176
|
-
}
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
watcher.unref();
|
|
180
|
-
watcher.disconnect(); // Close IPC channel so parent can exit
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function isProcessRunning(pid) {
|
|
184
|
-
if (!pid) return false;
|
|
185
|
-
try {
|
|
186
|
-
process.kill(pid, 0);
|
|
187
|
-
return true;
|
|
188
|
-
} catch {
|
|
189
|
-
return false;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function killTask(pid) {
|
|
194
|
-
if (!pid) return false;
|
|
195
|
-
try {
|
|
196
|
-
process.kill(pid, 'SIGTERM');
|
|
197
|
-
return true;
|
|
198
|
-
} catch {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
}
|
package/task-lib/scheduler.js
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Scheduler daemon - runs as background process
|
|
5
|
-
* Checks for due scheduled tasks and spawns them
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, writeFileSync, existsSync, appendFileSync, unlinkSync } from 'fs';
|
|
9
|
-
import { loadSchedules, updateSchedule } from './store.js';
|
|
10
|
-
import { spawnTask } from './runner.js';
|
|
11
|
-
import { SCHEDULER_PID_FILE, SCHEDULER_LOG } from './config.js';
|
|
12
|
-
|
|
13
|
-
const CHECK_INTERVAL = 60000; // 60 seconds
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Parse human-readable interval to milliseconds
|
|
17
|
-
* Supports: 30s, 5m, 2h, 1d, 1w
|
|
18
|
-
*/
|
|
19
|
-
export function parseInterval(str) {
|
|
20
|
-
const match = str.match(/^(\d+)([smhdw])$/i);
|
|
21
|
-
if (!match) return null;
|
|
22
|
-
|
|
23
|
-
const value = parseInt(match[1], 10);
|
|
24
|
-
const unit = match[2].toLowerCase();
|
|
25
|
-
|
|
26
|
-
const multipliers = {
|
|
27
|
-
s: 1000,
|
|
28
|
-
m: 60 * 1000,
|
|
29
|
-
h: 60 * 60 * 1000,
|
|
30
|
-
d: 24 * 60 * 60 * 1000,
|
|
31
|
-
w: 7 * 24 * 60 * 60 * 1000,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return value * multipliers[unit];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Parse cron expression and get next run time
|
|
39
|
-
* Simple cron parser supporting: minute hour day month weekday
|
|
40
|
-
*/
|
|
41
|
-
export function getNextCronTime(cronExpr, fromDate = new Date()) {
|
|
42
|
-
const parts = cronExpr.trim().split(/\s+/);
|
|
43
|
-
if (parts.length !== 5) return null;
|
|
44
|
-
|
|
45
|
-
const [minute, hour] = parts;
|
|
46
|
-
|
|
47
|
-
// Simple implementation - just handle basic cases
|
|
48
|
-
// For full cron support, use cron-parser package
|
|
49
|
-
const next = new Date(fromDate);
|
|
50
|
-
next.setSeconds(0);
|
|
51
|
-
next.setMilliseconds(0);
|
|
52
|
-
|
|
53
|
-
// Handle simple cases
|
|
54
|
-
if (minute !== '*') {
|
|
55
|
-
const mins = minute.split(',').map((m) => parseInt(m, 10));
|
|
56
|
-
const currentMin = next.getMinutes();
|
|
57
|
-
const nextMin = mins.find((m) => m > currentMin) ?? mins[0];
|
|
58
|
-
if (nextMin <= currentMin) {
|
|
59
|
-
next.setHours(next.getHours() + 1);
|
|
60
|
-
}
|
|
61
|
-
next.setMinutes(nextMin);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (hour !== '*') {
|
|
65
|
-
const hours = hour.split(',').map((h) => parseInt(h, 10));
|
|
66
|
-
const currentHour = next.getHours();
|
|
67
|
-
const nextHour = hours.find((h) => h > currentHour) ?? hours[0];
|
|
68
|
-
if (nextHour <= currentHour && minute === '*') {
|
|
69
|
-
next.setDate(next.getDate() + 1);
|
|
70
|
-
}
|
|
71
|
-
if (nextHour !== currentHour) {
|
|
72
|
-
next.setMinutes(minute === '*' ? 0 : parseInt(minute, 10));
|
|
73
|
-
}
|
|
74
|
-
next.setHours(nextHour);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// For complex cron expressions, fall back to 1 hour from now
|
|
78
|
-
// Full implementation would need cron-parser
|
|
79
|
-
if (next <= fromDate) {
|
|
80
|
-
next.setTime(fromDate.getTime() + 60 * 60 * 1000);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return next;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Calculate next run time for a schedule
|
|
88
|
-
*/
|
|
89
|
-
export function calculateNextRun(schedule) {
|
|
90
|
-
if (schedule.interval) {
|
|
91
|
-
return new Date(Date.now() + schedule.interval);
|
|
92
|
-
}
|
|
93
|
-
if (schedule.cron) {
|
|
94
|
-
return getNextCronTime(schedule.cron);
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Log to scheduler log file
|
|
101
|
-
*/
|
|
102
|
-
function log(msg) {
|
|
103
|
-
const timestamp = new Date().toISOString();
|
|
104
|
-
const line = `[${timestamp}] ${msg}\n`;
|
|
105
|
-
appendFileSync(SCHEDULER_LOG, line);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Check and run due schedules
|
|
110
|
-
*/
|
|
111
|
-
async function checkSchedules() {
|
|
112
|
-
const schedules = loadSchedules();
|
|
113
|
-
const now = new Date();
|
|
114
|
-
|
|
115
|
-
for (const schedule of Object.values(schedules)) {
|
|
116
|
-
if (!schedule.enabled) continue;
|
|
117
|
-
|
|
118
|
-
const nextRun = new Date(schedule.nextRunAt);
|
|
119
|
-
if (nextRun > now) continue;
|
|
120
|
-
|
|
121
|
-
// Schedule is due - spawn task
|
|
122
|
-
log(`Running scheduled task: ${schedule.id} - "${schedule.prompt.slice(0, 50)}..."`);
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const task = await spawnTask(schedule.prompt, {
|
|
126
|
-
cwd: schedule.cwd,
|
|
127
|
-
scheduleId: schedule.id,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Update schedule with next run time
|
|
131
|
-
const nextRunAt = calculateNextRun(schedule);
|
|
132
|
-
updateSchedule(schedule.id, {
|
|
133
|
-
lastRunAt: now.toISOString(),
|
|
134
|
-
lastTaskId: task.id,
|
|
135
|
-
nextRunAt: nextRunAt ? nextRunAt.toISOString() : null,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
log(
|
|
139
|
-
`Spawned task ${task.id} for schedule ${schedule.id}, next run: ${nextRunAt?.toISOString() || 'none'}`
|
|
140
|
-
);
|
|
141
|
-
} catch (err) {
|
|
142
|
-
log(`Error spawning task for schedule ${schedule.id}: ${err.message}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Start the scheduler daemon
|
|
149
|
-
*/
|
|
150
|
-
export function startDaemon() {
|
|
151
|
-
// Check if already running
|
|
152
|
-
if (existsSync(SCHEDULER_PID_FILE)) {
|
|
153
|
-
const existingPid = parseInt(readFileSync(SCHEDULER_PID_FILE, 'utf-8').trim(), 10);
|
|
154
|
-
try {
|
|
155
|
-
process.kill(existingPid, 0);
|
|
156
|
-
console.log(`Scheduler already running (PID: ${existingPid})`);
|
|
157
|
-
return false;
|
|
158
|
-
} catch {
|
|
159
|
-
// Process not running, clean up stale PID file
|
|
160
|
-
unlinkSync(SCHEDULER_PID_FILE);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Write PID file
|
|
165
|
-
writeFileSync(SCHEDULER_PID_FILE, String(process.pid));
|
|
166
|
-
|
|
167
|
-
log(`Scheduler daemon started (PID: ${process.pid})`);
|
|
168
|
-
console.log(`Scheduler daemon started (PID: ${process.pid})`);
|
|
169
|
-
|
|
170
|
-
// Run check loop
|
|
171
|
-
const runLoop = async () => {
|
|
172
|
-
while (true) {
|
|
173
|
-
try {
|
|
174
|
-
await checkSchedules();
|
|
175
|
-
} catch (err) {
|
|
176
|
-
// Log error with full stack trace - scheduler errors are critical bugs
|
|
177
|
-
const errorMsg = `SCHEDULER ERROR: ${err.message}\nStack: ${err.stack}`;
|
|
178
|
-
log(errorMsg);
|
|
179
|
-
console.error(errorMsg);
|
|
180
|
-
}
|
|
181
|
-
await new Promise((r) => setTimeout(r, CHECK_INTERVAL));
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
// Handle shutdown
|
|
186
|
-
process.on('SIGTERM', () => {
|
|
187
|
-
log('Scheduler daemon stopping (SIGTERM)');
|
|
188
|
-
if (existsSync(SCHEDULER_PID_FILE)) {
|
|
189
|
-
unlinkSync(SCHEDULER_PID_FILE);
|
|
190
|
-
}
|
|
191
|
-
process.exit(0);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
process.on('SIGINT', () => {
|
|
195
|
-
log('Scheduler daemon stopping (SIGINT)');
|
|
196
|
-
if (existsSync(SCHEDULER_PID_FILE)) {
|
|
197
|
-
unlinkSync(SCHEDULER_PID_FILE);
|
|
198
|
-
}
|
|
199
|
-
process.exit(0);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
runLoop();
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Stop the scheduler daemon
|
|
208
|
-
*/
|
|
209
|
-
export function stopDaemon() {
|
|
210
|
-
if (!existsSync(SCHEDULER_PID_FILE)) {
|
|
211
|
-
console.log('Scheduler is not running');
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const pid = parseInt(readFileSync(SCHEDULER_PID_FILE, 'utf-8').trim(), 10);
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
process.kill(pid, 'SIGTERM');
|
|
219
|
-
unlinkSync(SCHEDULER_PID_FILE);
|
|
220
|
-
console.log(`Scheduler stopped (PID: ${pid})`);
|
|
221
|
-
log(`Scheduler daemon stopped by user (PID: ${pid})`);
|
|
222
|
-
return true;
|
|
223
|
-
} catch {
|
|
224
|
-
// Process not running, clean up
|
|
225
|
-
unlinkSync(SCHEDULER_PID_FILE);
|
|
226
|
-
console.log('Scheduler was not running (cleaned up stale PID file)');
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Get daemon status
|
|
233
|
-
*/
|
|
234
|
-
export function getDaemonStatus() {
|
|
235
|
-
if (!existsSync(SCHEDULER_PID_FILE)) {
|
|
236
|
-
return { running: false, pid: null };
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const pid = parseInt(readFileSync(SCHEDULER_PID_FILE, 'utf-8').trim(), 10);
|
|
240
|
-
|
|
241
|
-
try {
|
|
242
|
-
process.kill(pid, 0);
|
|
243
|
-
return { running: true, pid };
|
|
244
|
-
} catch {
|
|
245
|
-
return { running: false, pid: null, stale: true };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// If run directly, start daemon
|
|
250
|
-
if (process.argv[1]?.endsWith('scheduler.js')) {
|
|
251
|
-
startDaemon();
|
|
252
|
-
}
|