glad-web 1.0.46 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -61
- package/README.zh-CN.md +0 -198
- package/assets/logo.svg +0 -43
- package/bin/cli.js +0 -65
- package/lib/ai-tools/demo/enhanced-demo.js +0 -625
- package/lib/ai-tools/demo/index.js +0 -24
- package/lib/ai-tools/demo/responses.js +0 -88
- package/lib/ai-tools/detector.js +0 -76
- package/lib/ai-tools/registry.js +0 -300
- package/lib/claude/cli-usage.js +0 -95
- package/lib/claude/config.js +0 -82
- package/lib/claude/structured-session.js +0 -884
- package/lib/claude/transcript-repository.js +0 -216
- package/lib/codex/image-store.js +0 -174
- package/lib/codex/structured-session.js +0 -1590
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -605
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -108
- package/lib/git/service.js +0 -83
- package/lib/notifications/message-formatter.js +0 -94
- package/lib/notifications/notification-service.js +0 -143
- package/lib/notifications/serverchan-client.js +0 -58
- package/lib/notifications/serverchan-settings-store.js +0 -115
- package/lib/schedule/job-runner.js +0 -162
- package/lib/schedule/job-store.js +0 -167
- package/lib/schedule/key-sequences.js +0 -49
- package/lib/schedule/scheduler-service.js +0 -39
- package/lib/server/routes/notifications.js +0 -52
- package/lib/server/routes/providers.js +0 -114
- package/lib/server/routes/schedules.js +0 -54
- package/lib/server/routes/skillhub.js +0 -104
- package/lib/server/routes/usage.js +0 -23
- package/lib/server/routes/workspace.js +0 -77
- package/lib/session/buffer.js +0 -102
- package/lib/session/file-attachment-store.js +0 -168
- package/lib/session/pty-manager.js +0 -255
- package/lib/session/rendered-history.js +0 -225
- package/lib/session/session-manager.js +0 -1032
- package/lib/session/text-history.js +0 -274
- package/lib/skillhub/client.js +0 -121
- package/lib/skillhub/settings-store.js +0 -168
- package/lib/skillhub/skill-installer.js +0 -320
- package/lib/usage/ccusage-runner.js +0 -128
- package/lib/usage/source-catalog.js +0 -26
- package/lib/usage/usage-service.js +0 -226
- package/lib/utils/logger.js +0 -74
- package/lib/utils/pid.js +0 -67
- package/lib/utils/validation.js +0 -53
- package/lib/web/bootstrap.js +0 -34
- package/lib/web/claude.js +0 -1150
- package/lib/web/codex.js +0 -1045
- package/lib/web/composer.js +0 -493
- package/lib/web/core.js +0 -385
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -547
- package/lib/web/layout.js +0 -69
- package/lib/web/notifications.js +0 -164
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -361
- package/lib/web/shell.js +0 -74
- package/lib/web/skillhub.js +0 -197
- package/lib/web/styles.css +0 -932
- package/lib/web/terminal-scroll.js +0 -81
- package/lib/web/theme.js +0 -60
- package/lib/web/timed-inputs.js +0 -216
- package/lib/web/usage.js +0 -323
- package/lib/workspace/service.js +0 -77
- package/scripts/check-syntax.js +0 -26
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
const { sequenceForKey } = require('./key-sequences');
|
|
2
|
-
|
|
3
|
-
function sleep(ms) {
|
|
4
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
class SessionEndedError extends Error {
|
|
8
|
-
constructor() {
|
|
9
|
-
super('Session ended');
|
|
10
|
-
this.code = 'SESSION_ENDED';
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
class JobRunner {
|
|
15
|
-
constructor({ createSession, getJob, updateJob, logger }) {
|
|
16
|
-
this.createSession = createSession;
|
|
17
|
-
this.getJob = getJob;
|
|
18
|
-
this.updateJob = updateJob;
|
|
19
|
-
this.logger = logger;
|
|
20
|
-
this.running = new Set();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async run(jobId, options = {}) {
|
|
24
|
-
const job = this.getJob(jobId);
|
|
25
|
-
if (!job) throw new Error('Scheduled task not found');
|
|
26
|
-
if (this.running.has(job.id)) {
|
|
27
|
-
this.updateJob(job.id, {
|
|
28
|
-
lastRunAt: Date.now(),
|
|
29
|
-
lastRunStatus: 'skipped',
|
|
30
|
-
lastRunMessage: 'Previous run is still active'
|
|
31
|
-
});
|
|
32
|
-
return { skipped: true, reason: 'Previous run is still active' };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
this.running.add(job.id);
|
|
36
|
-
this.updateJob(job.id, {
|
|
37
|
-
running: true,
|
|
38
|
-
lastRunAt: Date.now(),
|
|
39
|
-
lastRunStatus: options.manual ? 'manual-running' : 'running',
|
|
40
|
-
lastRunMessage: ''
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
let session = null;
|
|
44
|
-
try {
|
|
45
|
-
session = this.createSession({
|
|
46
|
-
toolKey: job.target.toolKey,
|
|
47
|
-
workingDirectory: job.target.workingDirectory,
|
|
48
|
-
name: `${job.name}${options.manual ? ' (Test)' : ''}`
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
this.updateJob(job.id, {
|
|
52
|
-
lastSessionId: session.id,
|
|
53
|
-
lastRunMessage: `Started session ${session.id}`
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const execute = async () => {
|
|
57
|
-
try {
|
|
58
|
-
await sleep(1000);
|
|
59
|
-
await this.executeSteps(job, session);
|
|
60
|
-
this.updateJob(job.id, {
|
|
61
|
-
running: false,
|
|
62
|
-
lastRunStatus: options.manual ? 'manual-success' : 'success',
|
|
63
|
-
lastRunMessage: `Started session ${session.id}`,
|
|
64
|
-
lastSessionId: session.id
|
|
65
|
-
});
|
|
66
|
-
} catch (error) {
|
|
67
|
-
const ended = error && error.code === 'SESSION_ENDED';
|
|
68
|
-
if (!ended) this.logger?.error?.(`Scheduled task failed: ${error.message}`);
|
|
69
|
-
this.updateJob(job.id, {
|
|
70
|
-
running: false,
|
|
71
|
-
lastRunStatus: ended ? 'cancelled' : 'failed',
|
|
72
|
-
lastRunMessage: ended ? 'Session ended' : error.message,
|
|
73
|
-
lastSessionId: session?.id || null
|
|
74
|
-
});
|
|
75
|
-
if (!options.background) throw error;
|
|
76
|
-
} finally {
|
|
77
|
-
this.running.delete(job.id);
|
|
78
|
-
this.updateJob(job.id, { running: false });
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
if (options.background) {
|
|
83
|
-
execute();
|
|
84
|
-
return { success: true, sessionId: session.id };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
await execute();
|
|
88
|
-
return { success: true, sessionId: session.id };
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if (!session) {
|
|
91
|
-
this.running.delete(job.id);
|
|
92
|
-
this.updateJob(job.id, { running: false });
|
|
93
|
-
}
|
|
94
|
-
this.logger?.error?.(`Scheduled task failed: ${error.message}`);
|
|
95
|
-
this.updateJob(job.id, {
|
|
96
|
-
running: false,
|
|
97
|
-
lastRunStatus: 'failed',
|
|
98
|
-
lastRunMessage: error.message,
|
|
99
|
-
lastSessionId: session?.id || null
|
|
100
|
-
});
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async executeSteps(job, session) {
|
|
106
|
-
const modifiers = { ctrl: false, alt: false };
|
|
107
|
-
|
|
108
|
-
for (const step of job.steps || []) {
|
|
109
|
-
this.assertSessionRunning(session);
|
|
110
|
-
if (step.type === 'sleep') {
|
|
111
|
-
await this.sleepWhileRunning(session, Math.max(0, Number(step.seconds) || 0) * 1000);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (step.type === 'sendText') {
|
|
116
|
-
if (!session.write(String(step.text || ''))) throw new SessionEndedError();
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (step.type === 'sendKey') {
|
|
121
|
-
if (!session.write(sequenceForKey(step.key, modifiers))) throw new SessionEndedError();
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (step.type === 'keyDown') {
|
|
126
|
-
const key = String(step.key || '').toLowerCase();
|
|
127
|
-
if (key === 'ctrl' || key === 'alt') modifiers[key] = true;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (step.type === 'keyUp') {
|
|
132
|
-
const key = String(step.key || '').toLowerCase();
|
|
133
|
-
if (key === 'ctrl' || key === 'alt') modifiers[key] = false;
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (step.type === 'stop') break;
|
|
138
|
-
|
|
139
|
-
if (step.type === 'closeSession') {
|
|
140
|
-
session.kill?.();
|
|
141
|
-
break;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
assertSessionRunning(session) {
|
|
147
|
-
if (session && typeof session.isRunning === 'function' && !session.isRunning()) {
|
|
148
|
-
throw new SessionEndedError();
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async sleepWhileRunning(session, ms) {
|
|
153
|
-
const end = Date.now() + ms;
|
|
154
|
-
while (Date.now() < end) {
|
|
155
|
-
this.assertSessionRunning(session);
|
|
156
|
-
await sleep(Math.min(500, end - Date.now()));
|
|
157
|
-
}
|
|
158
|
-
this.assertSessionRunning(session);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
module.exports = JobRunner;
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
const Conf = require('conf');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
const { v4: uuidv4 } = require('uuid');
|
|
5
|
-
|
|
6
|
-
const config = new Conf({
|
|
7
|
-
projectName: 'glad',
|
|
8
|
-
cwd: path.join(os.homedir(), '.glad'),
|
|
9
|
-
configName: 'schedules'
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
function normalizeWeekdays(weekdays) {
|
|
13
|
-
const values = Array.isArray(weekdays) ? weekdays : [];
|
|
14
|
-
return Array.from(new Set(values.map(Number).filter(value => value >= 0 && value <= 6))).sort((a, b) => a - b);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function normalizeSteps(steps) {
|
|
18
|
-
const values = Array.isArray(steps) ? steps : [];
|
|
19
|
-
return values.map(step => {
|
|
20
|
-
const type = String(step.type || '').trim();
|
|
21
|
-
if (type === 'sleep') return { type, seconds: Math.max(0, Number(step.seconds) || 0) };
|
|
22
|
-
if (type === 'sendText') return { type, text: String(step.text || '') };
|
|
23
|
-
if (type === 'sendKey') return { type, key: String(step.key || 'enter') };
|
|
24
|
-
if (type === 'keyDown' || type === 'keyUp') return { type, key: String(step.key || '') };
|
|
25
|
-
if (type === 'stop' || type === 'closeSession') return { type };
|
|
26
|
-
return null;
|
|
27
|
-
}).filter(Boolean);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function computeNextRunAt(schedule, from = Date.now()) {
|
|
31
|
-
const weekdays = normalizeWeekdays(schedule && schedule.weekdays);
|
|
32
|
-
const time = String(schedule && schedule.time || '').trim();
|
|
33
|
-
const match = /^([01]\d|2[0-3]):([0-5]\d)$/.exec(time);
|
|
34
|
-
if (!match || weekdays.length === 0) return null;
|
|
35
|
-
|
|
36
|
-
const hour = Number(match[1]);
|
|
37
|
-
const minute = Number(match[2]);
|
|
38
|
-
const start = new Date(from);
|
|
39
|
-
|
|
40
|
-
for (let offset = 0; offset <= 7; offset++) {
|
|
41
|
-
const candidate = new Date(start);
|
|
42
|
-
candidate.setDate(start.getDate() + offset);
|
|
43
|
-
candidate.setHours(hour, minute, 0, 0);
|
|
44
|
-
if (candidate.getTime() <= from) continue;
|
|
45
|
-
if (weekdays.includes(candidate.getDay())) return candidate.getTime();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function normalizeJob(input, existing = null, options = {}) {
|
|
52
|
-
const now = Date.now();
|
|
53
|
-
const schedule = input.schedule || {};
|
|
54
|
-
const target = input.target || {};
|
|
55
|
-
const hasStoredNextRunAt = existing && typeof existing.nextRunAt === 'number';
|
|
56
|
-
const job = {
|
|
57
|
-
id: existing?.id || input.id || uuidv4(),
|
|
58
|
-
name: String(input.name || existing?.name || 'Scheduled Task').trim() || 'Scheduled Task',
|
|
59
|
-
enabled: Boolean(input.enabled ?? existing?.enabled ?? true),
|
|
60
|
-
schedule: {
|
|
61
|
-
time: String(schedule.time || existing?.schedule?.time || '09:00'),
|
|
62
|
-
weekdays: normalizeWeekdays(schedule.weekdays ?? existing?.schedule?.weekdays ?? [1, 2, 3, 4, 5])
|
|
63
|
-
},
|
|
64
|
-
target: {
|
|
65
|
-
toolKey: String(target.toolKey || existing?.target?.toolKey || 'demo'),
|
|
66
|
-
workingDirectory: String(target.workingDirectory ?? existing?.target?.workingDirectory ?? '')
|
|
67
|
-
},
|
|
68
|
-
steps: normalizeSteps(input.steps ?? existing?.steps ?? []),
|
|
69
|
-
createdAt: existing?.createdAt || input.createdAt || now,
|
|
70
|
-
updatedAt: options.touch ? now : (existing?.updatedAt || input.updatedAt || now),
|
|
71
|
-
lastRunAt: existing?.lastRunAt || input.lastRunAt || null,
|
|
72
|
-
lastRunStatus: existing?.lastRunStatus || input.lastRunStatus || 'idle',
|
|
73
|
-
lastRunMessage: existing?.lastRunMessage || input.lastRunMessage || '',
|
|
74
|
-
lastSessionId: existing?.lastSessionId || input.lastSessionId || null,
|
|
75
|
-
running: Boolean(existing?.running || false)
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
if (options.recomputeNextRunAt || !hasStoredNextRunAt) {
|
|
79
|
-
job.nextRunAt = job.enabled ? computeNextRunAt(job.schedule, now) : null;
|
|
80
|
-
} else {
|
|
81
|
-
job.nextRunAt = existing.nextRunAt;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return job;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class JobStore {
|
|
88
|
-
list() {
|
|
89
|
-
return config.get('jobs', []).map(job => normalizeJob(job, job));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
saveAll(jobs) {
|
|
93
|
-
config.set('jobs', jobs);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
get(id) {
|
|
97
|
-
return this.list().find(job => job.id === id) || null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
create(input) {
|
|
101
|
-
const jobs = this.list();
|
|
102
|
-
const job = normalizeJob(input, null, { touch: true });
|
|
103
|
-
jobs.push(job);
|
|
104
|
-
this.saveAll(jobs);
|
|
105
|
-
return job;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
update(id, input) {
|
|
109
|
-
const jobs = this.list();
|
|
110
|
-
const index = jobs.findIndex(job => job.id === id);
|
|
111
|
-
if (index === -1) return null;
|
|
112
|
-
const existing = jobs[index];
|
|
113
|
-
const nextSchedule = {
|
|
114
|
-
time: String(input.schedule?.time || existing.schedule?.time || '09:00'),
|
|
115
|
-
weekdays: normalizeWeekdays(input.schedule?.weekdays ?? existing.schedule?.weekdays ?? [1, 2, 3, 4, 5])
|
|
116
|
-
};
|
|
117
|
-
const scheduleChanged = JSON.stringify(nextSchedule) !== JSON.stringify(existing.schedule);
|
|
118
|
-
const enabledChanged = Boolean(input.enabled ?? existing.enabled ?? true) !== Boolean(existing.enabled);
|
|
119
|
-
jobs[index] = normalizeJob(input, existing, {
|
|
120
|
-
touch: true,
|
|
121
|
-
recomputeNextRunAt: scheduleChanged || enabledChanged
|
|
122
|
-
});
|
|
123
|
-
this.saveAll(jobs);
|
|
124
|
-
return jobs[index];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
patchRuntime(id, patch) {
|
|
128
|
-
const jobs = this.list();
|
|
129
|
-
const index = jobs.findIndex(job => job.id === id);
|
|
130
|
-
if (index === -1) return null;
|
|
131
|
-
jobs[index] = { ...jobs[index], ...patch, updatedAt: Date.now() };
|
|
132
|
-
if ('enabled' in patch || 'lastRunAt' in patch || 'schedule' in patch) {
|
|
133
|
-
jobs[index].nextRunAt = jobs[index].enabled ? computeNextRunAt(jobs[index].schedule) : null;
|
|
134
|
-
}
|
|
135
|
-
this.saveAll(jobs);
|
|
136
|
-
return jobs[index];
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
delete(id) {
|
|
140
|
-
const jobs = this.list();
|
|
141
|
-
const next = jobs.filter(job => job.id !== id);
|
|
142
|
-
this.saveAll(next);
|
|
143
|
-
return next.length !== jobs.length;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
duplicate(id) {
|
|
147
|
-
const source = this.get(id);
|
|
148
|
-
if (!source) return null;
|
|
149
|
-
return this.create({
|
|
150
|
-
...source,
|
|
151
|
-
id: uuidv4(),
|
|
152
|
-
name: `${source.name} Copy`,
|
|
153
|
-
enabled: false,
|
|
154
|
-
lastRunAt: null,
|
|
155
|
-
lastRunStatus: 'idle',
|
|
156
|
-
lastRunMessage: '',
|
|
157
|
-
lastSessionId: null,
|
|
158
|
-
running: false
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
module.exports = {
|
|
164
|
-
JobStore,
|
|
165
|
-
computeNextRunAt,
|
|
166
|
-
normalizeJob
|
|
167
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const KEY_SEQUENCES = {
|
|
2
|
-
enter: '\r',
|
|
3
|
-
return: '\r',
|
|
4
|
-
tab: '\t',
|
|
5
|
-
esc: '\x1b',
|
|
6
|
-
escape: '\x1b',
|
|
7
|
-
up: '\x1b[A',
|
|
8
|
-
down: '\x1b[B',
|
|
9
|
-
right: '\x1b[C',
|
|
10
|
-
left: '\x1b[D',
|
|
11
|
-
backspace: '\x7f',
|
|
12
|
-
delete: '\x1b[3~',
|
|
13
|
-
home: '\x1b[H',
|
|
14
|
-
end: '\x1b[F'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
function ctrlSequence(key) {
|
|
18
|
-
const normalized = String(key || '').trim().toLowerCase();
|
|
19
|
-
if (normalized.length !== 1) return null;
|
|
20
|
-
const code = normalized.charCodeAt(0);
|
|
21
|
-
if (code >= 97 && code <= 122) return String.fromCharCode(code - 96);
|
|
22
|
-
if (normalized === '[') return '\x1b';
|
|
23
|
-
if (normalized === ']') return '\x1d';
|
|
24
|
-
if (normalized === '\\') return '\x1c';
|
|
25
|
-
if (normalized === '^') return '\x1e';
|
|
26
|
-
if (normalized === '_') return '\x1f';
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function sequenceForKey(key, modifiers = {}) {
|
|
31
|
-
const normalized = String(key || '').trim().toLowerCase();
|
|
32
|
-
if (!normalized) return '';
|
|
33
|
-
|
|
34
|
-
if (normalized.startsWith('ctrl+')) {
|
|
35
|
-
return ctrlSequence(normalized.slice(5)) || '';
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (modifiers.ctrl) {
|
|
39
|
-
const sequence = ctrlSequence(normalized);
|
|
40
|
-
if (sequence) return sequence;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const sequence = KEY_SEQUENCES[normalized] || normalized;
|
|
44
|
-
return modifiers.alt ? '\x1b' + sequence : sequence;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = {
|
|
48
|
-
sequenceForKey
|
|
49
|
-
};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
class SchedulerService {
|
|
2
|
-
constructor({ jobStore, jobRunner, logger, intervalMs = 30000 }) {
|
|
3
|
-
this.jobStore = jobStore;
|
|
4
|
-
this.jobRunner = jobRunner;
|
|
5
|
-
this.logger = logger || console;
|
|
6
|
-
this.intervalMs = intervalMs;
|
|
7
|
-
this.timer = null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
start() {
|
|
11
|
-
if (this.timer) return;
|
|
12
|
-
this.timer = setInterval(() => {
|
|
13
|
-
this.tick();
|
|
14
|
-
}, this.intervalMs);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
stop() {
|
|
18
|
-
if (!this.timer) return;
|
|
19
|
-
clearInterval(this.timer);
|
|
20
|
-
this.timer = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
tick(now = Date.now()) {
|
|
24
|
-
for (const job of this.jobStore.list()) {
|
|
25
|
-
if (!job.enabled || !job.nextRunAt || job.nextRunAt > now) continue;
|
|
26
|
-
this.jobStore.patchRuntime(job.id, {
|
|
27
|
-
nextRunAt: null,
|
|
28
|
-
lastRunAt: now,
|
|
29
|
-
lastRunStatus: 'queued',
|
|
30
|
-
lastRunMessage: ''
|
|
31
|
-
});
|
|
32
|
-
this.jobRunner.run(job.id, { manual: false }).catch(error => {
|
|
33
|
-
this.logger.error(`Scheduled task ${job.id} failed: ${error.message}`);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
module.exports = SchedulerService;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
function statusCode(error) {
|
|
2
|
-
return Number(error?.statusCode) || 500;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
function registerNotificationRoutes(app, {
|
|
6
|
-
settingsStore,
|
|
7
|
-
notificationService
|
|
8
|
-
}) {
|
|
9
|
-
app.get('/api/notifications/serverchan', (_req, res) => {
|
|
10
|
-
res.json(settingsStore.getPublic());
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
app.put('/api/notifications/serverchan', (req, res) => {
|
|
14
|
-
try {
|
|
15
|
-
res.json({ success: true, settings: settingsStore.save(req.body || {}) });
|
|
16
|
-
} catch (error) {
|
|
17
|
-
res.status(statusCode(error)).json({ error: error.message });
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
app.delete('/api/notifications/serverchan', (_req, res) => {
|
|
22
|
-
const settings = settingsStore.clear();
|
|
23
|
-
notificationService.disableAllSessions();
|
|
24
|
-
res.json({ success: true, settings });
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
app.post('/api/notifications/serverchan/test', async (req, res) => {
|
|
28
|
-
try {
|
|
29
|
-
await notificationService.sendTest(req.body || {});
|
|
30
|
-
res.json({ success: true });
|
|
31
|
-
} catch (error) {
|
|
32
|
-
res.status(statusCode(error)).json({ error: error.message });
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
app.put('/api/sessions/:id/notifications/serverchan', async (req, res) => {
|
|
37
|
-
try {
|
|
38
|
-
const state = await notificationService.setSessionEnabled(
|
|
39
|
-
req.params.id,
|
|
40
|
-
Boolean(req.body?.enabled)
|
|
41
|
-
);
|
|
42
|
-
res.json({ success: true, state });
|
|
43
|
-
} catch (error) {
|
|
44
|
-
res.status(statusCode(error)).json({
|
|
45
|
-
error: error.message,
|
|
46
|
-
...(error.code ? { code: error.code } : {})
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
module.exports = registerNotificationRoutes;
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
function registerProviderRoutes(app, { sessionManager }) {
|
|
2
|
-
app.get('/api/sessions/:id/claude-resume-sessions', (req, res) => {
|
|
3
|
-
const items = sessionManager.listClaudeResumeSessions(req.params.id);
|
|
4
|
-
if (!items) return res.status(404).json({ error: 'Claude session not found' });
|
|
5
|
-
res.json({ success: true, items });
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
app.patch('/api/sessions/:id/claude-settings', (req, res) => {
|
|
9
|
-
const state = sessionManager.updateClaudeSettings(req.params.id, req.body || {});
|
|
10
|
-
if (!state) return res.status(404).json({ error: 'Claude session not found' });
|
|
11
|
-
res.json({ success: true, state });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
app.post('/api/sessions/:id/claude-abort', (req, res) => {
|
|
15
|
-
const success = sessionManager.abortClaude(req.params.id);
|
|
16
|
-
if (!success) return res.status(404).json({ error: 'Claude session not found or idle' });
|
|
17
|
-
res.json({ success: true });
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
app.post('/api/sessions/:id/claude-resume', (req, res) => {
|
|
21
|
-
const resumeSessionId = req.body?.resumeSessionId;
|
|
22
|
-
if (!resumeSessionId) return res.status(400).json({ error: 'Missing resumeSessionId' });
|
|
23
|
-
const success = sessionManager.resumeClaude(req.params.id, resumeSessionId);
|
|
24
|
-
if (!success) return res.status(404).json({ error: 'Claude session not found' });
|
|
25
|
-
res.json({ success: true });
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
app.post('/api/sessions/:id/claude-fork', async (req, res) => {
|
|
29
|
-
try {
|
|
30
|
-
const result = await sessionManager.forkClaude(req.params.id, req.body?.claudeSessionId);
|
|
31
|
-
if (!result) return res.status(404).json({ error: 'Claude session not found' });
|
|
32
|
-
res.json({ success: true, ...result });
|
|
33
|
-
} catch (error) {
|
|
34
|
-
res.status(error.statusCode || 400).json({ error: error.message });
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
app.patch('/api/sessions/:id/codex-settings', async (req, res) => {
|
|
39
|
-
try {
|
|
40
|
-
const state = await sessionManager.updateCodexSettings(req.params.id, req.body || {});
|
|
41
|
-
if (!state) return res.status(404).json({ error: 'Codex session not found' });
|
|
42
|
-
res.json({ success: true, state });
|
|
43
|
-
} catch (error) {
|
|
44
|
-
res.status(400).json({ error: error.message });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
app.get('/api/sessions/:id/codex-resume-threads', async (req, res) => {
|
|
49
|
-
try {
|
|
50
|
-
const items = await sessionManager.listCodexResumeThreads(req.params.id);
|
|
51
|
-
if (!items) return res.status(404).json({ error: 'Codex session not found' });
|
|
52
|
-
res.json({ success: true, items });
|
|
53
|
-
} catch (error) {
|
|
54
|
-
res.status(400).json({ error: error.message });
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
app.get('/api/sessions/:id/codex-prompts', async (req, res) => {
|
|
59
|
-
try {
|
|
60
|
-
const result = await sessionManager.listCodexPrompts(req.params.id, {
|
|
61
|
-
offset: req.query?.offset,
|
|
62
|
-
limit: req.query?.limit
|
|
63
|
-
});
|
|
64
|
-
if (!result) return res.status(404).json({ error: 'Codex session not found' });
|
|
65
|
-
res.json({ success: true, ...result });
|
|
66
|
-
} catch (error) {
|
|
67
|
-
res.status(400).json({ error: error.message });
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
app.get('/api/sessions/:id/codex-skills', async (req, res) => {
|
|
72
|
-
try {
|
|
73
|
-
const result = await sessionManager.listCodexSkills(req.params.id, req.query?.forceReload === 'true');
|
|
74
|
-
if (!result) return res.status(404).json({ error: 'Codex session not found' });
|
|
75
|
-
res.json({ success: true, ...result });
|
|
76
|
-
} catch (error) {
|
|
77
|
-
res.status(400).json({ error: error.message });
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
app.post('/api/sessions/:id/codex-abort', (req, res) => {
|
|
82
|
-
const success = sessionManager.abortCodex(req.params.id);
|
|
83
|
-
if (!success) return res.status(409).json({ error: 'Codex session is idle or unavailable' });
|
|
84
|
-
res.json({ success: true });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
app.post('/api/sessions/:id/codex-resume', async (req, res) => {
|
|
88
|
-
try {
|
|
89
|
-
const success = await sessionManager.resumeCodex(req.params.id, req.body?.threadId);
|
|
90
|
-
if (!success) return res.status(409).json({ error: 'Codex session is busy or no thread is available' });
|
|
91
|
-
res.json({ success: true });
|
|
92
|
-
} catch (error) {
|
|
93
|
-
res.status(400).json({ error: error.message });
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
app.post('/api/sessions/:id/codex-fork', async (req, res) => {
|
|
98
|
-
try {
|
|
99
|
-
const session = await sessionManager.forkCodex(req.params.id, req.body?.threadId);
|
|
100
|
-
if (!session) return res.status(404).json({ error: 'Codex session not found' });
|
|
101
|
-
res.json({ success: true, id: session.id, name: session.name, threadId: session.threadId });
|
|
102
|
-
} catch (error) {
|
|
103
|
-
res.status(error.statusCode || 400).json({ error: error.message });
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
app.post('/api/debug/client-log', (req, res) => {
|
|
108
|
-
const { sessionId, event, payload } = req.body || {};
|
|
109
|
-
sessionManager.logClientDebug(sessionId, event, payload);
|
|
110
|
-
res.json({ success: true });
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
module.exports = registerProviderRoutes;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
function registerScheduleRoutes(app, { jobStore, jobRunner }) {
|
|
2
|
-
app.get('/api/schedules', (req, res) => {
|
|
3
|
-
res.json(jobStore.list());
|
|
4
|
-
});
|
|
5
|
-
|
|
6
|
-
app.post('/api/schedules', (req, res) => {
|
|
7
|
-
try {
|
|
8
|
-
res.json(jobStore.create(req.body || {}));
|
|
9
|
-
} catch (error) {
|
|
10
|
-
res.status(400).json({ error: error.message });
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
app.get('/api/schedules/:id', (req, res) => {
|
|
15
|
-
const job = jobStore.get(req.params.id);
|
|
16
|
-
if (!job) return res.status(404).json({ error: 'Scheduled task not found' });
|
|
17
|
-
res.json(job);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
app.patch('/api/schedules/:id', (req, res) => {
|
|
21
|
-
const job = jobStore.update(req.params.id, req.body || {});
|
|
22
|
-
if (!job) return res.status(404).json({ error: 'Scheduled task not found' });
|
|
23
|
-
res.json(job);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
app.patch('/api/schedules/:id/enabled', (req, res) => {
|
|
27
|
-
const job = jobStore.patchRuntime(req.params.id, { enabled: Boolean(req.body?.enabled) });
|
|
28
|
-
if (!job) return res.status(404).json({ error: 'Scheduled task not found' });
|
|
29
|
-
res.json(job);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
app.delete('/api/schedules/:id', (req, res) => {
|
|
33
|
-
res.json({ success: jobStore.delete(req.params.id) });
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
app.post('/api/schedules/:id/duplicate', (req, res) => {
|
|
37
|
-
const job = jobStore.duplicate(req.params.id);
|
|
38
|
-
if (!job) return res.status(404).json({ error: 'Scheduled task not found' });
|
|
39
|
-
res.json(job);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const runJob = options => async (req, res) => {
|
|
43
|
-
try {
|
|
44
|
-
res.json(await jobRunner.run(req.params.id, options));
|
|
45
|
-
} catch (error) {
|
|
46
|
-
res.status(400).json({ error: error.message });
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
app.post('/api/schedules/:id/run', runJob({ manual: false }));
|
|
51
|
-
app.post('/api/schedules/:id/simulate', runJob({ manual: true, background: true }));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = registerScheduleRoutes;
|