botmux 2.12.2 → 2.13.0
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.en.md +29 -0
- package/README.md +29 -0
- package/dist/cli.js +104 -8
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -1
- package/dist/core/dashboard-events.d.ts +57 -0
- package/dist/core/dashboard-events.d.ts.map +1 -0
- package/dist/core/dashboard-events.js +23 -0
- package/dist/core/dashboard-events.js.map +1 -0
- package/dist/core/dashboard-ipc-server.d.ts +43 -0
- package/dist/core/dashboard-ipc-server.d.ts.map +1 -0
- package/dist/core/dashboard-ipc-server.js +232 -0
- package/dist/core/dashboard-ipc-server.js.map +1 -0
- package/dist/core/dashboard-locate.d.ts +20 -0
- package/dist/core/dashboard-locate.d.ts.map +1 -0
- package/dist/core/dashboard-locate.js +26 -0
- package/dist/core/dashboard-locate.js.map +1 -0
- package/dist/core/dashboard-rows.d.ts +30 -0
- package/dist/core/dashboard-rows.d.ts.map +1 -0
- package/dist/core/dashboard-rows.js +48 -0
- package/dist/core/dashboard-rows.js.map +1 -0
- package/dist/core/scheduler.d.ts +20 -0
- package/dist/core/scheduler.d.ts.map +1 -1
- package/dist/core/scheduler.js +89 -2
- package/dist/core/scheduler.js.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/worker-pool.d.ts +19 -1
- package/dist/core/worker-pool.d.ts.map +1 -1
- package/dist/core/worker-pool.js +146 -0
- package/dist/core/worker-pool.js.map +1 -1
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +87 -4
- package/dist/daemon.js.map +1 -1
- package/dist/dashboard/aggregator.d.ts +41 -0
- package/dist/dashboard/aggregator.d.ts.map +1 -0
- package/dist/dashboard/aggregator.js +125 -0
- package/dist/dashboard/aggregator.js.map +1 -0
- package/dist/dashboard/auth.d.ts +23 -0
- package/dist/dashboard/auth.d.ts.map +1 -0
- package/dist/dashboard/auth.js +66 -0
- package/dist/dashboard/auth.js.map +1 -0
- package/dist/dashboard/registry.d.ts +28 -0
- package/dist/dashboard/registry.d.ts.map +1 -0
- package/dist/dashboard/registry.js +74 -0
- package/dist/dashboard/registry.js.map +1 -0
- package/dist/dashboard/web/app.d.ts +2 -0
- package/dist/dashboard/web/app.d.ts.map +1 -0
- package/dist/dashboard/web/app.js +42 -0
- package/dist/dashboard/web/app.js.map +1 -0
- package/dist/dashboard/web/groups.d.ts +2 -0
- package/dist/dashboard/web/groups.d.ts.map +1 -0
- package/dist/dashboard/web/groups.js +152 -0
- package/dist/dashboard/web/groups.js.map +1 -0
- package/dist/dashboard/web/schedules.d.ts +2 -0
- package/dist/dashboard/web/schedules.d.ts.map +1 -0
- package/dist/dashboard/web/schedules.js +105 -0
- package/dist/dashboard/web/schedules.js.map +1 -0
- package/dist/dashboard/web/sessions.d.ts +2 -0
- package/dist/dashboard/web/sessions.d.ts.map +1 -0
- package/dist/dashboard/web/sessions.js +184 -0
- package/dist/dashboard/web/sessions.js.map +1 -0
- package/dist/dashboard/web/store.d.ts +23 -0
- package/dist/dashboard/web/store.d.ts.map +1 -0
- package/dist/dashboard/web/store.js +82 -0
- package/dist/dashboard/web/store.js.map +1 -0
- package/dist/dashboard-web/app.js +129 -0
- package/dist/dashboard-web/index.html +22 -0
- package/dist/dashboard-web/style.css +50 -0
- package/dist/dashboard.d.ts +2 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +308 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/services/bridge-turn-queue.d.ts.map +1 -1
- package/dist/services/bridge-turn-queue.js +12 -7
- package/dist/services/bridge-turn-queue.js.map +1 -1
- package/dist/services/groups-store.d.ts +35 -0
- package/dist/services/groups-store.d.ts.map +1 -0
- package/dist/services/groups-store.js +104 -0
- package/dist/services/groups-store.js.map +1 -0
- package/dist/services/schedule-store.d.ts +1 -0
- package/dist/services/schedule-store.d.ts.map +1 -1
- package/dist/services/schedule-store.js +70 -1
- package/dist/services/schedule-store.js.map +1 -1
- package/dist/services/session-store.d.ts.map +1 -1
- package/dist/services/session-store.js +1 -0
- package/dist/services/session-store.js.map +1 -1
- package/dist/skills/definitions.d.ts.map +1 -1
- package/dist/skills/definitions.js +34 -0
- package/dist/skills/definitions.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/worker.js +47 -2
- package/dist/worker.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/core/dashboard-ipc-server.ts
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { logger } from '../utils/logger.js';
|
|
4
|
+
import * as sessionStore from '../services/session-store.js';
|
|
5
|
+
import * as scheduleStore from '../services/schedule-store.js';
|
|
6
|
+
import * as groupsStore from '../services/groups-store.js';
|
|
7
|
+
import * as scheduler from './scheduler.js';
|
|
8
|
+
import { listActiveSessions, findActiveBySessionId, closeSession } from './worker-pool.js';
|
|
9
|
+
import { replyMessage } from '../im/lark/client.js';
|
|
10
|
+
import { locateLimiter } from './dashboard-locate.js';
|
|
11
|
+
import { dashboardEventBus } from './dashboard-events.js';
|
|
12
|
+
import { composeRowFromActive, composeRowFromClosed, feishuChatLink, setBotName as setRowsBotName, getBotName, } from './dashboard-rows.js';
|
|
13
|
+
const routes = [];
|
|
14
|
+
/** Register a handler. Path supports `:name` segments captured into the params object. */
|
|
15
|
+
export function ipcRoute(method, path, handler) {
|
|
16
|
+
const keys = [];
|
|
17
|
+
const pattern = new RegExp('^' + path.replace(/:([a-zA-Z]+)/g, (_, k) => { keys.push(k); return '([^/]+)'; }) + '$');
|
|
18
|
+
routes.push({ method: method.toUpperCase(), pattern, keys, handler });
|
|
19
|
+
}
|
|
20
|
+
export function jsonRes(res, status, body) {
|
|
21
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
22
|
+
res.end(JSON.stringify(body));
|
|
23
|
+
}
|
|
24
|
+
export async function readJsonBody(req) {
|
|
25
|
+
const chunks = [];
|
|
26
|
+
for await (const c of req)
|
|
27
|
+
chunks.push(c);
|
|
28
|
+
if (chunks.length === 0)
|
|
29
|
+
return {};
|
|
30
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
31
|
+
}
|
|
32
|
+
ipcRoute('GET', '/__health', (_req, res) => {
|
|
33
|
+
jsonRes(res, 200, { ok: true });
|
|
34
|
+
});
|
|
35
|
+
export { composeRowFromActive, composeRowFromClosed };
|
|
36
|
+
// Re-export setBotName for backwards-compatible imports (daemon.ts). Both
|
|
37
|
+
// callers (this module's cachedBotName + dashboard-rows' cachedBotName) need
|
|
38
|
+
// to be primed; here we forward to the rows module which is the canonical
|
|
39
|
+
// holder.
|
|
40
|
+
export function setBotName(name) { setRowsBotName(name); }
|
|
41
|
+
// The daemon's own larkAppId, primed at startup. Required for the groups
|
|
42
|
+
// endpoints below which proxy calls into groups-store on this bot's behalf.
|
|
43
|
+
let cachedLarkAppId = '';
|
|
44
|
+
export function setLarkAppId(id) { cachedLarkAppId = id; }
|
|
45
|
+
ipcRoute('GET', '/api/sessions', (_req, res) => {
|
|
46
|
+
// Active first (live state), closed appended (historical)
|
|
47
|
+
const active = listActiveSessions().map(composeRowFromActive);
|
|
48
|
+
const activeIds = new Set(active.map(r => r.sessionId));
|
|
49
|
+
const closed = sessionStore.listSessions()
|
|
50
|
+
.filter(s => s.status === 'closed' && !activeIds.has(s.sessionId))
|
|
51
|
+
.map(composeRowFromClosed);
|
|
52
|
+
jsonRes(res, 200, { sessions: [...active, ...closed] });
|
|
53
|
+
});
|
|
54
|
+
ipcRoute('GET', '/api/sessions/:sessionId', (_req, res, params) => {
|
|
55
|
+
const ds = findActiveBySessionId(params.sessionId);
|
|
56
|
+
if (ds)
|
|
57
|
+
return jsonRes(res, 200, { session: composeRowFromActive(ds) });
|
|
58
|
+
const closed = sessionStore.listSessions().find(s => s.sessionId === params.sessionId);
|
|
59
|
+
if (closed)
|
|
60
|
+
return jsonRes(res, 200, { session: composeRowFromClosed(closed) });
|
|
61
|
+
jsonRes(res, 404, { error: 'not_found' });
|
|
62
|
+
});
|
|
63
|
+
ipcRoute('POST', '/api/sessions/:sessionId/close', async (_req, res, params) => {
|
|
64
|
+
const r = await closeSession(params.sessionId);
|
|
65
|
+
jsonRes(res, 200, r);
|
|
66
|
+
});
|
|
67
|
+
ipcRoute('POST', '/api/sessions/:sessionId/locate', async (_req, res, params) => {
|
|
68
|
+
const sid = params.sessionId;
|
|
69
|
+
const acq = locateLimiter.tryAcquire(sid);
|
|
70
|
+
if (!acq.ok) {
|
|
71
|
+
res.writeHead(429, {
|
|
72
|
+
'content-type': 'application/json',
|
|
73
|
+
'retry-after': String(Math.ceil(acq.retryAfterMs / 1000)),
|
|
74
|
+
});
|
|
75
|
+
res.end(JSON.stringify({ ok: false, error: 'rate_limited', retryAfterMs: acq.retryAfterMs }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Resolve owning session (active first, then closed-store fallback)
|
|
79
|
+
const ds = findActiveBySessionId(sid);
|
|
80
|
+
const closed = ds ? null : sessionStore.getSession(sid);
|
|
81
|
+
const ctx = ds
|
|
82
|
+
? {
|
|
83
|
+
larkAppId: ds.larkAppId,
|
|
84
|
+
rootMessageId: ds.session.rootMessageId,
|
|
85
|
+
title: ds.session.title || `Dashboard 定位 (${sid.slice(0, 8)})`,
|
|
86
|
+
}
|
|
87
|
+
: closed
|
|
88
|
+
? {
|
|
89
|
+
larkAppId: closed.larkAppId ?? '',
|
|
90
|
+
rootMessageId: closed.rootMessageId,
|
|
91
|
+
title: closed.title || `Dashboard 定位 (${sid.slice(0, 8)})`,
|
|
92
|
+
}
|
|
93
|
+
: null;
|
|
94
|
+
if (!ctx || !ctx.larkAppId) {
|
|
95
|
+
return jsonRes(res, 404, { ok: false, error: 'session_not_found' });
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const messageId = await replyMessage(ctx.larkAppId, ctx.rootMessageId, `📍 Dashboard 定位 ${ctx.title}`, 'text', true);
|
|
99
|
+
jsonRes(res, 200, { ok: true, messageId });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
jsonRes(res, 502, { ok: false, error: String(err) });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
function composeScheduleRow(t) {
|
|
106
|
+
return {
|
|
107
|
+
id: t.id,
|
|
108
|
+
name: t.name,
|
|
109
|
+
parsed: t.parsed,
|
|
110
|
+
prompt: t.prompt,
|
|
111
|
+
workingDir: t.workingDir,
|
|
112
|
+
chatId: t.chatId,
|
|
113
|
+
rootMessageId: t.rootMessageId,
|
|
114
|
+
larkAppId: t.larkAppId,
|
|
115
|
+
botName: getBotName(),
|
|
116
|
+
enabled: t.enabled,
|
|
117
|
+
createdAt: t.createdAt,
|
|
118
|
+
lastRunAt: t.lastRunAt,
|
|
119
|
+
nextRunAt: t.nextRunAt,
|
|
120
|
+
lastStatus: t.lastStatus,
|
|
121
|
+
lastError: t.lastError,
|
|
122
|
+
repeat: t.repeat,
|
|
123
|
+
feishuChatLink: feishuChatLink(t.chatId),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
ipcRoute('GET', '/api/schedules', (_req, res) => {
|
|
127
|
+
// Filter to tasks owned by this daemon's bot (multi-bot setups run one
|
|
128
|
+
// daemon per bot — each only manages its own schedules). belongsToOwner
|
|
129
|
+
// falls through to "all tasks" when no owner filter is configured (tests).
|
|
130
|
+
const all = scheduleStore.listTasks().filter(t => scheduler.belongsToOwner(t));
|
|
131
|
+
jsonRes(res, 200, { schedules: all.map(composeScheduleRow) });
|
|
132
|
+
});
|
|
133
|
+
ipcRoute('POST', '/api/schedules/:id/run', (_req, res, p) => jsonRes(res, 200, scheduler.runNow(p.id)));
|
|
134
|
+
ipcRoute('POST', '/api/schedules/:id/pause', (_req, res, p) => jsonRes(res, 200, scheduler.setEnabled(p.id, false)));
|
|
135
|
+
ipcRoute('POST', '/api/schedules/:id/resume', (_req, res, p) => jsonRes(res, 200, scheduler.setEnabled(p.id, true)));
|
|
136
|
+
// ─── Groups (Phase B) ──────────────────────────────────────────────────────
|
|
137
|
+
ipcRoute('GET', '/api/groups', async (_req, res) => {
|
|
138
|
+
if (!cachedLarkAppId)
|
|
139
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
140
|
+
try {
|
|
141
|
+
const chats = await groupsStore.listChats(cachedLarkAppId);
|
|
142
|
+
jsonRes(res, 200, { chats });
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
jsonRes(res, 502, { error: String(e) });
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
ipcRoute('GET', '/api/groups/:chatId/membership', async (_req, res, p) => {
|
|
149
|
+
if (!cachedLarkAppId)
|
|
150
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
151
|
+
try {
|
|
152
|
+
const inChat = await groupsStore.isInChat(cachedLarkAppId, p.chatId);
|
|
153
|
+
jsonRes(res, 200, { inChat });
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
jsonRes(res, 502, { error: String(e) });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
ipcRoute('POST', '/api/groups/:chatId/add-bots', async (req, res, p) => {
|
|
160
|
+
if (!cachedLarkAppId)
|
|
161
|
+
return jsonRes(res, 503, { error: 'larkAppId_not_set' });
|
|
162
|
+
let body;
|
|
163
|
+
try {
|
|
164
|
+
body = await readJsonBody(req);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return jsonRes(res, 400, { error: 'bad_json' });
|
|
168
|
+
}
|
|
169
|
+
if (!Array.isArray(body.larkAppIds) || !body.larkAppIds.every(x => typeof x === 'string')) {
|
|
170
|
+
return jsonRes(res, 400, { error: 'larkAppIds_required' });
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const result = await groupsStore.addBotToChat(cachedLarkAppId, p.chatId, body.larkAppIds);
|
|
174
|
+
jsonRes(res, 200, { result });
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
jsonRes(res, 502, { error: String(e) });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
// ─── SSE event stream ──────────────────────────────────────────────────────
|
|
181
|
+
ipcRoute('GET', '/api/events', (_req, res) => {
|
|
182
|
+
res.writeHead(200, {
|
|
183
|
+
'content-type': 'text/event-stream',
|
|
184
|
+
'cache-control': 'no-cache, no-transform',
|
|
185
|
+
'connection': 'keep-alive',
|
|
186
|
+
});
|
|
187
|
+
// Initial flush so the client sees the connection alive immediately.
|
|
188
|
+
res.write('retry: 5000\n\n');
|
|
189
|
+
const off = dashboardEventBus.subscribe(ev => {
|
|
190
|
+
res.write(`event: ${ev.type}\ndata: ${JSON.stringify(ev.body)}\n\n`);
|
|
191
|
+
});
|
|
192
|
+
const hb = setInterval(() => {
|
|
193
|
+
res.write(`event: heartbeat\ndata: ${JSON.stringify({ ts: Date.now() })}\n\n`);
|
|
194
|
+
}, 15_000);
|
|
195
|
+
res.on('close', () => { off(); clearInterval(hb); });
|
|
196
|
+
});
|
|
197
|
+
export function startIpcServer(opts) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
const server = createServer(async (req, res) => {
|
|
200
|
+
try {
|
|
201
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`);
|
|
202
|
+
for (const r of routes) {
|
|
203
|
+
if (r.method !== req.method)
|
|
204
|
+
continue;
|
|
205
|
+
const m = r.pattern.exec(url.pathname);
|
|
206
|
+
if (!m)
|
|
207
|
+
continue;
|
|
208
|
+
const params = {};
|
|
209
|
+
r.keys.forEach((k, i) => { params[k] = decodeURIComponent(m[i + 1]); });
|
|
210
|
+
await r.handler(req, res, params);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
jsonRes(res, 404, { error: 'not_found', path: url.pathname });
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
logger.error('[dashboard-ipc] handler error', err);
|
|
217
|
+
if (!res.headersSent)
|
|
218
|
+
jsonRes(res, 500, { error: String(err) });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
server.listen(opts.port, opts.host, () => {
|
|
222
|
+
const addr = server.address();
|
|
223
|
+
const port = typeof addr === 'object' && addr ? addr.port : opts.port;
|
|
224
|
+
resolve({
|
|
225
|
+
port,
|
|
226
|
+
close: () => new Promise(r => server.close(() => r())),
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
server.once('error', reject);
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
//# sourceMappingURL=dashboard-ipc-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-ipc-server.js","sourceRoot":"","sources":["../../src/core/dashboard-ipc-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-sessionId 30s rate limiter for the dashboard locate action.
|
|
3
|
+
* The slot is consumed on attempt, not on success — a 404 still burns the
|
|
4
|
+
* slot. This keeps the rate limit useful against sessionId enumeration via
|
|
5
|
+
* fast spam and matches the design spec.
|
|
6
|
+
*/
|
|
7
|
+
export declare class LocateRateLimiter {
|
|
8
|
+
private windowMs;
|
|
9
|
+
private last;
|
|
10
|
+
constructor(windowMs: number);
|
|
11
|
+
tryAcquire(sessionId: string): {
|
|
12
|
+
ok: true;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
retryAfterMs: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Module singleton — process-wide rate limit shared across all routes. */
|
|
19
|
+
export declare const locateLimiter: LocateRateLimiter;
|
|
20
|
+
//# sourceMappingURL=dashboard-locate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-locate.d.ts","sourceRoot":"","sources":["../../src/core/dashboard-locate.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qBAAa,iBAAiB;IAEhB,OAAO,CAAC,QAAQ;IAD5B,OAAO,CAAC,IAAI,CAA6B;gBACrB,QAAQ,EAAE,MAAM;IAEpC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,IAAI,CAAA;KAAE,GAAG;QAAE,EAAE,EAAE,KAAK,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE;CASlF;AAED,2EAA2E;AAC3E,eAAO,MAAM,aAAa,mBAAgC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// src/core/dashboard-locate.ts
|
|
2
|
+
/**
|
|
3
|
+
* Per-sessionId 30s rate limiter for the dashboard locate action.
|
|
4
|
+
* The slot is consumed on attempt, not on success — a 404 still burns the
|
|
5
|
+
* slot. This keeps the rate limit useful against sessionId enumeration via
|
|
6
|
+
* fast spam and matches the design spec.
|
|
7
|
+
*/
|
|
8
|
+
export class LocateRateLimiter {
|
|
9
|
+
windowMs;
|
|
10
|
+
last = new Map();
|
|
11
|
+
constructor(windowMs) {
|
|
12
|
+
this.windowMs = windowMs;
|
|
13
|
+
}
|
|
14
|
+
tryAcquire(sessionId) {
|
|
15
|
+
const now = Date.now();
|
|
16
|
+
const last = this.last.get(sessionId);
|
|
17
|
+
if (last !== undefined && now - last < this.windowMs) {
|
|
18
|
+
return { ok: false, retryAfterMs: this.windowMs - (now - last) };
|
|
19
|
+
}
|
|
20
|
+
this.last.set(sessionId, now);
|
|
21
|
+
return { ok: true };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Module singleton — process-wide rate limit shared across all routes. */
|
|
25
|
+
export const locateLimiter = new LocateRateLimiter(30_000);
|
|
26
|
+
//# sourceMappingURL=dashboard-locate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-locate.js","sourceRoot":"","sources":["../../src/core/dashboard-locate.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAE/B;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IAER;IADZ,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,YAAoB,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAExC,UAAU,CAAC,SAAiB;QAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;QAC9B,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;CACF;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DaemonSession } from './types.js';
|
|
2
|
+
import type { Session } from '../types.js';
|
|
3
|
+
import type { CliId } from '../adapters/cli/types.js';
|
|
4
|
+
export interface SessionRow {
|
|
5
|
+
sessionId: string;
|
|
6
|
+
larkAppId: string;
|
|
7
|
+
botName: string;
|
|
8
|
+
cliId: CliId | 'unknown';
|
|
9
|
+
status: 'starting' | 'working' | 'idle' | 'analyzing' | 'closed';
|
|
10
|
+
adopt: boolean;
|
|
11
|
+
spawnedAt: number;
|
|
12
|
+
lastMessageAt: number;
|
|
13
|
+
closedAt?: number;
|
|
14
|
+
workingDir?: string;
|
|
15
|
+
chatId: string;
|
|
16
|
+
rootMessageId: string;
|
|
17
|
+
threadId?: string;
|
|
18
|
+
title?: string;
|
|
19
|
+
ownerOpenId?: string;
|
|
20
|
+
webPort: number | null;
|
|
21
|
+
cliVersion?: string;
|
|
22
|
+
hasHistory?: boolean;
|
|
23
|
+
feishuChatLink: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function feishuChatLink(chatId: string): string;
|
|
26
|
+
export declare function setBotName(name: string): void;
|
|
27
|
+
export declare function getBotName(): string;
|
|
28
|
+
export declare function composeRowFromActive(ds: DaemonSession): SessionRow;
|
|
29
|
+
export declare function composeRowFromClosed(s: Session): SessionRow;
|
|
30
|
+
//# sourceMappingURL=dashboard-rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-rows.d.ts","sourceRoot":"","sources":["../../src/core/dashboard-rows.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAEtD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IACzB,MAAM,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACjE,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAA0B;AACxE,wBAAgB,UAAU,IAAI,MAAM,CAA0B;AAE9D,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,aAAa,GAAG,UAAU,CAoBlE;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,CAmB3D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export function feishuChatLink(chatId) {
|
|
2
|
+
return `https://applink.feishu.cn/client/chat/open?openChatId=${encodeURIComponent(chatId)}`;
|
|
3
|
+
}
|
|
4
|
+
let cachedBotName = '';
|
|
5
|
+
export function setBotName(name) { cachedBotName = name; }
|
|
6
|
+
export function getBotName() { return cachedBotName; }
|
|
7
|
+
export function composeRowFromActive(ds) {
|
|
8
|
+
return {
|
|
9
|
+
sessionId: ds.session.sessionId,
|
|
10
|
+
larkAppId: ds.larkAppId,
|
|
11
|
+
botName: cachedBotName,
|
|
12
|
+
cliId: ds.session.cliId ?? 'unknown',
|
|
13
|
+
status: ds.lastScreenStatus ?? 'starting',
|
|
14
|
+
adopt: !!ds.adoptedFrom,
|
|
15
|
+
spawnedAt: ds.spawnedAt,
|
|
16
|
+
lastMessageAt: ds.lastMessageAt,
|
|
17
|
+
workingDir: ds.workingDir,
|
|
18
|
+
chatId: ds.chatId,
|
|
19
|
+
rootMessageId: ds.session.rootMessageId,
|
|
20
|
+
title: ds.session.title,
|
|
21
|
+
ownerOpenId: ds.ownerOpenId,
|
|
22
|
+
webPort: ds.workerPort ?? null,
|
|
23
|
+
cliVersion: ds.cliVersion,
|
|
24
|
+
hasHistory: ds.hasHistory,
|
|
25
|
+
feishuChatLink: feishuChatLink(ds.chatId),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function composeRowFromClosed(s) {
|
|
29
|
+
return {
|
|
30
|
+
sessionId: s.sessionId,
|
|
31
|
+
larkAppId: s.larkAppId ?? '',
|
|
32
|
+
botName: cachedBotName,
|
|
33
|
+
cliId: s.cliId ?? 'unknown',
|
|
34
|
+
status: 'closed',
|
|
35
|
+
adopt: !!s.adoptedFrom,
|
|
36
|
+
spawnedAt: Date.parse(s.createdAt),
|
|
37
|
+
lastMessageAt: s.closedAt ? Date.parse(s.closedAt) : Date.parse(s.createdAt),
|
|
38
|
+
closedAt: s.closedAt ? Date.parse(s.closedAt) : undefined,
|
|
39
|
+
workingDir: s.workingDir,
|
|
40
|
+
chatId: s.chatId,
|
|
41
|
+
rootMessageId: s.rootMessageId,
|
|
42
|
+
title: s.title,
|
|
43
|
+
ownerOpenId: s.ownerOpenId,
|
|
44
|
+
webPort: s.webPort ?? null,
|
|
45
|
+
feishuChatLink: feishuChatLink(s.chatId),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=dashboard-rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard-rows.js","sourceRoot":"","sources":["../../src/core/dashboard-rows.ts"],"names":[],"mappings":"AAiCA,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,OAAO,yDAAyD,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,IAAI,aAAa,GAAG,EAAE,CAAC;AACvB,MAAM,UAAU,UAAU,CAAC,IAAY,IAAU,aAAa,GAAG,IAAI,CAAC,CAAC,CAAC;AACxE,MAAM,UAAU,UAAU,KAAa,OAAO,aAAa,CAAC,CAAC,CAAC;AAE9D,MAAM,UAAU,oBAAoB,CAAC,EAAiB;IACpD,OAAO;QACL,SAAS,EAAE,EAAE,CAAC,OAAO,CAAC,SAAS;QAC/B,SAAS,EAAE,EAAE,CAAC,SAAS;QACvB,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,SAAS;QACpC,MAAM,EAAE,EAAE,CAAC,gBAAgB,IAAI,UAAU;QACzC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW;QACvB,SAAS,EAAE,EAAE,CAAC,SAAS;QACvB,aAAa,EAAE,EAAE,CAAC,aAAa;QAC/B,UAAU,EAAE,EAAE,CAAC,UAAU;QACzB,MAAM,EAAE,EAAE,CAAC,MAAM;QACjB,aAAa,EAAE,EAAE,CAAC,OAAO,CAAC,aAAa;QACvC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK;QACvB,WAAW,EAAE,EAAE,CAAC,WAAW;QAC3B,OAAO,EAAE,EAAE,CAAC,UAAU,IAAI,IAAI;QAC9B,UAAU,EAAE,EAAE,CAAC,UAAU;QACzB,UAAU,EAAE,EAAE,CAAC,UAAU;QACzB,cAAc,EAAE,cAAc,CAAC,EAAE,CAAC,MAAM,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,CAAU;IAC7C,OAAO;QACL,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,SAAS,EAAE,CAAC,CAAC,SAAS,IAAI,EAAE;QAC5B,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS;QAC3B,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW;QACtB,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAClC,aAAa,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5E,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QACzD,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,aAAa,EAAE,CAAC,CAAC,aAAa;QAC9B,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,OAAO,EAAE,CAAC,CAAC,OAAO,IAAI,IAAI;QAC1B,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC;KACzC,CAAC;AACJ,CAAC"}
|
package/dist/core/scheduler.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare function setExecuteCallback(cb: (task: ScheduledTask) => Promise<
|
|
|
11
11
|
* routed here as a compatibility fallback
|
|
12
12
|
*/
|
|
13
13
|
export declare function setOwnerFilter(larkAppId: string, isPrimary: boolean): void;
|
|
14
|
+
/** Public ownership check — used by dashboard IPC to filter list-by-owner. */
|
|
15
|
+
export declare function belongsToOwner(task: ScheduledTask): boolean;
|
|
14
16
|
/**
|
|
15
17
|
* Parse a bare schedule string (no prompt). Supports:
|
|
16
18
|
* - Chinese NL: "每日17:50" / "每周一10:00" / "30分钟后" / "明天9:00"
|
|
@@ -59,5 +61,23 @@ export declare function enableTask(id: string): boolean;
|
|
|
59
61
|
export declare function disableTask(id: string): boolean;
|
|
60
62
|
export declare function runTaskNow(id: string): boolean;
|
|
61
63
|
export declare function getNextRun(id: string): Date | null;
|
|
64
|
+
/**
|
|
65
|
+
* Fire a scheduled task immediately. Returns ok=false if id not found or the
|
|
66
|
+
* scheduler hasn't been initialised with an executeCallback yet. Emits a
|
|
67
|
+
* `schedule.fired` event on completion (success or error).
|
|
68
|
+
*/
|
|
69
|
+
export declare function runNow(id: string): {
|
|
70
|
+
ok: boolean;
|
|
71
|
+
error?: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Toggle a task's `enabled` flag and persist. When enabling a task we also
|
|
75
|
+
* recompute `nextRunAt` so the next tick can pick it up. Emits a
|
|
76
|
+
* `schedule.updated` event.
|
|
77
|
+
*/
|
|
78
|
+
export declare function setEnabled(id: string, enabled: boolean): {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
error?: string;
|
|
81
|
+
};
|
|
62
82
|
export {};
|
|
63
83
|
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAiBjE,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAEnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAG1E;AASD,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE3D;AA4GD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CA+C3D;AAWD,UAAU,aAAa;IACrB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAWxE;AAID,iGAAiG;AACjG,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8BxF;AAiGD,wBAAgB,cAAc,IAAI,IAAI,CAkBrC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAGpC;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,OAAO,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC9B,GAAG,aAAa,CAsBhB;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAM9C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAK/C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAU9C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAIlD;AAOD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiClE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAexF"}
|
package/dist/core/scheduler.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Cron } from 'croner';
|
|
2
2
|
import * as scheduleStore from '../services/schedule-store.js';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
|
+
import { dashboardEventBus } from './dashboard-events.js';
|
|
4
5
|
// Callback set by daemon to execute a scheduled task
|
|
5
6
|
let executeCallback = null;
|
|
6
7
|
let tickTimer = null;
|
|
@@ -38,6 +39,10 @@ function taskBelongsToThisDaemon(task) {
|
|
|
38
39
|
// No larkAppId on task (legacy) — only the primary (bot-0) handles it.
|
|
39
40
|
return ownerIsPrimary;
|
|
40
41
|
}
|
|
42
|
+
/** Public ownership check — used by dashboard IPC to filter list-by-owner. */
|
|
43
|
+
export function belongsToOwner(task) {
|
|
44
|
+
return taskBelongsToThisDaemon(task);
|
|
45
|
+
}
|
|
41
46
|
// ─── Chinese NL parsing (schedule portion only, returns ParsedSchedule) ─────
|
|
42
47
|
const WEEKDAY_MAP = {
|
|
43
48
|
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5, '六': 6, '日': 0, '天': 0,
|
|
@@ -313,11 +318,27 @@ async function tick() {
|
|
|
313
318
|
logger.info(`[scheduler] Task "${task.name}" (${task.id}) triggered (kind=${task.parsed.kind})`);
|
|
314
319
|
scheduleStore.updateTask(task.id, { lastRunAt: new Date().toISOString() });
|
|
315
320
|
if (executeCallback) {
|
|
321
|
+
const taskId = task.id;
|
|
316
322
|
executeCallback(task)
|
|
317
|
-
.then(() =>
|
|
323
|
+
.then(() => {
|
|
324
|
+
scheduleStore.markRun(taskId, true);
|
|
325
|
+
dashboardEventBus.publish({
|
|
326
|
+
type: 'schedule.fired',
|
|
327
|
+
body: { id: taskId, runAt: Date.now(), status: 'ok' },
|
|
328
|
+
});
|
|
329
|
+
})
|
|
318
330
|
.catch(err => {
|
|
319
331
|
logger.error(`[scheduler] Task "${task.name}" failed: ${err.message}`);
|
|
320
|
-
scheduleStore.markRun(
|
|
332
|
+
scheduleStore.markRun(taskId, false, err.message);
|
|
333
|
+
dashboardEventBus.publish({
|
|
334
|
+
type: 'schedule.fired',
|
|
335
|
+
body: {
|
|
336
|
+
id: taskId,
|
|
337
|
+
runAt: Date.now(),
|
|
338
|
+
status: 'error',
|
|
339
|
+
error: err instanceof Error ? err.message : String(err),
|
|
340
|
+
},
|
|
341
|
+
});
|
|
321
342
|
});
|
|
322
343
|
}
|
|
323
344
|
}
|
|
@@ -407,4 +428,70 @@ export function getNextRun(id) {
|
|
|
407
428
|
return null;
|
|
408
429
|
return new Date(task.nextRunAt);
|
|
409
430
|
}
|
|
431
|
+
// ─── Dashboard IPC helpers ──────────────────────────────────────────────────
|
|
432
|
+
// Thin {ok, error?}-shaped wrappers used by the web dashboard. They invoke
|
|
433
|
+
// the real scheduler primitives above and additionally publish dashboard
|
|
434
|
+
// events so subscribed SSE clients see the state change immediately.
|
|
435
|
+
/**
|
|
436
|
+
* Fire a scheduled task immediately. Returns ok=false if id not found or the
|
|
437
|
+
* scheduler hasn't been initialised with an executeCallback yet. Emits a
|
|
438
|
+
* `schedule.fired` event on completion (success or error).
|
|
439
|
+
*/
|
|
440
|
+
export function runNow(id) {
|
|
441
|
+
const task = scheduleStore.getTask(id);
|
|
442
|
+
if (!task)
|
|
443
|
+
return { ok: false, error: 'not_found' };
|
|
444
|
+
if (!executeCallback)
|
|
445
|
+
return { ok: false, error: 'not_initialised' };
|
|
446
|
+
// Bump lastRunAt + nextRunAt synchronously so the upcoming 30s tick won't
|
|
447
|
+
// re-fire the same task while this manual run is still in flight.
|
|
448
|
+
const nowIso = new Date().toISOString();
|
|
449
|
+
const next = computeNextRun(task.parsed, nowIso);
|
|
450
|
+
scheduleStore.updateTask(id, {
|
|
451
|
+
lastRunAt: nowIso,
|
|
452
|
+
nextRunAt: next ?? undefined,
|
|
453
|
+
});
|
|
454
|
+
// Don't block the caller — fire on next tick. `Promise.resolve().then`
|
|
455
|
+
// coerces a synchronous throw from executeCallback into a rejection so the
|
|
456
|
+
// error path always runs and we don't leak a 500 to the IPC client.
|
|
457
|
+
void Promise.resolve().then(() => executeCallback(task)).then(() => {
|
|
458
|
+
scheduleStore.markRun(task.id, true);
|
|
459
|
+
dashboardEventBus.publish({
|
|
460
|
+
type: 'schedule.fired',
|
|
461
|
+
body: { id, runAt: Date.now(), status: 'ok' },
|
|
462
|
+
});
|
|
463
|
+
}, err => {
|
|
464
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
465
|
+
scheduleStore.markRun(task.id, false, msg);
|
|
466
|
+
dashboardEventBus.publish({
|
|
467
|
+
type: 'schedule.fired',
|
|
468
|
+
body: { id, runAt: Date.now(), status: 'error', error: msg },
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
return { ok: true };
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Toggle a task's `enabled` flag and persist. When enabling a task we also
|
|
475
|
+
* recompute `nextRunAt` so the next tick can pick it up. Emits a
|
|
476
|
+
* `schedule.updated` event.
|
|
477
|
+
*/
|
|
478
|
+
export function setEnabled(id, enabled) {
|
|
479
|
+
const task = scheduleStore.getTask(id);
|
|
480
|
+
if (!task)
|
|
481
|
+
return { ok: false, error: 'not_found' };
|
|
482
|
+
if (task.enabled === enabled)
|
|
483
|
+
return { ok: true }; // no-op
|
|
484
|
+
if (enabled) {
|
|
485
|
+
const next = computeNextRun(task.parsed);
|
|
486
|
+
scheduleStore.updateTask(id, { enabled: true, nextRunAt: next ?? undefined });
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
scheduleStore.updateTask(id, { enabled: false });
|
|
490
|
+
}
|
|
491
|
+
dashboardEventBus.publish({
|
|
492
|
+
type: 'schedule.updated',
|
|
493
|
+
body: { id, patch: { enabled } },
|
|
494
|
+
});
|
|
495
|
+
return { ok: true };
|
|
496
|
+
}
|
|
410
497
|
//# sourceMappingURL=scheduler.js.map
|