@csdwd/ai-teams-server 0.3.3 → 0.3.5
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
CHANGED
|
@@ -36,7 +36,9 @@ function parseLeaderToServerMessage(value) {
|
|
|
36
36
|
atAgents: agentTargetField(message, "atAgents"),
|
|
37
37
|
prompt: nonEmptyStringField(message, "prompt"),
|
|
38
38
|
workspace: optionalStringField(message, "workspace"),
|
|
39
|
-
timeoutSec: optionalPositiveNumberField(message, "timeoutSec")
|
|
39
|
+
timeoutSec: optionalPositiveNumberField(message, "timeoutSec"),
|
|
40
|
+
priority: optionalNonNegativeNumberField(message, "priority"),
|
|
41
|
+
requiredLabels: optionalStringArrayField(message, "requiredLabels")
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
if (type === "command.send") {
|
|
@@ -175,6 +177,15 @@ function stringArrayField(record, key) {
|
|
|
175
177
|
}
|
|
176
178
|
return value;
|
|
177
179
|
}
|
|
180
|
+
function optionalStringArrayField(record, key) {
|
|
181
|
+
const value = record[key];
|
|
182
|
+
if (value === void 0)
|
|
183
|
+
return void 0;
|
|
184
|
+
if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
|
|
185
|
+
throw new ProtocolError(`${key} must be a string array.`);
|
|
186
|
+
}
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
178
189
|
function agentTargetField(record, key) {
|
|
179
190
|
const value = record[key];
|
|
180
191
|
if (value === "queue") {
|
|
@@ -508,7 +519,8 @@ async function initDb(db) {
|
|
|
508
519
|
usage_input_tokens INTEGER,
|
|
509
520
|
usage_output_tokens INTEGER,
|
|
510
521
|
usage_cache_read_tokens INTEGER,
|
|
511
|
-
usage_cache_creation_tokens INTEGER
|
|
522
|
+
usage_cache_creation_tokens INTEGER,
|
|
523
|
+
retry_count INTEGER NOT NULL DEFAULT 0
|
|
512
524
|
)
|
|
513
525
|
`);
|
|
514
526
|
await db.run(`
|
|
@@ -559,6 +571,10 @@ async function initDb(db) {
|
|
|
559
571
|
updated_at TEXT NOT NULL
|
|
560
572
|
)
|
|
561
573
|
`);
|
|
574
|
+
try {
|
|
575
|
+
await db.run("ALTER TABLE tasks ADD COLUMN retry_count INTEGER NOT NULL DEFAULT 0");
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
562
578
|
}
|
|
563
579
|
async function hydrateState(db, state, defaultTimeoutSec, maxLogChunksPerTask) {
|
|
564
580
|
const employeeRows = await db.all("SELECT payload_json FROM employees");
|
|
@@ -575,13 +591,13 @@ async function hydrateState(db, state, defaultTimeoutSec, maxLogChunksPerTask) {
|
|
|
575
591
|
if (task.targetMode === "queue" || !task.employeeId) {
|
|
576
592
|
state.sharedTaskQueue.push(task.id);
|
|
577
593
|
} else {
|
|
578
|
-
const queue = state.
|
|
594
|
+
const queue = state.mainTaskQueues.get(task.employeeId) ?? [];
|
|
579
595
|
queue.push(task.id);
|
|
580
|
-
state.
|
|
596
|
+
state.mainTaskQueues.set(task.employeeId, queue);
|
|
581
597
|
}
|
|
582
598
|
} else if (task.employeeId && !TERMINAL_STATUSES.has(task.status)) {
|
|
583
599
|
task.status = "queued";
|
|
584
|
-
persistTask(db, task);
|
|
600
|
+
await persistTask(db, task);
|
|
585
601
|
if (task.targetMode === "queue") {
|
|
586
602
|
state.sharedTaskQueue.push(task.id);
|
|
587
603
|
} else {
|
|
@@ -637,6 +653,7 @@ var TASK_COLUMNS = [
|
|
|
637
653
|
"cli_config",
|
|
638
654
|
"priority",
|
|
639
655
|
"required_labels",
|
|
656
|
+
"retry_count",
|
|
640
657
|
"created_at",
|
|
641
658
|
"started_at",
|
|
642
659
|
"finished_at",
|
|
@@ -731,11 +748,38 @@ async function deleteTask(db, taskId) {
|
|
|
731
748
|
await db.run("DELETE FROM tasks WHERE id = $1", [taskId]);
|
|
732
749
|
return true;
|
|
733
750
|
}
|
|
751
|
+
var UPDATABLE_TASK_FIELDS = /* @__PURE__ */ new Set([
|
|
752
|
+
"status",
|
|
753
|
+
"timeoutSec",
|
|
754
|
+
"cliConfig",
|
|
755
|
+
"priority",
|
|
756
|
+
"requiredLabels",
|
|
757
|
+
"prompt",
|
|
758
|
+
"workspace",
|
|
759
|
+
"employeeId",
|
|
760
|
+
"sessionId",
|
|
761
|
+
"exitCode",
|
|
762
|
+
"summary",
|
|
763
|
+
"error",
|
|
764
|
+
"durationMs",
|
|
765
|
+
"durationApiMs",
|
|
766
|
+
"numTurns",
|
|
767
|
+
"totalCostUsd",
|
|
768
|
+
"usageInputTokens",
|
|
769
|
+
"usageOutputTokens",
|
|
770
|
+
"usageCacheReadTokens",
|
|
771
|
+
"usageCacheCreationTokens"
|
|
772
|
+
]);
|
|
734
773
|
async function updateTaskFields(db, taskId, fields) {
|
|
735
|
-
const entries = Object.entries(fields);
|
|
774
|
+
const entries = Object.entries(fields).filter(([key]) => UPDATABLE_TASK_FIELDS.has(key));
|
|
736
775
|
if (entries.length === 0) return getTaskById(db, taskId);
|
|
737
776
|
const setClauses = entries.map(([key], i) => `${toSnakeCase(key)} = $${i + 2}`).join(", ");
|
|
738
|
-
const values = entries.map(([, val]) =>
|
|
777
|
+
const values = entries.map(([key, val]) => {
|
|
778
|
+
if ((key === "cliConfig" || key === "requiredLabels") && val != null) {
|
|
779
|
+
return JSON.stringify(val);
|
|
780
|
+
}
|
|
781
|
+
return val;
|
|
782
|
+
});
|
|
739
783
|
await db.run(
|
|
740
784
|
`UPDATE tasks SET ${setClauses} WHERE id = $1`,
|
|
741
785
|
[taskId, ...values]
|
|
@@ -756,6 +800,7 @@ function dbRowToTask(row, defaultTimeoutSec) {
|
|
|
756
800
|
priority: row.priority ?? 1,
|
|
757
801
|
requiredLabels: row.required_labels ? JSON.parse(row.required_labels) : null,
|
|
758
802
|
status: row.status || "queued",
|
|
803
|
+
retryCount: row.retry_count ?? 0,
|
|
759
804
|
createdAt: row.created_at,
|
|
760
805
|
startedAt: row.started_at,
|
|
761
806
|
finishedAt: row.finished_at,
|
|
@@ -786,6 +831,7 @@ function taskToDbValues(task) {
|
|
|
786
831
|
task.cliConfig ? JSON.stringify(task.cliConfig) : null,
|
|
787
832
|
task.priority,
|
|
788
833
|
task.requiredLabels ? JSON.stringify(task.requiredLabels) : null,
|
|
834
|
+
task.retryCount,
|
|
789
835
|
task.createdAt,
|
|
790
836
|
task.startedAt,
|
|
791
837
|
task.finishedAt,
|
|
@@ -1212,12 +1258,12 @@ var scheduleResponseSchema = {
|
|
|
1212
1258
|
targetMode: { type: "string", enum: ["queue", "direct", "broadcast"] },
|
|
1213
1259
|
targetAgents: { type: "array", items: { type: "string" } },
|
|
1214
1260
|
prompt: { type: "string" },
|
|
1215
|
-
workspace:
|
|
1216
|
-
timeoutSec:
|
|
1261
|
+
workspace: nullableString,
|
|
1262
|
+
timeoutSec: nullableNumber,
|
|
1217
1263
|
priority: { type: "integer" },
|
|
1218
|
-
requiredLabels: { type: "array", items: { type: "string" } },
|
|
1219
|
-
lastRunAt:
|
|
1220
|
-
nextRunAt:
|
|
1264
|
+
requiredLabels: { anyOf: [{ type: "array", items: { type: "string" } }, { type: "null" }] },
|
|
1265
|
+
lastRunAt: nullableString,
|
|
1266
|
+
nextRunAt: nullableString,
|
|
1221
1267
|
createdAt: { type: "string" },
|
|
1222
1268
|
updatedAt: { type: "string" }
|
|
1223
1269
|
}
|
|
@@ -1248,6 +1294,7 @@ function extractDoneSessionId(content) {
|
|
|
1248
1294
|
}
|
|
1249
1295
|
function createDispatch(ctx) {
|
|
1250
1296
|
const { state, db, log } = ctx;
|
|
1297
|
+
const retryDelays = /* @__PURE__ */ new Map();
|
|
1251
1298
|
function broadcastToLeaders(payload) {
|
|
1252
1299
|
for (const socket of state.leaderSockets) {
|
|
1253
1300
|
sendJson(socket, payload, ctx.encryptor);
|
|
@@ -1515,13 +1562,14 @@ function createDispatch(ctx) {
|
|
|
1515
1562
|
if (!employee || employee.queueTaskId || employee.status !== "online") {
|
|
1516
1563
|
return;
|
|
1517
1564
|
}
|
|
1518
|
-
|
|
1519
|
-
const taskId = state.sharedTaskQueue
|
|
1565
|
+
for (let i = 0; i < state.sharedTaskQueue.length; i++) {
|
|
1566
|
+
const taskId = state.sharedTaskQueue[i];
|
|
1520
1567
|
const task = state.tasks.get(taskId);
|
|
1521
|
-
if (task
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1568
|
+
if (!task || task.status !== "queued" || task.targetMode !== "queue") continue;
|
|
1569
|
+
if (retryDelays.has(taskId)) continue;
|
|
1570
|
+
state.sharedTaskQueue.splice(i, 1);
|
|
1571
|
+
dispatchTask(task, employeeId);
|
|
1572
|
+
return;
|
|
1525
1573
|
}
|
|
1526
1574
|
}
|
|
1527
1575
|
function dispatchSharedQueuedTasks() {
|
|
@@ -1639,6 +1687,7 @@ function createDispatch(ctx) {
|
|
|
1639
1687
|
priority: priority ?? 1,
|
|
1640
1688
|
requiredLabels: requiredLabels ?? null,
|
|
1641
1689
|
status: "queued",
|
|
1690
|
+
retryCount: 0,
|
|
1642
1691
|
createdAt: nowIso(),
|
|
1643
1692
|
startedAt: null,
|
|
1644
1693
|
finishedAt: null,
|
|
@@ -1679,11 +1728,13 @@ function createDispatch(ctx) {
|
|
|
1679
1728
|
}
|
|
1680
1729
|
function dispatchLeaderCommand(message, webhookUrl, cliConfig, priority, requiredLabels) {
|
|
1681
1730
|
const leaderCommandId = randomUUID();
|
|
1731
|
+
const resolvedPriority = message.priority ?? priority;
|
|
1732
|
+
const resolvedRequiredLabels = message.requiredLabels ?? requiredLabels;
|
|
1682
1733
|
if (message.atAgents === "queue") {
|
|
1683
1734
|
return {
|
|
1684
1735
|
ok: true,
|
|
1685
1736
|
leaderCommandId,
|
|
1686
|
-
tasks: [createTask(null, message.prompt, message.workspace, message.timeoutSec, leaderCommandId, "queue", webhookUrl, cliConfig,
|
|
1737
|
+
tasks: [createTask(null, message.prompt, message.workspace, message.timeoutSec, leaderCommandId, "queue", webhookUrl, cliConfig, resolvedPriority, resolvedRequiredLabels)]
|
|
1687
1738
|
};
|
|
1688
1739
|
}
|
|
1689
1740
|
const targetIds = resolveTargetIds(message.atAgents);
|
|
@@ -1699,7 +1750,7 @@ function createDispatch(ctx) {
|
|
|
1699
1750
|
ok: true,
|
|
1700
1751
|
leaderCommandId,
|
|
1701
1752
|
tasks: targetIds.map(
|
|
1702
|
-
(employeeId) => createTask(employeeId, message.prompt, message.workspace, message.timeoutSec, leaderCommandId, targetMode, webhookUrl, cliConfig,
|
|
1753
|
+
(employeeId) => createTask(employeeId, message.prompt, message.workspace, message.timeoutSec, leaderCommandId, targetMode, webhookUrl, cliConfig, resolvedPriority, resolvedRequiredLabels)
|
|
1703
1754
|
)
|
|
1704
1755
|
};
|
|
1705
1756
|
}
|
|
@@ -1714,6 +1765,15 @@ function createDispatch(ctx) {
|
|
|
1714
1765
|
clearTimeout(disconnectTimer);
|
|
1715
1766
|
state.disconnectTimers.delete(message.employeeId);
|
|
1716
1767
|
}
|
|
1768
|
+
for (const prevTaskId of [previousMainTaskId, previousQueueTaskId]) {
|
|
1769
|
+
if (prevTaskId) {
|
|
1770
|
+
const timer = retryDelays.get(prevTaskId);
|
|
1771
|
+
if (timer) {
|
|
1772
|
+
clearTimeout(timer);
|
|
1773
|
+
retryDelays.delete(prevTaskId);
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1717
1777
|
state.agentSockets.set(message.employeeId, socket);
|
|
1718
1778
|
state.socketToEmployeeId.set(socket, message.employeeId);
|
|
1719
1779
|
let mainTaskId = null;
|
|
@@ -1757,6 +1817,7 @@ function createDispatch(ctx) {
|
|
|
1757
1817
|
if (!queueTaskId) {
|
|
1758
1818
|
dispatchNextQueuedTask(message.employeeId);
|
|
1759
1819
|
}
|
|
1820
|
+
dispatchSharedQueuedTasks();
|
|
1760
1821
|
resetHeartbeatTimer(message.employeeId);
|
|
1761
1822
|
}
|
|
1762
1823
|
function taskBelongsToSocket(taskId, employeeId, socket) {
|
|
@@ -1983,10 +2044,19 @@ function createDispatch(ctx) {
|
|
|
1983
2044
|
const task = state.tasks.get(taskId);
|
|
1984
2045
|
if (!task || TERMINAL_STATUSES.has(task.status)) continue;
|
|
1985
2046
|
clearTaskTimeout(taskId);
|
|
1986
|
-
task.status = "queued";
|
|
1987
2047
|
task.employeeId = null;
|
|
2048
|
+
task.retryCount += 1;
|
|
2049
|
+
if (task.retryCount > 3) {
|
|
2050
|
+
task.status = "failed";
|
|
2051
|
+
task.error = `\u4EFB\u52A1\u91CD\u8BD5\u6B21\u6570\u8D85\u8FC7\u4E0A\u9650\uFF083\u6B21\uFF09\uFF0C\u5DF2\u7EC8\u6B62\u3002`;
|
|
2052
|
+
task.finishedAt = nowIso();
|
|
2053
|
+
upsertTask(task);
|
|
2054
|
+
log.warn({ taskId, employeeId, retryCount: task.retryCount }, "Task exceeded max retry count, marked failed");
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
task.status = "queued";
|
|
1988
2058
|
upsertTask(task);
|
|
1989
|
-
log.info({ taskId, employeeId }, "Task re-queued after disconnect grace period");
|
|
2059
|
+
log.info({ taskId, employeeId, retryCount: task.retryCount }, "Task re-queued after disconnect grace period");
|
|
1990
2060
|
if (task.targetMode === "queue") {
|
|
1991
2061
|
enqueueSharedTask(taskId);
|
|
1992
2062
|
} else {
|
|
@@ -1994,6 +2064,16 @@ function createDispatch(ctx) {
|
|
|
1994
2064
|
if (!queue.includes(taskId)) queue.push(taskId);
|
|
1995
2065
|
state.mainTaskQueues.set(employeeId, queue);
|
|
1996
2066
|
}
|
|
2067
|
+
if (task.retryCount > 1) {
|
|
2068
|
+
const retryDelay = (task.retryCount - 1) * 5e3;
|
|
2069
|
+
const existingDelay = retryDelays.get(taskId);
|
|
2070
|
+
if (existingDelay) clearTimeout(existingDelay);
|
|
2071
|
+
retryDelays.set(taskId, setTimeout(() => {
|
|
2072
|
+
retryDelays.delete(taskId);
|
|
2073
|
+
dispatchSharedQueuedTasks();
|
|
2074
|
+
dispatchNextMainQueuedTask(employeeId);
|
|
2075
|
+
}, retryDelay));
|
|
2076
|
+
}
|
|
1997
2077
|
}
|
|
1998
2078
|
const emp = state.employees.get(employeeId);
|
|
1999
2079
|
if (emp) {
|
|
@@ -2014,7 +2094,13 @@ function createDispatch(ctx) {
|
|
|
2014
2094
|
handleAgentMessage,
|
|
2015
2095
|
handleLeaderMessage,
|
|
2016
2096
|
cancelTaskById,
|
|
2017
|
-
startDisconnectRecovery
|
|
2097
|
+
startDisconnectRecovery,
|
|
2098
|
+
cleanup() {
|
|
2099
|
+
for (const timer of retryDelays.values()) {
|
|
2100
|
+
clearTimeout(timer);
|
|
2101
|
+
}
|
|
2102
|
+
retryDelays.clear();
|
|
2103
|
+
}
|
|
2018
2104
|
};
|
|
2019
2105
|
}
|
|
2020
2106
|
|
|
@@ -2121,6 +2207,25 @@ function stopScheduleJob(jobs, scheduleId) {
|
|
|
2121
2207
|
}
|
|
2122
2208
|
|
|
2123
2209
|
// src/index.ts
|
|
2210
|
+
function scheduleToResponse(s) {
|
|
2211
|
+
return {
|
|
2212
|
+
id: s.id,
|
|
2213
|
+
name: s.name,
|
|
2214
|
+
cron: s.cronExpr,
|
|
2215
|
+
enabled: s.enabled,
|
|
2216
|
+
targetMode: s.targetMode,
|
|
2217
|
+
targetAgents: s.targetAgents,
|
|
2218
|
+
prompt: s.prompt,
|
|
2219
|
+
workspace: s.workspace,
|
|
2220
|
+
timeoutSec: s.timeoutSec,
|
|
2221
|
+
priority: s.priority,
|
|
2222
|
+
requiredLabels: s.requiredLabels,
|
|
2223
|
+
lastRunAt: s.lastRunAt,
|
|
2224
|
+
nextRunAt: s.nextRunAt,
|
|
2225
|
+
createdAt: s.createdAt,
|
|
2226
|
+
updatedAt: s.updatedAt
|
|
2227
|
+
};
|
|
2228
|
+
}
|
|
2124
2229
|
var DEFAULT_PORT = 3789;
|
|
2125
2230
|
function isAuthorized(authToken, rawUrl, headers) {
|
|
2126
2231
|
const bearer = typeof headers.authorization === "string" ? headers.authorization : "";
|
|
@@ -2208,7 +2313,7 @@ async function createAiTeamsServer(options) {
|
|
|
2208
2313
|
disconnectGraceMs,
|
|
2209
2314
|
encryptor: createEncryptor(process.env.AI_TEAMS_ENCRYPTION_KEY)
|
|
2210
2315
|
};
|
|
2211
|
-
const { dispatchLeaderCommand, handleAgentMessage, handleLeaderMessage, cancelTaskById, startDisconnectRecovery } = createDispatch(dispatchCtx);
|
|
2316
|
+
const { dispatchLeaderCommand, handleAgentMessage, handleLeaderMessage, cancelTaskById, startDisconnectRecovery, cleanup: dispatchCleanup } = createDispatch(dispatchCtx);
|
|
2212
2317
|
const scheduleDispatchFn = (message, webhookUrl, cliConfig, priority, requiredLabels) => {
|
|
2213
2318
|
return dispatchLeaderCommand(message, webhookUrl, cliConfig, priority, requiredLabels);
|
|
2214
2319
|
};
|
|
@@ -2653,7 +2758,7 @@ async function createAiTeamsServer(options) {
|
|
|
2653
2758
|
},
|
|
2654
2759
|
async () => {
|
|
2655
2760
|
const schedules = await getAllSchedules(db);
|
|
2656
|
-
return { schedules };
|
|
2761
|
+
return { schedules: schedules.map(scheduleToResponse) };
|
|
2657
2762
|
}
|
|
2658
2763
|
);
|
|
2659
2764
|
app.get(
|
|
@@ -2669,7 +2774,7 @@ async function createAiTeamsServer(options) {
|
|
|
2669
2774
|
async (request, reply) => {
|
|
2670
2775
|
const schedule = await getScheduleById(db, request.params.scheduleId);
|
|
2671
2776
|
if (!schedule) return reply.code(404).send({ error: "Schedule not found." });
|
|
2672
|
-
return schedule;
|
|
2777
|
+
return scheduleToResponse(schedule);
|
|
2673
2778
|
}
|
|
2674
2779
|
);
|
|
2675
2780
|
app.post(
|
|
@@ -2709,7 +2814,7 @@ async function createAiTeamsServer(options) {
|
|
|
2709
2814
|
schedule.nextRunAt = state.scheduleJobs.get(id)?.nextDate()?.toISO() ?? null;
|
|
2710
2815
|
}
|
|
2711
2816
|
await upsertSchedule(db, schedule);
|
|
2712
|
-
return reply.code(201).send(schedule);
|
|
2817
|
+
return reply.code(201).send(scheduleToResponse(schedule));
|
|
2713
2818
|
} catch (err) {
|
|
2714
2819
|
stopScheduleJob(state.scheduleJobs, id);
|
|
2715
2820
|
return reply.code(400).send({ error: err instanceof Error ? err.message : "Invalid schedule." });
|
|
@@ -2724,7 +2829,7 @@ async function createAiTeamsServer(options) {
|
|
|
2724
2829
|
summary: "Update a schedule",
|
|
2725
2830
|
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2726
2831
|
body: updateScheduleRequestSchema,
|
|
2727
|
-
response: { 200: scheduleResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2832
|
+
response: { 200: scheduleResponseSchema, 400: errorResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2728
2833
|
}
|
|
2729
2834
|
},
|
|
2730
2835
|
async (request, reply) => {
|
|
@@ -2743,13 +2848,18 @@ async function createAiTeamsServer(options) {
|
|
|
2743
2848
|
if (request.body.requiredLabels !== void 0) fields.requiredLabels = request.body.requiredLabels;
|
|
2744
2849
|
const updated = await updateScheduleFields(db, request.params.scheduleId, fields);
|
|
2745
2850
|
if (!updated) return reply.code(404).send({ error: "Schedule not found." });
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2851
|
+
try {
|
|
2852
|
+
stopScheduleJob(state.scheduleJobs, request.params.scheduleId);
|
|
2853
|
+
if (updated.enabled) {
|
|
2854
|
+
startScheduleJob(updated, scheduleDispatchFn, state.scheduleJobs, onScheduleFire);
|
|
2855
|
+
updated.nextRunAt = state.scheduleJobs.get(request.params.scheduleId)?.nextDate()?.toISO() ?? null;
|
|
2856
|
+
await updateScheduleFields(db, request.params.scheduleId, { nextRunAt: updated.nextRunAt });
|
|
2857
|
+
}
|
|
2858
|
+
return scheduleToResponse(updated);
|
|
2859
|
+
} catch (err) {
|
|
2860
|
+
stopScheduleJob(state.scheduleJobs, request.params.scheduleId);
|
|
2861
|
+
return reply.code(400).send({ error: err instanceof Error ? err.message : "Invalid schedule." });
|
|
2751
2862
|
}
|
|
2752
|
-
return updated;
|
|
2753
2863
|
}
|
|
2754
2864
|
);
|
|
2755
2865
|
app.delete(
|
|
@@ -2780,7 +2890,7 @@ async function createAiTeamsServer(options) {
|
|
|
2780
2890
|
tags: ["schedules"],
|
|
2781
2891
|
summary: "Manually trigger a schedule",
|
|
2782
2892
|
params: { type: "object", required: ["scheduleId"], properties: { scheduleId: { type: "string", minLength: 1 } } },
|
|
2783
|
-
response: { 200: scheduleResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2893
|
+
response: { 200: scheduleResponseSchema, 400: errorResponseSchema, 404: errorResponseSchema, 401: errorResponseSchema }
|
|
2784
2894
|
}
|
|
2785
2895
|
},
|
|
2786
2896
|
async (request, reply) => {
|
|
@@ -2796,7 +2906,9 @@ async function createAiTeamsServer(options) {
|
|
|
2796
2906
|
);
|
|
2797
2907
|
if (!result.ok) return reply.code(400).send({ error: result.message });
|
|
2798
2908
|
await onScheduleFire(request.params.scheduleId);
|
|
2799
|
-
|
|
2909
|
+
const updated = await getScheduleById(db, request.params.scheduleId);
|
|
2910
|
+
if (!updated) return reply.code(404).send({ error: "Schedule not found." });
|
|
2911
|
+
return scheduleToResponse(updated);
|
|
2800
2912
|
}
|
|
2801
2913
|
);
|
|
2802
2914
|
app.get("/ws/agent", { websocket: true }, (socket, request) => {
|
|
@@ -2886,8 +2998,9 @@ async function createAiTeamsServer(options) {
|
|
|
2886
2998
|
for (const timer of state.heartbeatTimers.values()) {
|
|
2887
2999
|
clearTimeout(timer);
|
|
2888
3000
|
}
|
|
3001
|
+
dispatchCleanup();
|
|
2889
3002
|
await app.close();
|
|
2890
|
-
db.close();
|
|
3003
|
+
await db.close();
|
|
2891
3004
|
}
|
|
2892
3005
|
};
|
|
2893
3006
|
}
|
|
@@ -2916,6 +3029,15 @@ if (isCli) {
|
|
|
2916
3029
|
const idx = args.indexOf(name);
|
|
2917
3030
|
if (idx === -1) return void 0;
|
|
2918
3031
|
return args[idx + 1];
|
|
3032
|
+
}, loadServerConfig = function() {
|
|
3033
|
+
try {
|
|
3034
|
+
return JSON.parse(fs2.readFileSync(SERVER_CONFIG_FILE, "utf8"));
|
|
3035
|
+
} catch {
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
}, saveServerConfig = function(config) {
|
|
3039
|
+
fs2.mkdirSync(SERVER_CONFIG_DIR, { recursive: true });
|
|
3040
|
+
fs2.writeFileSync(SERVER_CONFIG_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
2919
3041
|
}, resolveDataDir = function() {
|
|
2920
3042
|
return getArgValue("--data-dir") || process.env.DATA_DIR || path2.join(process.cwd(), "data");
|
|
2921
3043
|
}, resolvePidFile = function() {
|
|
@@ -2923,6 +3045,7 @@ if (isCli) {
|
|
|
2923
3045
|
}, resolveLogDir = function() {
|
|
2924
3046
|
return getArgValue("--log-dir") || process.env.LOG_DIR || path2.join(resolveDataDir(), "logs");
|
|
2925
3047
|
}, applyCliArgsToEnv = function() {
|
|
3048
|
+
const saved = loadServerConfig() ?? {};
|
|
2926
3049
|
const cliToken = getArgValue("--token");
|
|
2927
3050
|
const cliPort = getArgValue("--port");
|
|
2928
3051
|
const cliHost = getArgValue("--host");
|
|
@@ -2931,19 +3054,31 @@ if (isCli) {
|
|
|
2931
3054
|
const cliDbPath = getArgValue("--db-path");
|
|
2932
3055
|
const cliLogLevel = getArgValue("--log-level");
|
|
2933
3056
|
const cliLogDir = getArgValue("--log-dir");
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
3057
|
+
const token = cliToken || process.env.AI_TEAMS_AUTH_TOKEN || saved.authToken;
|
|
3058
|
+
const port = cliPort || process.env.AI_TEAMS_SERVER_PORT || saved.port;
|
|
3059
|
+
const host = cliHost || process.env.HOST || saved.host;
|
|
3060
|
+
const dataDir = cliDataDir || process.env.DATA_DIR || saved.dataDir;
|
|
3061
|
+
const databaseUrl = cliDatabaseUrl || process.env.DATABASE_URL || saved.databaseUrl;
|
|
3062
|
+
const dbPath = cliDbPath || process.env.DB_PATH || saved.dbPath;
|
|
3063
|
+
const logLevel = cliLogLevel || process.env.LOG_LEVEL || saved.logLevel;
|
|
3064
|
+
const logDir = cliLogDir || process.env.LOG_DIR || saved.logDir;
|
|
3065
|
+
if (token) process.env.AI_TEAMS_AUTH_TOKEN = token;
|
|
3066
|
+
if (port) process.env.AI_TEAMS_SERVER_PORT = port;
|
|
3067
|
+
if (host) process.env.HOST = host;
|
|
3068
|
+
if (dataDir) process.env.DATA_DIR = dataDir;
|
|
3069
|
+
if (databaseUrl) process.env.DATABASE_URL = databaseUrl;
|
|
3070
|
+
if (dbPath) process.env.DB_PATH = dbPath;
|
|
3071
|
+
if (logLevel) process.env.LOG_LEVEL = logLevel;
|
|
3072
|
+
if (logDir) process.env.LOG_DIR = logDir;
|
|
3073
|
+
const newConfig = { authToken: token, port, host, dataDir, databaseUrl, dbPath, logLevel, logDir };
|
|
3074
|
+
if (cliToken || cliPort || cliHost || cliDataDir || cliDatabaseUrl || cliDbPath || cliLogLevel || cliLogDir || !loadServerConfig()) {
|
|
3075
|
+
saveServerConfig(newConfig);
|
|
3076
|
+
}
|
|
2942
3077
|
};
|
|
2943
|
-
getArgValue2 = getArgValue, resolveDataDir2 = resolveDataDir, resolvePidFile2 = resolvePidFile, resolveLogDir2 = resolveLogDir, applyCliArgsToEnv2 = applyCliArgsToEnv;
|
|
3078
|
+
getArgValue2 = getArgValue, loadServerConfig2 = loadServerConfig, saveServerConfig2 = saveServerConfig, resolveDataDir2 = resolveDataDir, resolvePidFile2 = resolvePidFile, resolveLogDir2 = resolveLogDir, applyCliArgsToEnv2 = applyCliArgsToEnv;
|
|
2944
3079
|
const args = process.argv.slice(2);
|
|
2945
3080
|
if (args.includes("--version") || args.includes("-v")) {
|
|
2946
|
-
console.log("0.3.
|
|
3081
|
+
console.log("0.3.5");
|
|
2947
3082
|
process.exit(0);
|
|
2948
3083
|
}
|
|
2949
3084
|
if (args.includes("--help") || args.includes("-h")) {
|
|
@@ -2973,22 +3108,26 @@ if (isCli) {
|
|
|
2973
3108
|
`);
|
|
2974
3109
|
process.exit(0);
|
|
2975
3110
|
}
|
|
3111
|
+
const SERVER_CONFIG_DIR = path2.join(os.homedir(), ".ai-teams");
|
|
3112
|
+
const SERVER_CONFIG_FILE = path2.join(SERVER_CONFIG_DIR, "server-config.json");
|
|
2976
3113
|
const subcommand = args[0];
|
|
2977
3114
|
if (subcommand === "start" || subcommand === "restart") {
|
|
2978
3115
|
void (async () => {
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
if (
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3116
|
+
applyCliArgsToEnv();
|
|
3117
|
+
if (!process.env.__AI_TEAMS_DAEMON_WATCHDOG && !process.env.__AI_TEAMS_DAEMON_WORKER) {
|
|
3118
|
+
if (subcommand === "restart") {
|
|
3119
|
+
const status = getDaemonStatus(resolvePidFile());
|
|
3120
|
+
if (status.running) {
|
|
3121
|
+
await stopDaemon(resolvePidFile());
|
|
3122
|
+
}
|
|
3123
|
+
} else {
|
|
3124
|
+
const status = getDaemonStatus(resolvePidFile());
|
|
3125
|
+
if (status.running) {
|
|
3126
|
+
console.log(`Already running (PID ${status.pid}).`);
|
|
3127
|
+
process.exit(0);
|
|
3128
|
+
}
|
|
2989
3129
|
}
|
|
2990
3130
|
}
|
|
2991
|
-
applyCliArgsToEnv();
|
|
2992
3131
|
if (!process.env.AI_TEAMS_AUTH_TOKEN) {
|
|
2993
3132
|
console.error("\u9519\u8BEF: \u9700\u8981\u8BA4\u8BC1 Token\u3002\u4F7F\u7528 --token <token> \u6216\u8BBE\u7F6E AI_TEAMS_AUTH_TOKEN \u73AF\u5883\u53D8\u91CF\u3002");
|
|
2994
3133
|
process.exit(1);
|
|
@@ -3017,6 +3156,20 @@ if (isCli) {
|
|
|
3017
3156
|
} else {
|
|
3018
3157
|
console.log("ai-teams-server is not running.");
|
|
3019
3158
|
}
|
|
3159
|
+
} else if (subcommand === "update") {
|
|
3160
|
+
const { execSync } = await import("node:child_process");
|
|
3161
|
+
try {
|
|
3162
|
+
execSync("npm install -g @csdwd/ai-teams-server@latest", { stdio: "inherit" });
|
|
3163
|
+
const ver = execSync("ai-teams-server --version").toString().trim();
|
|
3164
|
+
console.log(`
|
|
3165
|
+
\u2713 \u5DF2\u66F4\u65B0\u5230 ${ver}`);
|
|
3166
|
+
const status = getDaemonStatus(resolvePidFile());
|
|
3167
|
+
if (status.running) {
|
|
3168
|
+
console.log(" \u63D0\u793A: \u8FD0\u884C ai-teams-server restart \u4EE5\u5E94\u7528\u66F4\u65B0\u3002");
|
|
3169
|
+
}
|
|
3170
|
+
} catch {
|
|
3171
|
+
process.exit(1);
|
|
3172
|
+
}
|
|
3020
3173
|
} else {
|
|
3021
3174
|
applyCliArgsToEnv();
|
|
3022
3175
|
const options = readOptionsFromEnv();
|
|
@@ -3031,6 +3184,8 @@ if (isCli) {
|
|
|
3031
3184
|
}
|
|
3032
3185
|
}
|
|
3033
3186
|
var getArgValue2;
|
|
3187
|
+
var loadServerConfig2;
|
|
3188
|
+
var saveServerConfig2;
|
|
3034
3189
|
var resolveDataDir2;
|
|
3035
3190
|
var resolvePidFile2;
|
|
3036
3191
|
var resolveLogDir2;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{color:#e8eefc;background:radial-gradient(circle at top left,rgba(105,170,255,.24),transparent 34%),radial-gradient(circle at bottom right,rgba(68,111,196,.28),transparent 32%),#091221;font-family:SF Pro Display,PingFang SC,Microsoft YaHei,sans-serif}*{box-sizing:border-box}html,body,#root{height:100%;overflow:hidden}body{margin:0;min-height:100vh;background:transparent;font-size:13px;line-height:1.42}button,input,select,textarea{font:inherit}.app-shell{display:grid;height:100dvh;min-height:0;overflow:hidden;grid-template-columns:minmax(0,1fr) 500px;grid-template-rows:auto minmax(0,1fr);grid-template-areas:"topbar topbar" "workspace command";align-items:stretch}.auth-screen{display:grid;min-height:100dvh;place-items:center;padding:18px;overflow:auto}.auth-card{width:min(390px,100%);border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:18px;background:#0d1727e0;box-shadow:0 14px 32px #0108143d}.auth-brand{margin-bottom:14px}.topbar,.command-panel{padding:12px 14px;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);background:#091221c2}.topbar{grid-area:topbar;display:flex;align-items:center;justify-content:space-between;gap:14px;border-bottom:1px solid rgba(255,255,255,.08);position:relative;z-index:10}.brand{display:flex;gap:10px;align-items:center;flex:0 0 auto}.brand-badge{display:grid;place-items:center;width:32px;height:32px;border-radius:10px;background:linear-gradient(135deg,#63b3ff,#67ffd4);color:#07203f;font-weight:800;font-size:13px}.brand strong{font-size:14px}.brand p{margin:2px 0 0;color:#90a1be;font-size:12px}.nav-list{display:flex;flex:1;flex-direction:row;gap:6px;justify-content:flex-end;overflow-x:auto}.nav-item{flex:0 0 auto;border:0;border-radius:9px;padding:7px 10px;text-align:left;color:#c9d6f2;background:#ffffff0a;cursor:pointer;font-size:12px;white-space:nowrap}.nav-item.active{background:linear-gradient(135deg,#63b3ff52,#67ffd42e);color:#fff}.workspace-panel{grid-area:workspace;display:flex;min-width:0;min-height:0;flex-direction:column;gap:10px;padding:12px 14px;overflow:auto;overscroll-behavior:contain}.board,.task-log-page{min-width:0}.board-header{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:10px}.board-header h1,.panel-card h2,.task-log-panel h1{margin:0}.board-header h1,.task-log-panel h1{font-size:20px;line-height:1.2}.panel-card h2,.section-title-row h2{font-size:15px;line-height:1.25}.board-header p{margin:5px 0 0;color:#92a6c8;font-size:12px}.employee-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,560px),1fr));gap:10px}.employee-card,.panel-card,.task-log-panel,.empty-state{border:1px solid rgba(255,255,255,.08);border-radius:14px;background:#0d1727cc;box-shadow:0 12px 30px #0108143d}.employee-card{padding:10px}.task-log-panel{flex:0 0 auto;margin:0;padding:10px}.section-title-row{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;margin-bottom:10px}.section-title-row h2{margin:0}.section-title-row p{margin:4px 0 0;color:#90a1be;font-size:12px}.filter-row{display:flex;flex-wrap:wrap;gap:6px;justify-content:flex-end}.filter-chip,.target-chip{border:1px solid rgba(255,255,255,.08);border-radius:999px;padding:5px 8px;color:#c9d6f2;background:#ffffff0a;cursor:pointer;font-size:12px}.filter-chip.active,.target-chip.active{color:#061626;background:#79ffd1}.employee-card__header{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;margin-bottom:8px}.employee-card__title{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline}.employee-card__header h2{margin:0;font-size:15px}.task-state-badge{display:inline-flex;align-items:center;border-radius:999px;padding:1px 6px;color:#90a1be;background:#ffffff0a;font-size:10px;line-height:1.4;text-transform:uppercase}.task-state-badge.task-running,.task-state-badge.task-accepted,.task-state-badge.task-dispatched{color:#9bc8ff;background:#5da6ff1a}.task-state-badge.task-completed{color:#9df6b5;background:#79ffd114}.task-state-badge.task-failed,.task-state-badge.task-cancelled,.task-state-badge.task-timeout{color:#ff9e9e;background:#ff59591a}.employee-meta,.task-strip,.history-meta,.chat-message__meta{display:flex;justify-content:space-between;gap:10px}.employee-meta{flex-wrap:wrap;justify-content:flex-start;row-gap:4px;margin-bottom:8px;color:#7f93b5;font-size:11px}.task-strip{align-items:center;margin-bottom:8px;padding:6px 8px;border-radius:9px;background:#ffffff0a}.task-strip.task-failed,.task-row.task-failed{border-color:#ff77776b;background:#ff59591a}.task-strip.task-completed,.task-row.task-completed{border-color:#79ffd13d}.task-strip span{flex:1;color:#c9d6f2;font-size:12px}.log-window{margin:0;width:100%;aspect-ratio:16 / 9;min-height:220px;max-height:460px;overflow:auto;border-radius:10px;border:1px solid rgba(121,255,209,.12);padding:10px;background:linear-gradient(180deg,rgba(121,255,209,.03),transparent 34%),#06101b;color:#bfffd4;font-family:SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;box-shadow:inset 0 0 0 1px #ffffff05}.status-pill{display:inline-flex;align-items:center;gap:6px;border-radius:999px;padding:5px 8px;font-size:11px;background:#ffffff0d}.status-pill.online{color:#9df6b5}.status-pill.offline{color:#f5a8a8}.status-pill.agent-presence{flex:0 0 auto}.status-pill.busy{color:#9bc8ff;background:#5da6ff1f}.status-pill.failed{color:#ff9e9e;background:#ff59591f}.status-pill.completed{color:#9df6b5}.status-pill.running,.status-pill.accepted,.status-pill.dispatched{color:#9bc8ff}.command-panel{grid-area:command;display:flex;min-width:0;min-height:0;height:100%;flex-direction:column;gap:10px;border-left:1px solid rgba(255,255,255,.08);overflow:hidden}.panel-card{padding:10px}.chat-panel{display:flex;flex:1;min-height:0;flex-direction:column;overflow:hidden}.chat-header{display:flex;flex:0 0 auto;justify-content:space-between;gap:10px;align-items:flex-start}.chat-header p{margin:3px 0 0;color:#90a1be;font-size:12px}.chat-header code{color:#79ffd1}.chat-list{display:flex;flex:1 1 auto;min-height:0;flex-direction:column;gap:8px;margin-top:10px;overflow:auto;padding-right:4px;overscroll-behavior:contain}.chat-composer{flex:0 0 auto;margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.08)}.target-picker{display:flex;flex-wrap:wrap;gap:6px;max-height:78px;overflow:auto;overscroll-behavior:contain}.chat-message{width:fit-content;max-width:88%;border-radius:11px 11px 4px;padding:8px}.leader-message{border-bottom-right-radius:6px;background:linear-gradient(135deg,#68b6ff47,#79ffd11f);max-width:none}.leader-message small,.leader-message .chat-message__executing span{white-space:nowrap}.leader-message-wrapper{display:flex;flex-direction:column;align-items:flex-end;max-width:88%;align-self:flex-end}.chat-message__time-outside{color:#90a1be;font-size:11px;margin-top:2px;padding-right:4px}.chat-message__executing{display:flex;flex-direction:column;gap:2px;margin-top:4px;padding-top:4px;border-top:1px solid rgba(255,255,255,.1)}.chat-message__executing span{font-size:11px}.executing-status.executing-dispatched,.executing-status.executing-accepted,.executing-status.executing-running{color:#9bc8ff}.executing-status.executing-completed{color:#9df6b5}.executing-status.executing-failed,.executing-status.executing-timeout,.executing-status.executing-cancelled{color:#ff9e9e}.employee-message{align-self:flex-start;border-bottom-left-radius:6px;background:#ffffff0f}.employee-message.task-failed,.employee-message.task-timeout{background:#ff59591f}.chat-message p{margin:6px 0;color:#eef4ff;white-space:pre-wrap}.chat-message small{color:#79ffd1}.chat-message__meta span,.chat-empty{color:#90a1be;font-size:11px}.field{display:flex;flex-direction:column;gap:5px;margin-top:9px}.field span{color:#98abcb;font-size:12px}.field input,.field select,.field textarea{width:100%;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a;color:#eef4ff;font-size:12px}.field textarea{min-height:82px;max-height:112px;resize:none;overflow:auto}.primary-button,.secondary-button{border:0;border-radius:10px;cursor:pointer}.primary-button{width:100%;margin-top:10px;padding:8px 12px;color:#07203f;background:linear-gradient(135deg,#68b6ff,#79ffd1);font-weight:700;font-size:12px}.secondary-button{padding:6px 8px;background:#ffffff14;color:#eef4ff;font-size:12px}.history-list{display:flex;flex-direction:column;gap:10px;margin-top:14px}.task-table{display:flex;flex-direction:column;gap:6px}.task-row{display:flex;justify-content:space-between;gap:10px;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a}.task-row__main{min-width:0}.task-row__main p{margin:4px 0 0;color:#c9d6f2}.task-row__side{display:flex;flex-direction:column;align-items:flex-end;gap:6px;white-space:nowrap}.task-row__side small,.error-text{color:#90a1be}.error-text{display:inline-block;margin-top:4px;color:#ffb3b3}.history-item{border-radius:10px;padding:10px;background:#ffffff0a}.history-item p,.history-empty{margin:6px 0 0;color:#c9d6f2}.history-meta span{color:#90a1be;font-size:11px}.empty-state{padding:24px;color:#90a1be;text-align:center}.reconnect-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#091221bf;color:#79ffd1;font-size:16px;font-weight:600;letter-spacing:.05em;cursor:pointer;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;user-select:none}.mobile-status-bar,.mobile-chat-feed,.mobile-input-bar,.mobile-logout,.mobile-terminal-overlay{display:none}.schedule-create-btn{width:auto;margin-top:0;padding:7px 14px;white-space:nowrap}.schedule-row{flex-wrap:wrap;gap:8px}.schedule-row__header{display:flex;align-items:center;gap:8px}.schedule-target-badge{display:inline-flex;border-radius:999px;padding:1px 7px;font-size:10px;font-weight:600;text-transform:uppercase}.schedule-target-queue{color:#9bc8ff;background:#5da6ff1f}.schedule-target-direct{color:#c9b3ff;background:#a980ff1f}.schedule-target-broadcast{color:#ffd480;background:#ffc83c1f}.schedule-cron{font-family:SFMono-Regular,Menlo,monospace;font-size:11px;color:#79ffd1}.schedule-prompt{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.schedule-row__side{gap:6px}.schedule-actions{display:flex;gap:4px;flex-wrap:wrap}.schedule-actions .secondary-button{font-size:11px;padding:4px 8px}.schedule-delete-btn{color:#ff9e9e}.schedule-toggle{display:flex;align-items:center;gap:6px;cursor:pointer;-webkit-user-select:none;user-select:none}.schedule-toggle__track{position:relative;width:36px;height:20px;border-radius:10px;transition:background .2s}.schedule-toggle__track.on{background:#22c55e}.schedule-toggle__track.off{background:#ffffff1f}.schedule-toggle__thumb{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s}.schedule-toggle__track.on .schedule-toggle__thumb{transform:translate(16px)}.schedule-toggle small{color:#90a1be;font-size:11px}.modal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:150;display:flex;align-items:center;justify-content:center;background:#091221cc;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.modal{width:min(520px,92vw);max-height:88vh;display:flex;flex-direction:column;border:1px solid rgba(255,255,255,.1);border-radius:14px;background:#0d1727f5;box-shadow:0 20px 50px #00000080}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid rgba(255,255,255,.08)}.modal-header h2{margin:0;font-size:16px}.modal-body{flex:1;overflow-y:auto;padding:14px 16px}.modal-footer{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid rgba(255,255,255,.08)}.modal-error{background:#ff59591f;color:#ffb3b3;border-radius:8px;padding:8px 10px;margin-bottom:8px;font-size:12px}.schedule-enable-field{flex-direction:row;align-items:center;justify-content:space-between}.cron-input{font-family:SFMono-Regular,Menlo,monospace}@media(max-width:1100px)and (min-width:641px){.app-shell{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr) minmax(min(500px,62dvh),62dvh);grid-template-areas:"topbar" "workspace" "command"}.topbar,.command-panel{border:0}.topbar{align-items:flex-start;flex-direction:column;gap:10px;padding:12px 14px}.nav-list{width:100%;justify-content:flex-start}.command-panel{border-top:1px solid rgba(255,255,255,.08);padding:10px}.command-panel .panel-card{padding:10px}.section-title-row,.task-row{flex-direction:column}.task-row__side{align-items:flex-start}.field textarea{min-height:68px;max-height:78px}}@media(max-width:640px){html,body,#root{position:fixed;top:0;left:0;right:0;bottom:0;overflow:hidden}.app-shell{height:100%;height:100dvh;box-sizing:border-box;padding-bottom:env(safe-area-inset-bottom,0px);grid-template-columns:1fr;grid-template-rows:auto auto 1fr auto;grid-template-areas:"topbar" "mobile-status" "mobile-chat" "mobile-input";align-items:stretch}.topbar{grid-area:topbar;padding:6px 10px;gap:8px;overflow:hidden}.topbar .nav-list,.brand p{display:none}.brand strong{font-size:13px}.brand-badge{width:26px;height:26px;font-size:11px;border-radius:8px}.workspace-panel,.command-panel{display:none}.mobile-status-bar{grid-area:mobile-status;display:flex;flex:0 0 auto;gap:6px;padding:4px 10px;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;background:#09122199;border-bottom:1px solid rgba(255,255,255,.06);-webkit-overflow-scrolling:touch;scrollbar-width:none}.mobile-status-bar::-webkit-scrollbar{display:none}.mobile-agent-pill{display:flex;align-items:center;gap:4px;border-radius:999px;padding:3px 8px;background:#ffffff0d;font-size:11px;white-space:nowrap;flex:0 0 auto}.mobile-agent-pill .dot{width:6px;height:6px;border-radius:50%;background:#6b7a96}.mobile-agent-pill.online .dot{background:#67e8a0;box-shadow:0 0 4px #67e8a080}.mobile-agent-pill.busy .dot{background:#63b3ff;animation:pulse-dot 1.5s infinite}.mobile-agent-pill.offline .dot{background:#6b7a96}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.4}}.mobile-agent-pill .agent-name{color:#c9d6f2;font-weight:500}.mobile-agent-pill .agent-task{color:#7f93b5;max-width:80px;overflow:hidden;text-overflow:ellipsis}.mobile-chat-feed{grid-area:mobile-chat;display:flex;flex-direction:column;gap:6px;padding:8px 10px;min-height:0;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}.mobile-chat-feed .chat-message{max-width:88%;border-radius:10px 10px 4px;padding:6px 8px}.mobile-chat-feed .chat-message p{margin:4px 0;font-size:12px;white-space:pre-wrap;word-break:break-word}.mobile-chat-feed .chat-message__meta{gap:6px}.mobile-chat-feed .chat-message__meta strong{font-size:11px}.mobile-chat-feed .chat-message__meta span,.mobile-chat-feed .chat-message small{font-size:10px}.mobile-chat-feed .leader-message{max-width:none}.mobile-chat-feed .leader-message-wrapper{max-width:88%}.mobile-chat-feed .chat-message__time-outside,.mobile-chat-feed .chat-message__executing span{font-size:10px}.mobile-chat-empty{flex:1;display:flex;align-items:center;justify-content:center;color:#6b7a96;font-size:12px}.mobile-input-bar{grid-area:mobile-input;display:flex;flex-direction:column;gap:6px;padding:8px 10px;flex:0 0 auto;background:#091221eb;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-top:1px solid rgba(255,255,255,.06)}.mobile-target-row{display:flex;gap:4px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:none}.mobile-target-row::-webkit-scrollbar{display:none}.mobile-target-row .target-chip{padding:3px 7px;font-size:11px;white-space:nowrap;flex:0 0 auto}.mobile-input-row{display:flex;gap:6px;align-items:flex-end}.mobile-input-row textarea{flex:1;border:1px solid rgba(255,255,255,.08);border-radius:9px;padding:8px 10px;background:#ffffff0a;color:#eef4ff;font-size:14px;font-family:inherit;height:36px;max-height:120px;resize:none;line-height:1.4;overflow-y:auto}.mobile-input-row .send-btn{flex:0 0 auto;width:36px;height:36px;border-radius:50%;border:0;background:linear-gradient(135deg,#68b6ff,#79ffd1);color:#07203f;font-size:16px;cursor:pointer;display:grid;place-items:center}.mobile-input-row .send-btn:disabled{opacity:.4;cursor:default}.auth-card{width:min(340px,100%);padding:14px}.auth-brand{margin-bottom:10px}.mobile-logout{display:block}.mobile-status-bar,.mobile-chat-feed,.mobile-input-bar{display:flex}.mobile-agent-pill.selected{background:#67e8a02e;border:1px solid rgba(103,232,160,.3)}.mobile-terminal-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:100;display:flex;flex-direction:column;justify-content:flex-end;background:#0000008c;-webkit-tap-highlight-color:transparent}.mobile-terminal-card{display:flex;flex-direction:column;max-height:70dvh;margin:0 6px 6px;border-radius:14px;border:1px solid rgba(255,255,255,.08);background:#06101bf7;box-shadow:0 -8px 30px #0006;overflow:hidden}.mobile-terminal-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255,255,255,.06);font-size:12px;color:#c9d6f2;font-weight:600}.mobile-terminal-header button{border:0;background:#ffffff0f;color:#90a1be;border-radius:50%;width:26px;height:26px;font-size:13px;cursor:pointer;display:grid;place-items:center}.mobile-terminal-content{flex:1;min-height:0;margin:0;padding:10px;overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;color:#bfffd4;font-family:SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;white-space:pre-wrap;word-break:break-all}}
|