glad-web 1.0.45 → 2.0.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/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -58
- 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 -1578
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -586
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -89
- 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/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 -1001
- package/lib/session/text-history.js +0 -274
- 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/claude.js +0 -1129
- package/lib/web/codex.js +0 -1042
- package/lib/web/composer.js +0 -463
- package/lib/web/core.js +0 -373
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -516
- package/lib/web/layout.js +0 -72
- package/lib/web/notifications.js +0 -163
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -360
- package/lib/web/shell.js +0 -59
- package/lib/web/styles.css +0 -905
- 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,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;
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module.exports = function registerUsageRoutes(app, { usageService, sendJson = (_req, res, payload) => res.json(payload) }) {
|
|
2
|
-
app.get('/api/usage/sources', async (req, res) => {
|
|
3
|
-
try {
|
|
4
|
-
res.json(await usageService.listSources(req.query.refresh === '1'));
|
|
5
|
-
} catch (error) {
|
|
6
|
-
res.status(error.statusCode || 500).json({ error: error.message || 'Failed to load usage sources' });
|
|
7
|
-
}
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
app.get('/api/usage/report', async (req, res) => {
|
|
11
|
-
try {
|
|
12
|
-
const report = await usageService.getDashboard(
|
|
13
|
-
req.query.source,
|
|
14
|
-
req.query.scope || 'weekly',
|
|
15
|
-
req.query.period,
|
|
16
|
-
req.query.refresh === '1'
|
|
17
|
-
);
|
|
18
|
-
sendJson(req, res, report);
|
|
19
|
-
} catch (error) {
|
|
20
|
-
res.status(error.statusCode || 500).json({ error: error.message || 'Failed to load usage report' });
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
};
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
function registerWorkspaceRoutes(app, { sessionManager, gitService, workspaceService, getWorkingDirectory }) {
|
|
2
|
-
function getSession(req, res) {
|
|
3
|
-
const session = sessionManager.get(req.params.id);
|
|
4
|
-
if (!session) res.status(404).json({ error: 'Session not found' });
|
|
5
|
-
return session;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
app.get('/api/sessions/:id/git-show/:hash', async (req, res) => {
|
|
9
|
-
const session = getSession(req, res);
|
|
10
|
-
if (!session) return;
|
|
11
|
-
const result = await gitService.show(getWorkingDirectory(session), req.params.hash);
|
|
12
|
-
res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
app.get('/api/sessions/:id/git-branch/:hash', async (req, res) => {
|
|
16
|
-
const session = getSession(req, res);
|
|
17
|
-
if (!session) return;
|
|
18
|
-
const result = await gitService.nameRev(getWorkingDirectory(session), req.params.hash);
|
|
19
|
-
res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
app.get('/api/sessions/:id/git-log', async (req, res) => {
|
|
23
|
-
const session = getSession(req, res);
|
|
24
|
-
if (!session) return;
|
|
25
|
-
const result = await gitService.log(getWorkingDirectory(session), req.query.maxCount);
|
|
26
|
-
if (!result.success) return res.status(500).json({ error: result.error, stderr: result.stderr });
|
|
27
|
-
res.json({ success: true, commits: result.commits });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
app.get('/api/sessions/:id/git-status', async (req, res) => {
|
|
31
|
-
const session = getSession(req, res);
|
|
32
|
-
if (!session) return;
|
|
33
|
-
const result = await gitService.status(getWorkingDirectory(session));
|
|
34
|
-
if (!result.success) return res.status(500).json({ error: result.error, stderr: result.stderr });
|
|
35
|
-
res.json({ success: true, files: result.files });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
app.get('/api/sessions/:id/git-diff-numstat', async (req, res) => {
|
|
39
|
-
const session = getSession(req, res);
|
|
40
|
-
if (!session) return;
|
|
41
|
-
const result = await gitService.diffNumstat(getWorkingDirectory(session), req.query.staged === 'true');
|
|
42
|
-
res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
app.get('/api/sessions/:id/git-diff-file', async (req, res) => {
|
|
46
|
-
const session = getSession(req, res);
|
|
47
|
-
if (!session) return;
|
|
48
|
-
if (!req.query.path) return res.status(400).json({ error: 'Missing file path' });
|
|
49
|
-
const result = await gitService.diffFile(getWorkingDirectory(session), req.query.path, req.query.staged === 'true');
|
|
50
|
-
res.json({ success: result.success, stdout: result.stdout, stderr: result.stderr });
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
app.get('/api/sessions/:id/file', (req, res) => {
|
|
54
|
-
const session = getSession(req, res);
|
|
55
|
-
if (!session) return;
|
|
56
|
-
if (!req.query.path) return res.status(400).json({ error: 'Missing file path' });
|
|
57
|
-
try {
|
|
58
|
-
const content = workspaceService.readFile(getWorkingDirectory(session), req.query.path);
|
|
59
|
-
res.json({ success: true, content });
|
|
60
|
-
} catch (error) {
|
|
61
|
-
res.status(error.statusCode || 200).json({ success: false, error: error.message });
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
app.get('/api/sessions/:id/fs/dir', async (req, res) => {
|
|
66
|
-
const session = getSession(req, res);
|
|
67
|
-
if (!session) return;
|
|
68
|
-
try {
|
|
69
|
-
const files = await workspaceService.listDirectory(getWorkingDirectory(session), req.query.path || '');
|
|
70
|
-
res.json({ success: true, files });
|
|
71
|
-
} catch (error) {
|
|
72
|
-
res.status(error.statusCode || 200).json({ success: false, error: error.message });
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = registerWorkspaceRoutes;
|
package/lib/session/buffer.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
const logger = require('../utils/logger');
|
|
2
|
-
|
|
3
|
-
class CircularBuffer {
|
|
4
|
-
constructor(maxSize = 100000) {
|
|
5
|
-
this.maxSize = maxSize; // 100KB default
|
|
6
|
-
this.buffer = [];
|
|
7
|
-
this.totalSize = 0;
|
|
8
|
-
this.currentSeq = 0;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
previewText(text, maxChars = 240) {
|
|
12
|
-
if (!text) return '';
|
|
13
|
-
const normalized = String(text)
|
|
14
|
-
.replace(/\r/g, '\\r')
|
|
15
|
-
.replace(/\n/g, '\\n')
|
|
16
|
-
.replace(/\t/g, '\\t')
|
|
17
|
-
.replace(/\x1b/g, '\\x1b');
|
|
18
|
-
return normalized.length > maxChars ? normalized.slice(-maxChars) : normalized;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Calculate byte size of data
|
|
22
|
-
getByteSize(data) {
|
|
23
|
-
return Buffer.byteLength(data, 'utf8');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Append data to buffer
|
|
27
|
-
append(data) {
|
|
28
|
-
const timestamp = new Date().toISOString();
|
|
29
|
-
const seq = ++this.currentSeq;
|
|
30
|
-
const byteSize = this.getByteSize(data);
|
|
31
|
-
|
|
32
|
-
const item = {
|
|
33
|
-
seq,
|
|
34
|
-
data,
|
|
35
|
-
timestamp,
|
|
36
|
-
size: byteSize
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
this.buffer.push(item);
|
|
40
|
-
this.totalSize += byteSize;
|
|
41
|
-
|
|
42
|
-
// Remove old items if buffer exceeds max size
|
|
43
|
-
while (this.totalSize > this.maxSize && this.buffer.length > 0) {
|
|
44
|
-
const removed = this.buffer.shift();
|
|
45
|
-
this.totalSize -= removed.size;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return item;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Get all items after a specific sequence number
|
|
52
|
-
getAfter(seq) {
|
|
53
|
-
return this.buffer.filter(item => item.seq > seq);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Get all items
|
|
57
|
-
getAll() {
|
|
58
|
-
return [...this.buffer];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Get current sequence number
|
|
62
|
-
getCurrentSeq() {
|
|
63
|
-
return this.currentSeq;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Clear buffer
|
|
67
|
-
clear() {
|
|
68
|
-
logger.debug('Buffer: cleared');
|
|
69
|
-
this.buffer = [];
|
|
70
|
-
this.totalSize = 0;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Get buffer stats
|
|
74
|
-
getStats() {
|
|
75
|
-
return {
|
|
76
|
-
items: this.buffer.length,
|
|
77
|
-
totalSize: this.totalSize,
|
|
78
|
-
maxSize: this.maxSize,
|
|
79
|
-
currentSeq: this.currentSeq,
|
|
80
|
-
oldestSeq: this.buffer.length > 0 ? this.buffer[0].seq : null,
|
|
81
|
-
newestSeq: this.buffer.length > 0 ? this.buffer[this.buffer.length - 1].seq : null
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getDebugSnapshot(maxItems = 5, maxChars = 240) {
|
|
86
|
-
return {
|
|
87
|
-
...this.getStats(),
|
|
88
|
-
tailItems: this.buffer.slice(-maxItems).map(item => ({
|
|
89
|
-
seq: item.seq,
|
|
90
|
-
size: item.size,
|
|
91
|
-
timestamp: item.timestamp,
|
|
92
|
-
preview: this.previewText(item.data, maxChars)
|
|
93
|
-
})),
|
|
94
|
-
combinedTailPreview: this.previewText(
|
|
95
|
-
this.buffer.slice(-maxItems).map(item => item.data).join(''),
|
|
96
|
-
maxChars
|
|
97
|
-
)
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
module.exports = CircularBuffer;
|