@csdwd/ai-teams-server 0.1.5 → 0.2.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/dist/index.js +387 -2
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import fs from "node:fs";
|
|
7
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
import Fastify from "fastify";
|
|
9
10
|
import swagger from "@fastify/swagger";
|
|
@@ -373,6 +374,25 @@ async function initDb(db) {
|
|
|
373
374
|
webhook_url TEXT NOT NULL
|
|
374
375
|
)
|
|
375
376
|
`);
|
|
377
|
+
await db.run(`
|
|
378
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
379
|
+
id TEXT PRIMARY KEY,
|
|
380
|
+
name TEXT NOT NULL,
|
|
381
|
+
cron_expr TEXT NOT NULL,
|
|
382
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
383
|
+
target_mode TEXT NOT NULL DEFAULT 'queue',
|
|
384
|
+
target_agents TEXT NOT NULL DEFAULT '[]',
|
|
385
|
+
prompt TEXT NOT NULL,
|
|
386
|
+
workspace TEXT,
|
|
387
|
+
timeout_sec INTEGER,
|
|
388
|
+
priority INTEGER NOT NULL DEFAULT 0,
|
|
389
|
+
required_labels TEXT DEFAULT '[]',
|
|
390
|
+
last_run_at TEXT,
|
|
391
|
+
next_run_at TEXT,
|
|
392
|
+
created_at TEXT NOT NULL,
|
|
393
|
+
updated_at TEXT NOT NULL
|
|
394
|
+
)
|
|
395
|
+
`);
|
|
376
396
|
}
|
|
377
397
|
async function hydrateState(db, state, defaultTimeoutSec, maxLogChunksPerTask) {
|
|
378
398
|
const employeeRows = await db.all("SELECT payload_json FROM employees");
|
|
@@ -609,6 +629,90 @@ function taskToDbValues(task) {
|
|
|
609
629
|
function toSnakeCase(str) {
|
|
610
630
|
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
611
631
|
}
|
|
632
|
+
var SCHEDULE_COLUMNS = [
|
|
633
|
+
"id",
|
|
634
|
+
"name",
|
|
635
|
+
"cron_expr",
|
|
636
|
+
"enabled",
|
|
637
|
+
"target_mode",
|
|
638
|
+
"target_agents",
|
|
639
|
+
"prompt",
|
|
640
|
+
"workspace",
|
|
641
|
+
"timeout_sec",
|
|
642
|
+
"priority",
|
|
643
|
+
"required_labels",
|
|
644
|
+
"last_run_at",
|
|
645
|
+
"next_run_at",
|
|
646
|
+
"created_at",
|
|
647
|
+
"updated_at"
|
|
648
|
+
];
|
|
649
|
+
var SCHEDULE_PLACEHOLDERS = SCHEDULE_COLUMNS.map((_, i) => `$${i + 1}`).join(", ");
|
|
650
|
+
var SCHEDULE_UPDATE_SET = SCHEDULE_COLUMNS.slice(1).map((col) => `${col} = excluded.${col}`).join(", ");
|
|
651
|
+
function dbRowToSchedule(row) {
|
|
652
|
+
return {
|
|
653
|
+
id: row.id,
|
|
654
|
+
name: row.name,
|
|
655
|
+
cronExpr: row.cron_expr,
|
|
656
|
+
enabled: row.enabled === 1,
|
|
657
|
+
targetMode: row.target_mode,
|
|
658
|
+
targetAgents: JSON.parse(row.target_agents || "[]"),
|
|
659
|
+
prompt: row.prompt,
|
|
660
|
+
workspace: row.workspace,
|
|
661
|
+
timeoutSec: row.timeout_sec,
|
|
662
|
+
priority: row.priority,
|
|
663
|
+
requiredLabels: row.required_labels ? JSON.parse(row.required_labels) : null,
|
|
664
|
+
lastRunAt: row.last_run_at,
|
|
665
|
+
nextRunAt: row.next_run_at,
|
|
666
|
+
createdAt: row.created_at,
|
|
667
|
+
updatedAt: row.updated_at
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
function scheduleToDbValues(s) {
|
|
671
|
+
return [
|
|
672
|
+
s.id,
|
|
673
|
+
s.name,
|
|
674
|
+
s.cronExpr,
|
|
675
|
+
s.enabled ? 1 : 0,
|
|
676
|
+
s.targetMode,
|
|
677
|
+
JSON.stringify(s.targetAgents),
|
|
678
|
+
s.prompt,
|
|
679
|
+
s.workspace,
|
|
680
|
+
s.timeoutSec,
|
|
681
|
+
s.priority,
|
|
682
|
+
s.requiredLabels ? JSON.stringify(s.requiredLabels) : null,
|
|
683
|
+
s.lastRunAt,
|
|
684
|
+
s.nextRunAt,
|
|
685
|
+
s.createdAt,
|
|
686
|
+
s.updatedAt
|
|
687
|
+
];
|
|
688
|
+
}
|
|
689
|
+
async function upsertSchedule(db, schedule) {
|
|
690
|
+
await db.run(
|
|
691
|
+
`INSERT INTO schedules (${SCHEDULE_COLUMNS.join(", ")}) VALUES (${SCHEDULE_PLACEHOLDERS}) ON CONFLICT(id) DO UPDATE SET ${SCHEDULE_UPDATE_SET}`,
|
|
692
|
+
scheduleToDbValues(schedule)
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
async function getAllSchedules(db) {
|
|
696
|
+
const rows = await db.all("SELECT * FROM schedules ORDER BY created_at");
|
|
697
|
+
return rows.map(dbRowToSchedule);
|
|
698
|
+
}
|
|
699
|
+
async function getScheduleById(db, id) {
|
|
700
|
+
const row = await db.get("SELECT * FROM schedules WHERE id = $1", [id]);
|
|
701
|
+
return row ? dbRowToSchedule(row) : void 0;
|
|
702
|
+
}
|
|
703
|
+
async function deleteScheduleRow(db, id) {
|
|
704
|
+
const row = await db.get("SELECT id FROM schedules WHERE id = $1", [id]);
|
|
705
|
+
if (!row) return false;
|
|
706
|
+
await db.run("DELETE FROM schedules WHERE id = $1", [id]);
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
async function updateScheduleFields(db, id, fields) {
|
|
710
|
+
const existing = await getScheduleById(db, id);
|
|
711
|
+
if (!existing) return void 0;
|
|
712
|
+
const updated = { ...existing, ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
713
|
+
await upsertSchedule(db, updated);
|
|
714
|
+
return updated;
|
|
715
|
+
}
|
|
612
716
|
|
|
613
717
|
// src/schemas.ts
|
|
614
718
|
var errorResponseSchema = {
|
|
@@ -890,6 +994,65 @@ function parseWebhookUrl(value) {
|
|
|
890
994
|
}
|
|
891
995
|
return url.toString();
|
|
892
996
|
}
|
|
997
|
+
var createScheduleRequestSchema = {
|
|
998
|
+
type: "object",
|
|
999
|
+
required: ["name", "cron", "prompt"],
|
|
1000
|
+
properties: {
|
|
1001
|
+
name: { type: "string", minLength: 1 },
|
|
1002
|
+
cron: { type: "string", minLength: 9 },
|
|
1003
|
+
enabled: { type: "boolean", default: true },
|
|
1004
|
+
targetMode: { type: "string", enum: ["queue", "direct", "broadcast"], default: "queue" },
|
|
1005
|
+
targetAgents: { type: "array", items: { type: "string" } },
|
|
1006
|
+
prompt: { type: "string", minLength: 1 },
|
|
1007
|
+
workspace: { type: "string" },
|
|
1008
|
+
timeoutSec: { type: "number", minimum: 1 },
|
|
1009
|
+
priority: { type: "integer", minimum: 0, maximum: 3, default: 0 },
|
|
1010
|
+
requiredLabels: { type: "array", items: { type: "string" } }
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
var updateScheduleRequestSchema = {
|
|
1014
|
+
type: "object",
|
|
1015
|
+
properties: {
|
|
1016
|
+
name: { type: "string", minLength: 1 },
|
|
1017
|
+
cron: { type: "string", minLength: 9 },
|
|
1018
|
+
enabled: { type: "boolean" },
|
|
1019
|
+
targetMode: { type: "string", enum: ["queue", "direct", "broadcast"] },
|
|
1020
|
+
targetAgents: { type: "array", items: { type: "string" } },
|
|
1021
|
+
prompt: { type: "string", minLength: 1 },
|
|
1022
|
+
workspace: { type: "string" },
|
|
1023
|
+
timeoutSec: { type: "number", minimum: 1 },
|
|
1024
|
+
priority: { type: "integer", minimum: 0, maximum: 3 },
|
|
1025
|
+
requiredLabels: { type: "array", items: { type: "string" } }
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
var scheduleResponseSchema = {
|
|
1029
|
+
type: "object",
|
|
1030
|
+
required: ["id", "name", "cron", "enabled", "targetMode", "prompt", "createdAt", "updatedAt"],
|
|
1031
|
+
properties: {
|
|
1032
|
+
id: { type: "string" },
|
|
1033
|
+
name: { type: "string" },
|
|
1034
|
+
cron: { type: "string" },
|
|
1035
|
+
enabled: { type: "boolean" },
|
|
1036
|
+
targetMode: { type: "string", enum: ["queue", "direct", "broadcast"] },
|
|
1037
|
+
targetAgents: { type: "array", items: { type: "string" } },
|
|
1038
|
+
prompt: { type: "string" },
|
|
1039
|
+
workspace: { type: "string" },
|
|
1040
|
+
timeoutSec: { type: "number" },
|
|
1041
|
+
priority: { type: "integer" },
|
|
1042
|
+
requiredLabels: { type: "array", items: { type: "string" } },
|
|
1043
|
+
lastRunAt: { type: "string" },
|
|
1044
|
+
nextRunAt: { type: "string" },
|
|
1045
|
+
createdAt: { type: "string" },
|
|
1046
|
+
updatedAt: { type: "string" }
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
var scheduleListResponseSchema = {
|
|
1050
|
+
type: "object",
|
|
1051
|
+
required: ["schedules"],
|
|
1052
|
+
properties: {
|
|
1053
|
+
schedules: { type: "array", items: scheduleResponseSchema }
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
893
1056
|
|
|
894
1057
|
// src/dispatch.ts
|
|
895
1058
|
import { createHmac, randomUUID } from "node:crypto";
|
|
@@ -1655,7 +1818,8 @@ function createInMemoryStateStore() {
|
|
|
1655
1818
|
taskQueues: /* @__PURE__ */ new Map(),
|
|
1656
1819
|
mainTaskQueues: /* @__PURE__ */ new Map(),
|
|
1657
1820
|
sharedTaskQueue: [],
|
|
1658
|
-
sharedQueueCursor: 0
|
|
1821
|
+
sharedQueueCursor: 0,
|
|
1822
|
+
scheduleJobs: /* @__PURE__ */ new Map()
|
|
1659
1823
|
};
|
|
1660
1824
|
}
|
|
1661
1825
|
|
|
@@ -1705,6 +1869,41 @@ function createEncryptor(encryptionKeyHex) {
|
|
|
1705
1869
|
};
|
|
1706
1870
|
}
|
|
1707
1871
|
|
|
1872
|
+
// src/scheduler.ts
|
|
1873
|
+
import { CronJob } from "cron";
|
|
1874
|
+
function startScheduleJob(schedule, dispatchFn, jobs, onFire) {
|
|
1875
|
+
const atAgents = schedule.targetMode === "queue" ? "queue" : schedule.targetMode === "broadcast" ? "all" : schedule.targetAgents;
|
|
1876
|
+
const job = new CronJob(
|
|
1877
|
+
schedule.cronExpr,
|
|
1878
|
+
() => {
|
|
1879
|
+
dispatchFn(
|
|
1880
|
+
{
|
|
1881
|
+
type: "command.dispatch",
|
|
1882
|
+
atAgents,
|
|
1883
|
+
prompt: schedule.prompt,
|
|
1884
|
+
workspace: schedule.workspace ?? void 0,
|
|
1885
|
+
timeoutSec: schedule.timeoutSec ?? void 0
|
|
1886
|
+
},
|
|
1887
|
+
null,
|
|
1888
|
+
void 0,
|
|
1889
|
+
schedule.priority,
|
|
1890
|
+
schedule.requiredLabels
|
|
1891
|
+
);
|
|
1892
|
+
onFire(schedule.id);
|
|
1893
|
+
},
|
|
1894
|
+
null,
|
|
1895
|
+
true
|
|
1896
|
+
);
|
|
1897
|
+
jobs.set(schedule.id, job);
|
|
1898
|
+
}
|
|
1899
|
+
function stopScheduleJob(jobs, scheduleId) {
|
|
1900
|
+
const job = jobs.get(scheduleId);
|
|
1901
|
+
if (job) {
|
|
1902
|
+
job.stop();
|
|
1903
|
+
jobs.delete(scheduleId);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1708
1907
|
// src/index.ts
|
|
1709
1908
|
var DEFAULT_PORT = 3789;
|
|
1710
1909
|
function isAuthorized(authToken, rawUrl, headers) {
|
|
@@ -1794,6 +1993,29 @@ async function createAiTeamsServer(options) {
|
|
|
1794
1993
|
encryptor: createEncryptor(process.env.AI_TEAMS_ENCRYPTION_KEY)
|
|
1795
1994
|
};
|
|
1796
1995
|
const { dispatchLeaderCommand, handleAgentMessage, handleLeaderMessage, cancelTaskById } = createDispatch(dispatchCtx);
|
|
1996
|
+
const scheduleDispatchFn = (message, webhookUrl, cliConfig, priority, requiredLabels) => {
|
|
1997
|
+
return dispatchLeaderCommand(message, webhookUrl, cliConfig, priority, requiredLabels);
|
|
1998
|
+
};
|
|
1999
|
+
async function onScheduleFire(scheduleId) {
|
|
2000
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2001
|
+
const job = state.scheduleJobs.get(scheduleId);
|
|
2002
|
+
const nextRun = job?.nextDate()?.toISO() ?? null;
|
|
2003
|
+
await updateScheduleFields(db, scheduleId, { lastRunAt: now, nextRunAt: nextRun });
|
|
2004
|
+
}
|
|
2005
|
+
async function loadAndStartSchedules() {
|
|
2006
|
+
const schedules = await getAllSchedules(db);
|
|
2007
|
+
for (const schedule of schedules) {
|
|
2008
|
+
if (schedule.enabled) {
|
|
2009
|
+
try {
|
|
2010
|
+
startScheduleJob(schedule, scheduleDispatchFn, state.scheduleJobs, onScheduleFire);
|
|
2011
|
+
} catch (err) {
|
|
2012
|
+
app.log.error({ scheduleId: schedule.id, cronExpr: schedule.cronExpr, err }, "Failed to start schedule");
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
app.log.info({ count: schedules.filter((s) => s.enabled).length }, "Schedules loaded");
|
|
2017
|
+
}
|
|
2018
|
+
await loadAndStartSchedules();
|
|
1797
2019
|
function buildSnapshot() {
|
|
1798
2020
|
return {
|
|
1799
2021
|
employees: [...state.employees.values()],
|
|
@@ -2204,6 +2426,163 @@ async function createAiTeamsServer(options) {
|
|
|
2204
2426
|
return { deleted: true };
|
|
2205
2427
|
}
|
|
2206
2428
|
);
|
|
2429
|
+
app.get(
|
|
2430
|
+
"/api/schedules",
|
|
2431
|
+
{
|
|
2432
|
+
schema: {
|
|
2433
|
+
tags: ["schedules"],
|
|
2434
|
+
summary: "List all schedules",
|
|
2435
|
+
response: { 200: scheduleListResponseSchema, 401: errorResponseSchema }
|
|
2436
|
+
}
|
|
2437
|
+
},
|
|
2438
|
+
async () => {
|
|
2439
|
+
const schedules = await getAllSchedules(db);
|
|
2440
|
+
return { schedules };
|
|
2441
|
+
}
|
|
2442
|
+
);
|
|
2443
|
+
app.get(
|
|
2444
|
+
"/api/schedules/:scheduleId",
|
|
2445
|
+
{
|
|
2446
|
+
schema: {
|
|
2447
|
+
tags: ["schedules"],
|
|
2448
|
+
summary: "Get a schedule by ID",
|
|
2449
|
+
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2450
|
+
response: { 200: scheduleResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2451
|
+
}
|
|
2452
|
+
},
|
|
2453
|
+
async (request, reply) => {
|
|
2454
|
+
const schedule = await getScheduleById(db, request.params.scheduleId);
|
|
2455
|
+
if (!schedule) return reply.code(404).send({ error: "Schedule not found." });
|
|
2456
|
+
return schedule;
|
|
2457
|
+
}
|
|
2458
|
+
);
|
|
2459
|
+
app.post(
|
|
2460
|
+
"/api/schedules",
|
|
2461
|
+
{
|
|
2462
|
+
schema: {
|
|
2463
|
+
tags: ["schedules"],
|
|
2464
|
+
summary: "Create a schedule",
|
|
2465
|
+
body: createScheduleRequestSchema,
|
|
2466
|
+
response: { 201: scheduleResponseSchema, 400: errorResponseSchema, 401: errorResponseSchema }
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
async (request, reply) => {
|
|
2470
|
+
const body = request.body;
|
|
2471
|
+
const id = randomUUID2();
|
|
2472
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2473
|
+
const schedule = {
|
|
2474
|
+
id,
|
|
2475
|
+
name: body.name,
|
|
2476
|
+
cronExpr: body.cron,
|
|
2477
|
+
enabled: body.enabled ?? true,
|
|
2478
|
+
targetMode: body.targetMode ?? "queue",
|
|
2479
|
+
targetAgents: body.targetAgents ?? [],
|
|
2480
|
+
prompt: body.prompt,
|
|
2481
|
+
workspace: body.workspace ?? null,
|
|
2482
|
+
timeoutSec: body.timeoutSec ?? null,
|
|
2483
|
+
priority: body.priority ?? 0,
|
|
2484
|
+
requiredLabels: body.requiredLabels ?? null,
|
|
2485
|
+
lastRunAt: null,
|
|
2486
|
+
nextRunAt: null,
|
|
2487
|
+
createdAt: now,
|
|
2488
|
+
updatedAt: now
|
|
2489
|
+
};
|
|
2490
|
+
try {
|
|
2491
|
+
if (schedule.enabled) {
|
|
2492
|
+
startScheduleJob(schedule, scheduleDispatchFn, state.scheduleJobs, onScheduleFire);
|
|
2493
|
+
schedule.nextRunAt = state.scheduleJobs.get(id)?.nextDate()?.toISO() ?? null;
|
|
2494
|
+
}
|
|
2495
|
+
await upsertSchedule(db, schedule);
|
|
2496
|
+
return reply.code(201).send(schedule);
|
|
2497
|
+
} catch (err) {
|
|
2498
|
+
stopScheduleJob(state.scheduleJobs, id);
|
|
2499
|
+
return reply.code(400).send({ error: err instanceof Error ? err.message : "Invalid schedule." });
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
);
|
|
2503
|
+
app.patch(
|
|
2504
|
+
"/api/schedules/:scheduleId",
|
|
2505
|
+
{
|
|
2506
|
+
schema: {
|
|
2507
|
+
tags: ["schedules"],
|
|
2508
|
+
summary: "Update a schedule",
|
|
2509
|
+
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2510
|
+
body: updateScheduleRequestSchema,
|
|
2511
|
+
response: { 200: scheduleResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2512
|
+
}
|
|
2513
|
+
},
|
|
2514
|
+
async (request, reply) => {
|
|
2515
|
+
const existing = await getScheduleById(db, request.params.scheduleId);
|
|
2516
|
+
if (!existing) return reply.code(404).send({ error: "Schedule not found." });
|
|
2517
|
+
const fields = {};
|
|
2518
|
+
if (request.body.name !== void 0) fields.name = request.body.name;
|
|
2519
|
+
if (request.body.cron !== void 0) fields.cronExpr = request.body.cron;
|
|
2520
|
+
if (request.body.enabled !== void 0) fields.enabled = request.body.enabled;
|
|
2521
|
+
if (request.body.targetMode !== void 0) fields.targetMode = request.body.targetMode;
|
|
2522
|
+
if (request.body.targetAgents !== void 0) fields.targetAgents = request.body.targetAgents;
|
|
2523
|
+
if (request.body.prompt !== void 0) fields.prompt = request.body.prompt;
|
|
2524
|
+
if (request.body.workspace !== void 0) fields.workspace = request.body.workspace;
|
|
2525
|
+
if (request.body.timeoutSec !== void 0) fields.timeoutSec = request.body.timeoutSec;
|
|
2526
|
+
if (request.body.priority !== void 0) fields.priority = request.body.priority;
|
|
2527
|
+
if (request.body.requiredLabels !== void 0) fields.requiredLabels = request.body.requiredLabels;
|
|
2528
|
+
const updated = await updateScheduleFields(db, request.params.scheduleId, fields);
|
|
2529
|
+
if (!updated) return reply.code(404).send({ error: "Schedule not found." });
|
|
2530
|
+
stopScheduleJob(state.scheduleJobs, request.params.scheduleId);
|
|
2531
|
+
if (updated.enabled) {
|
|
2532
|
+
startScheduleJob(updated, scheduleDispatchFn, state.scheduleJobs, onScheduleFire);
|
|
2533
|
+
updated.nextRunAt = state.scheduleJobs.get(request.params.scheduleId)?.nextDate()?.toISO() ?? null;
|
|
2534
|
+
await updateScheduleFields(db, request.params.scheduleId, { nextRunAt: updated.nextRunAt });
|
|
2535
|
+
}
|
|
2536
|
+
return updated;
|
|
2537
|
+
}
|
|
2538
|
+
);
|
|
2539
|
+
app.delete(
|
|
2540
|
+
"/api/schedules/:scheduleId",
|
|
2541
|
+
{
|
|
2542
|
+
schema: {
|
|
2543
|
+
tags: ["schedules"],
|
|
2544
|
+
summary: "Delete a schedule",
|
|
2545
|
+
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2546
|
+
response: {
|
|
2547
|
+
200: { type: "object", required: ["deleted"], properties: { deleted: { type: "boolean" } } },
|
|
2548
|
+
404: errorResponseSchema,
|
|
2549
|
+
401: errorResponseSchema
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
},
|
|
2553
|
+
async (request, reply) => {
|
|
2554
|
+
stopScheduleJob(state.scheduleJobs, request.params.scheduleId);
|
|
2555
|
+
const deleted = await deleteScheduleRow(db, request.params.scheduleId);
|
|
2556
|
+
if (!deleted) return reply.code(404).send({ error: "Schedule not found." });
|
|
2557
|
+
return { deleted: true };
|
|
2558
|
+
}
|
|
2559
|
+
);
|
|
2560
|
+
app.post(
|
|
2561
|
+
"/api/schedules/:scheduleId/trigger",
|
|
2562
|
+
{
|
|
2563
|
+
schema: {
|
|
2564
|
+
tags: ["schedules"],
|
|
2565
|
+
summary: "Manually trigger a schedule",
|
|
2566
|
+
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2567
|
+
response: { 200: scheduleResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2568
|
+
}
|
|
2569
|
+
},
|
|
2570
|
+
async (request, reply) => {
|
|
2571
|
+
const schedule = await getScheduleById(db, request.params.scheduleId);
|
|
2572
|
+
if (!schedule) return reply.code(404).send({ error: "Schedule not found." });
|
|
2573
|
+
const atAgents = schedule.targetMode === "queue" ? "queue" : schedule.targetMode === "broadcast" ? "all" : schedule.targetAgents;
|
|
2574
|
+
const result = dispatchLeaderCommand(
|
|
2575
|
+
{ type: "command.dispatch", atAgents, prompt: schedule.prompt, workspace: schedule.workspace ?? void 0, timeoutSec: schedule.timeoutSec ?? void 0 },
|
|
2576
|
+
null,
|
|
2577
|
+
void 0,
|
|
2578
|
+
schedule.priority,
|
|
2579
|
+
schedule.requiredLabels
|
|
2580
|
+
);
|
|
2581
|
+
if (!result.ok) return reply.code(400).send({ error: result.message });
|
|
2582
|
+
await onScheduleFire(request.params.scheduleId);
|
|
2583
|
+
return await getScheduleById(db, request.params.scheduleId);
|
|
2584
|
+
}
|
|
2585
|
+
);
|
|
2207
2586
|
app.get("/ws/agent", { websocket: true }, (socket, request) => {
|
|
2208
2587
|
if (!isAuthorized(options.authToken, request.url, request.headers)) {
|
|
2209
2588
|
socket.close(1008, "unauthorized");
|
|
@@ -2278,6 +2657,9 @@ async function createAiTeamsServer(options) {
|
|
|
2278
2657
|
buildSnapshot,
|
|
2279
2658
|
close: async () => {
|
|
2280
2659
|
closing = true;
|
|
2660
|
+
for (const job of state.scheduleJobs.values()) {
|
|
2661
|
+
job.stop();
|
|
2662
|
+
}
|
|
2281
2663
|
for (const timer of state.taskTimeouts.values()) {
|
|
2282
2664
|
clearTimeout(timer);
|
|
2283
2665
|
}
|
|
@@ -2321,7 +2703,7 @@ if (isCli) {
|
|
|
2321
2703
|
getArgValue2 = getArgValue;
|
|
2322
2704
|
const args = process.argv.slice(2);
|
|
2323
2705
|
if (args.includes("--version") || args.includes("-v")) {
|
|
2324
|
-
console.log("0.
|
|
2706
|
+
console.log("0.2.0");
|
|
2325
2707
|
process.exit(0);
|
|
2326
2708
|
}
|
|
2327
2709
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -2334,6 +2716,7 @@ if (isCli) {
|
|
|
2334
2716
|
--port <port> \u670D\u52A1\u7AEF\u53E3 (\u9ED8\u8BA4 3789)
|
|
2335
2717
|
--host <host> \u7ED1\u5B9A\u5730\u5740 (\u9ED8\u8BA4 0.0.0.0)
|
|
2336
2718
|
--data-dir <dir> \u6570\u636E\u76EE\u5F55
|
|
2719
|
+
--database-url <url> PostgreSQL \u8FDE\u63A5\u5B57\u7B26\u4E32 (\u8BBE\u7F6E\u540E\u4F7F\u7528 PostgreSQL \u800C\u975E SQLite)
|
|
2337
2720
|
--db-path <path> \u6570\u636E\u5E93\u8DEF\u5F84
|
|
2338
2721
|
--log-level <level> \u65E5\u5FD7\u7EA7\u522B trace/debug/info/warn/error (\u9ED8\u8BA4 info)
|
|
2339
2722
|
--log-dir <dir> \u65E5\u5FD7\u6587\u4EF6\u76EE\u5F55 (\u4E0D\u8BBE\u5219\u4EC5\u8F93\u51FA\u5230 stdout)
|
|
@@ -2346,6 +2729,7 @@ if (isCli) {
|
|
|
2346
2729
|
const cliPort = getArgValue("--port");
|
|
2347
2730
|
const cliHost = getArgValue("--host");
|
|
2348
2731
|
const cliDataDir = getArgValue("--data-dir");
|
|
2732
|
+
const cliDatabaseUrl = getArgValue("--database-url");
|
|
2349
2733
|
const cliDbPath = getArgValue("--db-path");
|
|
2350
2734
|
const cliLogLevel = getArgValue("--log-level");
|
|
2351
2735
|
const cliLogDir = getArgValue("--log-dir");
|
|
@@ -2353,6 +2737,7 @@ if (isCli) {
|
|
|
2353
2737
|
if (cliPort) process.env.AI_TEAMS_SERVER_PORT = cliPort;
|
|
2354
2738
|
if (cliHost) process.env.HOST = cliHost;
|
|
2355
2739
|
if (cliDataDir) process.env.DATA_DIR = cliDataDir;
|
|
2740
|
+
if (cliDatabaseUrl) process.env.DATABASE_URL = cliDatabaseUrl;
|
|
2356
2741
|
if (cliDbPath) process.env.DB_PATH = cliDbPath;
|
|
2357
2742
|
if (cliLogLevel) process.env.LOG_LEVEL = cliLogLevel;
|
|
2358
2743
|
if (cliLogDir) process.env.LOG_DIR = cliLogDir;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@csdwd/ai-teams-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "AI Teams central server — Fastify HTTP + WebSocket server for task dispatch, employee management, and web UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"@fastify/swagger": "^9.7.0",
|
|
21
21
|
"@fastify/swagger-ui": "^5.2.5",
|
|
22
22
|
"@fastify/websocket": "^11.0.0",
|
|
23
|
+
"cron": "^4.4.0",
|
|
23
24
|
"fastify": "^5.2.0",
|
|
24
25
|
"pg": "^8.20.0",
|
|
25
26
|
"ws": "^8.18.3"
|