@co0ontty/wand 4.94.0 → 4.95.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.md +4 -1
- package/dist/ai-text-runner.d.ts +4 -0
- package/dist/ai-text-runner.js +44 -4
- package/dist/build-info.json +3 -3
- package/dist/cli-api.d.ts +1 -0
- package/dist/cli-api.js +7 -2
- package/dist/cli.js +47 -0
- package/dist/conversation-execution-preference.d.ts +7 -0
- package/dist/conversation-execution-preference.js +26 -0
- package/dist/conversation-organize-contract.d.ts +70 -0
- package/dist/conversation-organize-contract.js +1 -0
- package/dist/conversation-organize-service.d.ts +46 -0
- package/dist/conversation-organize-service.js +505 -0
- package/dist/conversation-organize.d.ts +41 -0
- package/dist/conversation-organize.js +97 -0
- package/dist/conversation-service.d.ts +2 -1
- package/dist/conversation-service.js +10 -4
- package/dist/core-turn-tracker.d.ts +5 -1
- package/dist/core-turn-tracker.js +17 -1
- package/dist/decision-expert-service.d.ts +2 -0
- package/dist/decision-expert-service.js +17 -1
- package/dist/decision-skill.d.ts +0 -1
- package/dist/decision-skill.js +5 -64
- package/dist/deployment-lock.js +4 -1
- package/dist/employee-im-config.d.ts +17 -0
- package/dist/employee-im-config.js +85 -0
- package/dist/employee-im-lifecycle.d.ts +35 -0
- package/dist/employee-im-lifecycle.js +168 -0
- package/dist/employee-im-types.d.ts +80 -0
- package/dist/employee-im-types.js +32 -0
- package/dist/employee-im.d.ts +35 -0
- package/dist/employee-im.js +157 -0
- package/dist/employee-knowledge.js +3 -2
- package/dist/employee-text.d.ts +1 -0
- package/dist/employee-text.js +28 -13
- package/dist/git-quick-commit.js +21 -3
- package/dist/managed-skill.d.ts +6 -0
- package/dist/managed-skill.js +99 -0
- package/dist/model-auto-assign.js +1 -1
- package/dist/process-manager.js +1 -1
- package/dist/prompt-optimizer.js +1 -1
- package/dist/scheduled-task-activity.d.ts +16 -0
- package/dist/scheduled-task-activity.js +42 -0
- package/dist/scheduled-task-agenda.d.ts +20 -0
- package/dist/scheduled-task-agenda.js +82 -0
- package/dist/scheduled-task-calendar.d.ts +37 -0
- package/dist/scheduled-task-calendar.js +131 -0
- package/dist/scheduled-task-read-range.d.ts +7 -0
- package/dist/scheduled-task-read-range.js +35 -0
- package/dist/scheduled-task-service.d.ts +6 -1
- package/dist/scheduled-task-service.js +61 -1
- package/dist/scheduled-task-state.d.ts +1 -1
- package/dist/scheduled-task-types.d.ts +38 -0
- package/dist/server-asset-routes.js +3 -2
- package/dist/server-conversation-organize-routes.d.ts +4 -0
- package/dist/server-conversation-organize-routes.js +75 -0
- package/dist/server-conversation-routes.js +2 -1
- package/dist/server-employee-im-routes.d.ts +9 -0
- package/dist/server-employee-im-routes.js +40 -0
- package/dist/server-employee-routes.js +1 -1
- package/dist/server-scheduled-task-routes.js +24 -0
- package/dist/server-session-lifecycle-routes.js +8 -3
- package/dist/server-session-routes.js +18 -0
- package/dist/server-session-worktree-routes.js +3 -3
- package/dist/server-workspace-routes.d.ts +3 -0
- package/dist/server-workspace-routes.js +14 -1
- package/dist/server.js +39 -5
- package/dist/session-ai-context.d.ts +11 -1
- package/dist/session-ai-context.js +21 -2
- package/dist/session-metadata-service.js +1 -1
- package/dist/session-registry.js +6 -0
- package/dist/session-request.js +3 -0
- package/dist/session-topic.js +1 -1
- package/dist/silicon-employee-draft.js +1 -1
- package/dist/speech-polisher-service.d.ts +2 -0
- package/dist/speech-polisher-service.js +28 -6
- package/dist/storage/conversation-organization-repository.d.ts +13 -0
- package/dist/storage/conversation-organization-repository.js +33 -0
- package/dist/storage/migrations.js +10 -0
- package/dist/storage/secrets-repository.d.ts +7 -0
- package/dist/storage/secrets-repository.js +28 -1
- package/dist/storage/tasks-repository.js +4 -0
- package/dist/storage-schedules.d.ts +8 -1
- package/dist/storage-schedules.js +24 -0
- package/dist/storage-schema.js +9 -0
- package/dist/storage.d.ts +6 -1
- package/dist/storage.js +13 -0
- package/dist/structured-session-manager.d.ts +1 -0
- package/dist/structured-session-manager.js +18 -4
- package/dist/system-ai-call.d.ts +49 -0
- package/dist/system-ai-call.js +28 -0
- package/dist/system-ai-session-recorder.d.ts +30 -0
- package/dist/system-ai-session-recorder.js +239 -0
- package/dist/system-cli.d.ts +6 -0
- package/dist/system-cli.js +93 -0
- package/dist/system-skill.d.ts +5 -0
- package/dist/system-skill.js +19 -0
- package/dist/task-organize-contract.js +2 -2
- package/dist/task-title-organize-service.d.ts +1 -1
- package/dist/task-title-organize-service.js +9 -5
- package/dist/task-title-organize.js +13 -6
- package/dist/task-title-service.js +1 -1
- package/dist/task-title.js +1 -1
- package/dist/user-memory.js +1 -1
- package/dist/wand-task-sync.js +2 -1
- package/dist/web-ui/content/ai-teams.js +12 -12
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{0051bd3d55f10ab7/library-bridge-DTMQIHVJ.js → 09842858f9d34a4e/library-bridge.js} +2 -2
- package/dist/web-ui/content/modules/1021ae40c5761e3f/sha256.js +1 -0
- package/dist/web-ui/content/modules/134f6120c0acaf57/host.js +1 -0
- package/dist/web-ui/content/modules/1d518ae6ed66fe5a/chunk.js +3 -0
- package/dist/web-ui/content/modules/1f93b5bd70381bb6/runtime.js +539 -0
- package/dist/web-ui/content/modules/26c68d4decdd18b4/markdown.js +1 -0
- package/dist/web-ui/content/modules/2ed8bafd5b4fecc5/workspace-window.js +1 -0
- package/dist/web-ui/content/modules/31ffc2f05f87652c/host.js +1 -0
- package/dist/web-ui/content/modules/35aaa85b9d0937af/chunk.js +20 -0
- package/dist/web-ui/content/modules/3664d0620f124292/chunk.js +3 -0
- package/dist/web-ui/content/modules/3cea480770cb184c/chunk.js +1 -0
- package/dist/web-ui/content/modules/3d12adf54647c1e2/host-bridge.js +1 -0
- package/dist/web-ui/content/modules/459cca2482a9a4ba/host.js +18 -0
- package/dist/web-ui/content/modules/474c9647130ef3e9/task-board-host.js +2 -0
- package/dist/web-ui/content/modules/4f74d2517f29134c/chunk.js +15 -0
- package/dist/web-ui/content/modules/52a218d74002a011/host.js +30 -0
- package/dist/web-ui/content/modules/54f2b10659ef8475/employee-card.js +1 -0
- package/dist/web-ui/content/modules/703fb1c670f7b199/chunk.js +1 -0
- package/dist/web-ui/content/modules/75511da23f051718/host.js +1 -0
- package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-2IVIZ2YC.js → 799b19da948bec8d/host.js} +3 -2
- package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-XNIR3NTJ.js → 7ef73b6c20d2504f/host.js} +3 -2
- package/dist/web-ui/content/modules/8f8f7a0a8874d29a/host.js +1 -0
- package/dist/web-ui/content/modules/90f2cd73cdf3a2cd/chat-render.js +25 -0
- package/dist/web-ui/content/modules/9510efce6caaa9ec/chunk.js +1 -0
- package/dist/web-ui/content/modules/9a0e65188739957f/chunk.js +67 -0
- package/dist/web-ui/content/modules/9eae1033f47e4526/host.js +1 -0
- package/dist/web-ui/content/modules/a2c5b246e57fa165/panel.js +1 -0
- package/dist/web-ui/content/modules/a50ce28f45bfeb2e/host.js +1 -0
- package/dist/web-ui/content/modules/adfc53789d5bedb2/chunk.js +1 -0
- package/dist/web-ui/content/modules/bcecb0cc14b7d2bd/chunk.js +1 -0
- package/dist/web-ui/content/modules/c4706f47c23f33e4/host.js +1 -0
- package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-Q524TMSR.js → c7de873095c061ab/host.js} +4 -3
- package/dist/web-ui/content/modules/d789f84a3f8765ae/page.js +6 -0
- package/dist/web-ui/content/modules/db4e2dbc7aed6353/chunk.js +1 -0
- package/dist/web-ui/content/modules/e4fd9906521b4aa3/chunk.js +1 -0
- package/dist/web-ui/content/modules/eb95c3ee0d42827a/chunk.js +6 -0
- package/dist/web-ui/content/modules/{0051bd3d55f10ab7/host-PHHNGVNL.js → ec68d93a95c1d5d6/host.js} +2 -2
- package/dist/web-ui/content/modules/f33ed89d1ccd3a2d/chunk.js +1 -0
- package/dist/web-ui/content/modules/f6502dab121f9d91/chunk.js +1 -0
- package/dist/web-ui/content/modules/fac83e61c0a8c8a7/host.js +1 -0
- package/dist/web-ui/content/scripts.js +1 -1
- package/dist/web-ui/embedded-assets.d.ts +200 -27
- package/dist/web-ui/embedded-assets.js +5 -4
- package/dist/web-ui/react/workspaces/sidebar-manage.d.ts +5 -0
- package/dist/web-ui/react/workspaces/sidebar-manage.js +27 -3
- package/dist/web-ui/running-activity.d.ts +22 -9
- package/dist/web-ui/running-activity.js +58 -42
- package/dist/web-ui/running-current-action.d.ts +12 -0
- package/dist/web-ui/running-current-action.js +74 -0
- package/dist/web-ui/scripts.d.ts +4 -0
- package/dist/web-ui/scripts.js +17 -8
- package/package.json +4 -2
- package/scripts/recover-web-update.mjs +26 -19
- package/scripts/recover-web-update.sh +9 -6
- package/scripts/update-node.sh +3 -1
- package/skills/wand-system/SKILL.md +69 -0
- package/skills/wand-system/references/operations.md +100 -0
- package/skills/wand-system/scripts/wand.mjs +34 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chat-render-RY2I2CF3.js +0 -24
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-2F6J2MKX.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-6C6A2RHG.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-7AFM245W.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-ECWYA3U5.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-MHX2QXIS.js +0 -111
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-RAAPETNH.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-RXKBHYUR.js +0 -38
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-T35D57FW.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-U4EDP4TI.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-WHXQR7JT.js +0 -20
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-YOXXNXOX.js +0 -2
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/chunk-YQJ6XYV7.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/employee-card-NV34TZIH.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-2AQAIUNO.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-AZXNFCRW.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-EPEQX22R.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-FZYIMOLC.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-G6GL4SNL.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-PEILC5NU.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-RO5MB6RB.js +0 -69
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-WOQRK6NG.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-XRVOIEP6.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-ZDKOQEMS.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-bridge-PMQZRG5G.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/page-PY4JUL4W.js +0 -3
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/panel-BBQFC55Q.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/runtime.js +0 -974
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/sha256-ANXV72UC.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/task-board-host-PXZSWBBG.js +0 -2
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-agent-picker-DJSECSY6.js +0 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-window-MNMKSOBG.js +0 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ScheduledTaskSummary } from "./scheduled-task-types.js";
|
|
2
|
+
export interface AgendaGroup {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
dateLabel: string;
|
|
6
|
+
active: boolean;
|
|
7
|
+
tasks: ScheduledTaskSummary[];
|
|
8
|
+
}
|
|
9
|
+
export declare function appointment(value: string, now: Date, timeZone: string): {
|
|
10
|
+
day: string;
|
|
11
|
+
time: string;
|
|
12
|
+
period: string;
|
|
13
|
+
dateLabel: string;
|
|
14
|
+
exact: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function isScheduled(task: ScheduledTaskSummary): boolean;
|
|
17
|
+
/** Upcoming details and agenda share exactly the same scheduling facts, independent of local filters. */
|
|
18
|
+
export declare function nextTask(tasks: ScheduledTaskSummary[]): ScheduledTaskSummary | undefined;
|
|
19
|
+
/** Calendar dates, not elapsed 24h blocks; a saved nextRunAt is never recalculated by the view. */
|
|
20
|
+
export declare function groupAgenda(tasks: ScheduledTaskSummary[], now: Date, timeZone: string): AgendaGroup[];
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
function dayParts(value, timeZone) {
|
|
2
|
+
const parts = new Intl.DateTimeFormat("en-CA", { timeZone, year: "numeric", month: "2-digit", day: "2-digit" }).formatToParts(value);
|
|
3
|
+
return ["year", "month", "day"].map(key => Number(parts.find(part => part.type === key).value));
|
|
4
|
+
}
|
|
5
|
+
function calendarDay(value, timeZone) {
|
|
6
|
+
const [year, month, day] = dayParts(value, timeZone);
|
|
7
|
+
return Date.UTC(year, month - 1, day) / 86_400_000;
|
|
8
|
+
}
|
|
9
|
+
export function appointment(value, now, timeZone) {
|
|
10
|
+
const date = new Date(value);
|
|
11
|
+
if (!Number.isFinite(date.getTime()))
|
|
12
|
+
return { day: "时间待确认", time: "—", period: "", dateLabel: "", exact: "计划时间不可用" };
|
|
13
|
+
const distance = calendarDay(date, timeZone) - calendarDay(now, timeZone);
|
|
14
|
+
const [year] = dayParts(date, timeZone), [currentYear] = dayParts(now, timeZone);
|
|
15
|
+
const weekday = new Intl.DateTimeFormat("zh-CN", { timeZone, weekday: "short" }).format(date);
|
|
16
|
+
const dateLabel = new Intl.DateTimeFormat("zh-CN", { timeZone, ...(year === currentYear ? {} : { year: "numeric" }), month: "long", day: "numeric" }).format(date);
|
|
17
|
+
const day = distance === 0 ? "今天" : distance === 1 ? "明天" : distance === 2 ? "后天" : distance > 2 && distance < 7 ? weekday : dateLabel;
|
|
18
|
+
const time = new Intl.DateTimeFormat("zh-CN", { timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(date);
|
|
19
|
+
const hour = Number(new Intl.DateTimeFormat("en-GB", { timeZone, hour: "2-digit", hourCycle: "h23" }).format(date));
|
|
20
|
+
const period = hour < 5 ? "凌晨" : hour < 12 ? "上午" : hour < 14 ? "中午" : hour < 18 ? "下午" : "晚上";
|
|
21
|
+
const exact = new Intl.DateTimeFormat("zh-CN", { timeZone, year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(date);
|
|
22
|
+
return { day, time, period, dateLabel: `${dateLabel} · ${weekday}`, exact };
|
|
23
|
+
}
|
|
24
|
+
export function isScheduled(task) {
|
|
25
|
+
return task.enabled && !task.cancelledAt && Boolean(task.nextRunAt);
|
|
26
|
+
}
|
|
27
|
+
function orderedTasks(tasks) {
|
|
28
|
+
return [...tasks].sort((a, b) => {
|
|
29
|
+
const left = isScheduled(a) ? Date.parse(a.nextRunAt) : NaN;
|
|
30
|
+
const right = isScheduled(b) ? Date.parse(b.nextRunAt) : NaN;
|
|
31
|
+
if (Number.isFinite(left) && Number.isFinite(right))
|
|
32
|
+
return left - right;
|
|
33
|
+
return Number.isFinite(left) ? -1 : Number.isFinite(right) ? 1 : 0;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** Upcoming details and agenda share exactly the same scheduling facts, independent of local filters. */
|
|
37
|
+
export function nextTask(tasks) {
|
|
38
|
+
return orderedTasks(tasks).find(task => isScheduled(task) && Number.isFinite(Date.parse(task.nextRunAt)));
|
|
39
|
+
}
|
|
40
|
+
/** Calendar dates, not elapsed 24h blocks; a saved nextRunAt is never recalculated by the view. */
|
|
41
|
+
export function groupAgenda(tasks, now, timeZone) {
|
|
42
|
+
const groups = new Map();
|
|
43
|
+
for (const task of orderedTasks(tasks)) {
|
|
44
|
+
let id, title, dateLabel = "", active = false;
|
|
45
|
+
if (isScheduled(task)) {
|
|
46
|
+
const date = new Date(task.nextRunAt);
|
|
47
|
+
if (!Number.isFinite(date.getTime())) {
|
|
48
|
+
id = "unconfirmed";
|
|
49
|
+
title = "时间待确认";
|
|
50
|
+
}
|
|
51
|
+
else if (date.getTime() <= now.getTime()) {
|
|
52
|
+
id = "due";
|
|
53
|
+
title = "已到计划时间";
|
|
54
|
+
active = true;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const description = appointment(task.nextRunAt, now, timeZone);
|
|
58
|
+
id = `day-${calendarDay(date, timeZone)}`;
|
|
59
|
+
title = description.day;
|
|
60
|
+
dateLabel = description.dateLabel;
|
|
61
|
+
active = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else if (!task.cancelledAt && task.nextRunAt && !task.enabled) {
|
|
65
|
+
id = "paused";
|
|
66
|
+
title = "已暂停";
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
id = "finished";
|
|
70
|
+
title = "已结束";
|
|
71
|
+
}
|
|
72
|
+
let group = groups.get(id);
|
|
73
|
+
if (!group) {
|
|
74
|
+
group = { id, title, dateLabel, active, tasks: [] };
|
|
75
|
+
groups.set(id, group);
|
|
76
|
+
}
|
|
77
|
+
group.tasks.push(task);
|
|
78
|
+
}
|
|
79
|
+
const all = [...groups.values()];
|
|
80
|
+
return [...all.filter(group => group.active), ...all.filter(group => !group.active && group.id === "unconfirmed"),
|
|
81
|
+
...all.filter(group => group.id === "paused"), ...all.filter(group => group.id === "finished")];
|
|
82
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ScheduledTaskRunView, ScheduledTaskRunFactView, ScheduledTaskSummary } from "./scheduled-task-types.js";
|
|
2
|
+
interface CalendarEntryBase {
|
|
3
|
+
key: string;
|
|
4
|
+
task: ScheduledTaskSummary;
|
|
5
|
+
at: string;
|
|
6
|
+
date: string;
|
|
7
|
+
}
|
|
8
|
+
export type CalendarEntry<T extends ScheduledTaskRunFactView = ScheduledTaskRunView> = CalendarEntryBase & ({
|
|
9
|
+
kind: "planned";
|
|
10
|
+
waiting: boolean;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "execution";
|
|
13
|
+
waiting: false;
|
|
14
|
+
run: T;
|
|
15
|
+
});
|
|
16
|
+
export interface CalendarProjection<T extends ScheduledTaskRunFactView = ScheduledTaskRunView> {
|
|
17
|
+
entries: CalendarEntry<T>[];
|
|
18
|
+
issues: string[];
|
|
19
|
+
truncated: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Future projections only: saved next trigger first, then the canonical calendar-time engine.
|
|
22
|
+
* Never infer past executions from the current recurrence rule. This does not schedule work. */
|
|
23
|
+
export declare function calendarEntries<T extends ScheduledTaskRunFactView = ScheduledTaskRunView>(tasks: readonly ScheduledTaskSummary[], start: string, end: string, now: Date, displayZone: string, limit?: number): CalendarProjection<T>;
|
|
24
|
+
/** Historical bars are immutable execution facts. Their actual claim time wins
|
|
25
|
+
* over a projected duplicate; old recurrences are never retroactively expanded. */
|
|
26
|
+
export declare function calendarData<T extends ScheduledTaskRunFactView>(tasks: readonly ScheduledTaskSummary[], records: Readonly<Record<string, readonly T[]>>, start: string, end: string, now: Date, zone: string): CalendarProjection<T>;
|
|
27
|
+
export interface WeekPosition<T extends ScheduledTaskRunFactView = ScheduledTaskRunView> {
|
|
28
|
+
entry: CalendarEntry<T>;
|
|
29
|
+
day: number;
|
|
30
|
+
minute: number;
|
|
31
|
+
lane: number;
|
|
32
|
+
lanes: number;
|
|
33
|
+
}
|
|
34
|
+
/** Lane collisions are based on readable marker height, NOT invented execution
|
|
35
|
+
* duration. Every marker remains anchored to its own exact wall-clock minute. */
|
|
36
|
+
export declare function weekPositions<T extends ScheduledTaskRunFactView>(entries: readonly CalendarEntry<T>[], weekStart: string, zone: string, hourPixels?: number, markerHeight?: number): WeekPosition<T>[];
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { nextScheduledTime } from "./scheduled-task-time.js";
|
|
2
|
+
import { isScheduled } from "./scheduled-task-agenda.js";
|
|
3
|
+
import { dateKey } from "./scheduled-task-activity.js";
|
|
4
|
+
/** Future projections only: saved next trigger first, then the canonical calendar-time engine.
|
|
5
|
+
* Never infer past executions from the current recurrence rule. This does not schedule work. */
|
|
6
|
+
export function calendarEntries(tasks, start, end, now, displayZone, limit = 1000) {
|
|
7
|
+
const result = { entries: [], issues: [], truncated: false };
|
|
8
|
+
const lower = Date.parse(`${start}T00:00:00Z`) - 36 * 3_600_000;
|
|
9
|
+
const upper = Date.parse(`${end}T23:59:59Z`) + 36 * 3_600_000;
|
|
10
|
+
if (!Number.isFinite(lower) || !Number.isFinite(upper) || end < start || upper - lower > 70 * 86_400_000)
|
|
11
|
+
throw new Error("日历范围应限制在当前月的日期网格。");
|
|
12
|
+
limit = Number.isFinite(limit) ? Math.max(1, Math.min(1000, Math.trunc(limit))) : 1000;
|
|
13
|
+
const append = (task, at) => {
|
|
14
|
+
const date = dateKey(at, displayZone);
|
|
15
|
+
if (date < start || date > end)
|
|
16
|
+
return true;
|
|
17
|
+
if (result.entries.length >= limit) {
|
|
18
|
+
result.truncated = true;
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
result.entries.push({ kind: "planned", key: `plan:${task.id}:${Date.parse(at)}`, task, at, date, waiting: Date.parse(at) <= now.getTime() });
|
|
22
|
+
return true;
|
|
23
|
+
};
|
|
24
|
+
taskLoop: for (const task of tasks) {
|
|
25
|
+
if (!isScheduled(task))
|
|
26
|
+
continue;
|
|
27
|
+
if (!Number.isFinite(Date.parse(task.nextRunAt))) {
|
|
28
|
+
result.issues.push(`${task.title}:计划时间待确认`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
let candidate = task.nextRunAt;
|
|
33
|
+
if (task.schedule.kind === "once") {
|
|
34
|
+
if (!append(task, candidate))
|
|
35
|
+
break;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const cursor = Math.max(now.getTime(), lower);
|
|
39
|
+
if (Date.parse(candidate) < cursor) {
|
|
40
|
+
if (Date.parse(candidate) <= now.getTime() && !append(task, candidate))
|
|
41
|
+
break;
|
|
42
|
+
candidate = nextScheduledTime(task.schedule, new Date(cursor));
|
|
43
|
+
}
|
|
44
|
+
// A single supported recurrence produces at most one future occurrence per
|
|
45
|
+
// local day. Bounded iteration also protects corrupt legacy input.
|
|
46
|
+
for (let count = 0; candidate && Date.parse(candidate) <= upper && count < 74; count++) {
|
|
47
|
+
if (!append(task, candidate))
|
|
48
|
+
break taskLoop;
|
|
49
|
+
const next = nextScheduledTime(task.schedule, new Date(candidate));
|
|
50
|
+
if (next && Date.parse(next) <= Date.parse(candidate))
|
|
51
|
+
throw new Error("计划未向后推进");
|
|
52
|
+
candidate = next;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
result.issues.push(`${task.title}:暂时无法推算日程,请核对计划与时区`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
result.entries.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
/** Historical bars are immutable execution facts. Their actual claim time wins
|
|
63
|
+
* over a projected duplicate; old recurrences are never retroactively expanded. */
|
|
64
|
+
export function calendarData(tasks, records, start, end, now, zone) {
|
|
65
|
+
const projected = calendarEntries(tasks, start, end, now, zone);
|
|
66
|
+
const known = new Set();
|
|
67
|
+
const actual = [];
|
|
68
|
+
for (const task of tasks)
|
|
69
|
+
for (const run of records[task.id] ?? []) {
|
|
70
|
+
if (!Number.isFinite(Date.parse(run.claimedAt)))
|
|
71
|
+
continue;
|
|
72
|
+
known.add(`${task.id}:${Date.parse(run.scheduledAt)}`);
|
|
73
|
+
const date = dateKey(run.claimedAt, zone);
|
|
74
|
+
if (date < start || date > end)
|
|
75
|
+
continue;
|
|
76
|
+
if (actual.length >= 1000) {
|
|
77
|
+
projected.truncated = true;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
actual.push({ kind: "execution", key: `run:${run.id}`, task, at: run.claimedAt, date, waiting: false, run });
|
|
81
|
+
}
|
|
82
|
+
const forecast = projected.entries.filter(entry => !known.has(`${entry.task.id}:${Date.parse(entry.at)}`));
|
|
83
|
+
const entries = [...actual, ...forecast].sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
84
|
+
return { ...projected, entries: entries.slice(0, 1000), truncated: projected.truncated || entries.length > 1000 };
|
|
85
|
+
}
|
|
86
|
+
/** Lane collisions are based on readable marker height, NOT invented execution
|
|
87
|
+
* duration. Every marker remains anchored to its own exact wall-clock minute. */
|
|
88
|
+
export function weekPositions(entries, weekStart, zone, hourPixels = 64, markerHeight = 44) {
|
|
89
|
+
const result = [];
|
|
90
|
+
const byDay = new Map();
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const day = Math.round((Date.parse(`${entry.date}T12:00:00Z`) - Date.parse(`${weekStart}T12:00:00Z`)) / 86_400_000);
|
|
93
|
+
if (day < 0 || day > 6)
|
|
94
|
+
continue;
|
|
95
|
+
const parts = new Intl.DateTimeFormat("en-GB", { timeZone: zone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).formatToParts(new Date(entry.at));
|
|
96
|
+
const minute = Number(parts.find(part => part.type === "hour").value) * 60 + Number(parts.find(part => part.type === "minute").value);
|
|
97
|
+
const list = byDay.get(day) ?? [];
|
|
98
|
+
list.push({ entry, minute });
|
|
99
|
+
byDay.set(day, list);
|
|
100
|
+
}
|
|
101
|
+
for (const [day, items] of byDay) {
|
|
102
|
+
items.sort((a, b) => a.minute - b.minute);
|
|
103
|
+
let cluster = [], end = -1;
|
|
104
|
+
const flush = () => {
|
|
105
|
+
const lanes = [], positioned = [];
|
|
106
|
+
for (const item of cluster) {
|
|
107
|
+
const top = item.minute * hourPixels / 60;
|
|
108
|
+
let lane = lanes.findIndex(bottom => bottom <= top);
|
|
109
|
+
if (lane < 0)
|
|
110
|
+
lane = lanes.length;
|
|
111
|
+
lanes[lane] = top + markerHeight;
|
|
112
|
+
positioned.push({ ...item, day, lane, lanes: 0 });
|
|
113
|
+
}
|
|
114
|
+
for (const item of positioned)
|
|
115
|
+
result.push({ ...item, lanes: lanes.length });
|
|
116
|
+
};
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
const top = item.minute * hourPixels / 60;
|
|
119
|
+
if (cluster.length && top >= end) {
|
|
120
|
+
flush();
|
|
121
|
+
cluster = [];
|
|
122
|
+
end = -1;
|
|
123
|
+
}
|
|
124
|
+
cluster.push(item);
|
|
125
|
+
end = Math.max(end, top + markerHeight);
|
|
126
|
+
}
|
|
127
|
+
if (cluster.length)
|
|
128
|
+
flush();
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ScheduledTaskReadRange } from "./scheduled-task-types.js";
|
|
2
|
+
export declare function scheduledReadZone(value: unknown): string;
|
|
3
|
+
export declare function scheduledReadRange(from: unknown, to: unknown, zone: unknown, limit?: number): ScheduledTaskReadRange;
|
|
4
|
+
export declare function scheduledActivityRange(now: Date, zone: unknown): ScheduledTaskReadRange;
|
|
5
|
+
/** Offset guard band lets SQLite use the normalized date index; exact IANA date
|
|
6
|
+
* filtering follows in the service, without approximating local days as 24h. */
|
|
7
|
+
export declare function scheduledReadBounds(range: ScheduledTaskReadRange): [string, string];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { addDateDays, dateKey } from "./scheduled-task-activity.js";
|
|
2
|
+
export function scheduledReadZone(value) {
|
|
3
|
+
if (typeof value !== "string" || value.length > 100)
|
|
4
|
+
throw new Error("请提供有效的显示时区。");
|
|
5
|
+
try {
|
|
6
|
+
new Intl.DateTimeFormat("en", { timeZone: value }).format();
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
throw new Error("请提供有效的显示时区。");
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
function dateOnly(value) {
|
|
14
|
+
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value)
|
|
15
|
+
|| !Number.isFinite(Date.parse(`${value}T12:00:00Z`)) || new Date(`${value}T12:00:00Z`).toISOString().slice(0, 10) !== value)
|
|
16
|
+
throw new Error("日期须为有效的 YYYY-MM-DD。");
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
export function scheduledReadRange(from, to, zone, limit = 1000) {
|
|
20
|
+
const start = dateOnly(from), end = dateOnly(to), timeZone = scheduledReadZone(zone);
|
|
21
|
+
const days = (Date.parse(`${end}T12:00:00Z`) - Date.parse(`${start}T12:00:00Z`)) / 86_400_000;
|
|
22
|
+
if (days < 0 || days > 61)
|
|
23
|
+
throw new Error("日历一次最多读取 62 天。");
|
|
24
|
+
return { from: start, to: end, timeZone, complete: true, truncated: false, limit, invalidDates: 0 };
|
|
25
|
+
}
|
|
26
|
+
export function scheduledActivityRange(now, zone) {
|
|
27
|
+
const timeZone = scheduledReadZone(zone), today = dateKey(now, timeZone);
|
|
28
|
+
const weekday = (new Date(`${today}T12:00:00Z`).getUTCDay() + 6) % 7;
|
|
29
|
+
return { from: addDateDays(today, -weekday - 77), to: addDateDays(today, 6 - weekday), timeZone, complete: true, truncated: false, limit: 5000, invalidDates: 0 };
|
|
30
|
+
}
|
|
31
|
+
/** Offset guard band lets SQLite use the normalized date index; exact IANA date
|
|
32
|
+
* filtering follows in the service, without approximating local days as 24h. */
|
|
33
|
+
export function scheduledReadBounds(range) {
|
|
34
|
+
return [new Date(Date.parse(`${range.from}T00:00:00Z`) - 36 * 3_600_000).toISOString(), new Date(Date.parse(`${range.to}T23:59:59Z`) + 36 * 3_600_000).toISOString()];
|
|
35
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConversationService } from "./conversation-service.js";
|
|
2
|
-
import type { ScheduledTaskDetail, ScheduledTaskSummary } from "./scheduled-task-types.js";
|
|
2
|
+
import type { ScheduledTaskDetail, ScheduledTaskRunView, ScheduledTaskSummary, ScheduledTaskCalendarResponse, ScheduledTaskActivityResponse } from "./scheduled-task-types.js";
|
|
3
3
|
import type { WandStorage } from "./storage.js";
|
|
4
4
|
import type { ProcessEvent, SessionSnapshot } from "./types.js";
|
|
5
5
|
export declare class ScheduledTaskConflictError extends Error {
|
|
@@ -29,6 +29,11 @@ export declare class ScheduledTaskService {
|
|
|
29
29
|
private summary;
|
|
30
30
|
list(includeCancelled?: boolean): ScheduledTaskSummary[];
|
|
31
31
|
get(id: string): ScheduledTaskDetail;
|
|
32
|
+
getRun(taskId: string, runId: string): ScheduledTaskRunView;
|
|
33
|
+
private fact;
|
|
34
|
+
private facts;
|
|
35
|
+
activity(id: string, timeZone: unknown): ScheduledTaskActivityResponse;
|
|
36
|
+
calendar(from: unknown, to: unknown, timeZone: unknown): ScheduledTaskCalendarResponse;
|
|
32
37
|
create(value: unknown): ScheduledTaskSummary;
|
|
33
38
|
update(id: string, value: unknown): ScheduledTaskSummary;
|
|
34
39
|
private reconcile;
|
|
@@ -5,6 +5,9 @@ import { requireTeamEmployee } from "./ai-team-employee-binding.js";
|
|
|
5
5
|
import { employeeConversationId } from "./conversation-types.js";
|
|
6
6
|
import { getErrorMessage } from "./error-utils.js";
|
|
7
7
|
import { nextScheduledTime, parseTaskSchedule } from "./scheduled-task-time.js";
|
|
8
|
+
import { dateKey } from "./scheduled-task-activity.js";
|
|
9
|
+
import { calendarData } from "./scheduled-task-calendar.js";
|
|
10
|
+
import { scheduledActivityRange, scheduledReadBounds, scheduledReadRange } from "./scheduled-task-read-range.js";
|
|
8
11
|
import { bodyObject } from "./server-request.js";
|
|
9
12
|
import { resolveSessionCwd } from "./session-cwd.js";
|
|
10
13
|
import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
|
|
@@ -93,7 +96,7 @@ export class ScheduledTaskService {
|
|
|
93
96
|
}
|
|
94
97
|
const workspace = task.workspaceId ? this.deps.storage.getWorkspace(task.workspaceId) : null;
|
|
95
98
|
const directory = this.executionDirectory(task);
|
|
96
|
-
const lastRun = this.deps.storage.
|
|
99
|
+
const lastRun = this.deps.storage.latestScheduledTaskRun(task.id);
|
|
97
100
|
return { ...task, employeeName: employee?.name ?? "员工已不可用", employeeUnavailableReason,
|
|
98
101
|
workspaceName: task.workspaceId ? workspace?.name ?? "项目已不可用" : "服务默认目录",
|
|
99
102
|
workspaceCwd: directory.cwd, workspaceUnavailableReason: directory.error,
|
|
@@ -105,6 +108,63 @@ export class ScheduledTaskService {
|
|
|
105
108
|
get(id) {
|
|
106
109
|
return { task: this.summary(this.requireTask(id)), runs: this.deps.storage.listScheduledTaskRuns(id).map(run => this.runView(run)) };
|
|
107
110
|
}
|
|
111
|
+
getRun(taskId, runId) {
|
|
112
|
+
this.requireTask(taskId);
|
|
113
|
+
const run = this.deps.storage.getScheduledTaskRun(runId);
|
|
114
|
+
if (!run || run.taskId !== taskId)
|
|
115
|
+
throw new ScheduledTaskNotFoundError("执行记录不存在。");
|
|
116
|
+
return this.runView(run);
|
|
117
|
+
}
|
|
118
|
+
fact(run) {
|
|
119
|
+
const view = this.runView(run);
|
|
120
|
+
const { id, taskId, scheduledAt, claimedAt, executionFinishedAt, executionOutcome, employeeId, workspaceId, cwd, state, executionStatus, sessionId } = view;
|
|
121
|
+
return { id, taskId, scheduledAt, claimedAt, executionFinishedAt, executionOutcome, employeeId, workspaceId, cwd, state, executionStatus, sessionId,
|
|
122
|
+
error: view.error && view.error.length > 8000 ? `${view.error.slice(0, 8000)}…(完整错误请查看本次记录或会话)` : view.error };
|
|
123
|
+
}
|
|
124
|
+
facts(range, taskId) {
|
|
125
|
+
const [from, to] = scheduledReadBounds(range);
|
|
126
|
+
const raw = this.deps.storage.listScheduledTaskRunFacts(from, to, taskId, range.limit + 1);
|
|
127
|
+
range.truncated = raw.length > range.limit;
|
|
128
|
+
range.invalidDates = this.deps.storage.countInvalidScheduledTaskRunDates(taskId);
|
|
129
|
+
range.complete = !range.truncated && range.invalidDates === 0;
|
|
130
|
+
return raw.slice(0, range.limit).filter(run => {
|
|
131
|
+
if (!Number.isFinite(Date.parse(run.claimedAt))) {
|
|
132
|
+
range.complete = false;
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const day = dateKey(run.claimedAt, range.timeZone);
|
|
136
|
+
return day >= range.from && day <= range.to;
|
|
137
|
+
}).map(run => this.fact(run));
|
|
138
|
+
}
|
|
139
|
+
activity(id, timeZone) {
|
|
140
|
+
this.requireTask(id);
|
|
141
|
+
const now = this.now(), range = scheduledActivityRange(now, timeZone);
|
|
142
|
+
return { runs: this.facts(range, id), range, generatedAt: now.toISOString() };
|
|
143
|
+
}
|
|
144
|
+
calendar(from, to, timeZone) {
|
|
145
|
+
const now = this.now(), range = scheduledReadRange(from, to, timeZone);
|
|
146
|
+
const tasks = this.list(true), facts = this.facts(range);
|
|
147
|
+
const records = {};
|
|
148
|
+
for (const run of facts)
|
|
149
|
+
(records[run.taskId] ??= []).push(run);
|
|
150
|
+
const projection = calendarData(tasks, records, range.from, range.to, now, range.timeZone);
|
|
151
|
+
range.truncated ||= projection.truncated;
|
|
152
|
+
const taskIDs = new Set(tasks.map(task => task.id));
|
|
153
|
+
const orphaned = facts.filter(run => !taskIDs.has(run.taskId)).length;
|
|
154
|
+
range.complete &&= !range.truncated && orphaned === 0 && projection.issues.length === 0;
|
|
155
|
+
const issues = [...projection.issues];
|
|
156
|
+
if (range.truncated)
|
|
157
|
+
issues.push("历史或预计安排已达到读取上限,当前范围可能不完整,请缩小日期范围。");
|
|
158
|
+
if (range.invalidDates)
|
|
159
|
+
issues.push(`${range.invalidDates} 条旧记录时间待确认,未绘入日期格。`);
|
|
160
|
+
if (orphaned)
|
|
161
|
+
issues.push(`${orphaned} 条记录的原计划已不可用。`);
|
|
162
|
+
return { tasks, entries: projection.entries.map(({ task, ...entry }) => {
|
|
163
|
+
const parts = new Intl.DateTimeFormat("en-GB", { timeZone: range.timeZone, hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).formatToParts(new Date(entry.at));
|
|
164
|
+
const minuteOfDay = Number(parts.find(part => part.type === "hour").value) * 60 + Number(parts.find(part => part.type === "minute").value);
|
|
165
|
+
return { ...entry, taskId: task.id, minuteOfDay };
|
|
166
|
+
}), range, generatedAt: now.toISOString(), issues };
|
|
167
|
+
}
|
|
108
168
|
create(value) {
|
|
109
169
|
const input = bodyObject(value);
|
|
110
170
|
if (typeof input.requestId !== "string" || !/^[\w:-]{8,80}$/.test(input.requestId))
|
|
@@ -3,7 +3,7 @@ import type { ScheduledTaskRun, ScheduledTaskRunView } from "./scheduled-task-ty
|
|
|
3
3
|
import type { SessionSnapshot } from "./types.js";
|
|
4
4
|
export declare function sessionActive(session: SessionSnapshot): boolean;
|
|
5
5
|
/** Pure read projection; it never settles or replays a scheduled occurrence. */
|
|
6
|
-
export declare function scheduledExecutionStatus(run: ScheduledTaskRun, receipt: ConversationReceipt | null, session: SessionSnapshot | null): ScheduledTaskRunView["executionStatus"];
|
|
6
|
+
export declare function scheduledExecutionStatus(run: Pick<ScheduledTaskRun, "state" | "executionOutcome" | "executionFinishedAt">, receipt: ConversationReceipt | null, session: SessionSnapshot | null): ScheduledTaskRunView["executionStatus"];
|
|
7
7
|
/** Settles only observed facts; missing/unknown acceptance never authorizes replay. */
|
|
8
8
|
export declare function reconcileScheduledRun(run: ScheduledTaskRun, receipt: ConversationReceipt | null, session: SessionSnapshot | null, now: string, options: {
|
|
9
9
|
recoverInactive: boolean;
|
|
@@ -69,3 +69,41 @@ export interface ScheduledTaskDetail {
|
|
|
69
69
|
task: ScheduledTaskSummary;
|
|
70
70
|
runs: ScheduledTaskRunView[];
|
|
71
71
|
}
|
|
72
|
+
/** Bounded read projections deliberately exclude prompts, receipts and request IDs. */
|
|
73
|
+
export type ScheduledTaskRunFactView = Pick<ScheduledTaskRunView, "id" | "taskId" | "scheduledAt" | "claimedAt" | "executionFinishedAt" | "executionOutcome" | "employeeId" | "workspaceId" | "cwd" | "state" | "error" | "executionStatus" | "sessionId">;
|
|
74
|
+
export type ScheduledTaskRunFact = Omit<ScheduledTaskRun, "prompt">;
|
|
75
|
+
export interface ScheduledTaskReadRange {
|
|
76
|
+
from: string;
|
|
77
|
+
to: string;
|
|
78
|
+
timeZone: string;
|
|
79
|
+
complete: boolean;
|
|
80
|
+
truncated: boolean;
|
|
81
|
+
limit: number;
|
|
82
|
+
invalidDates: number;
|
|
83
|
+
}
|
|
84
|
+
export type ScheduledCalendarItem = {
|
|
85
|
+
key: string;
|
|
86
|
+
taskId: string;
|
|
87
|
+
at: string;
|
|
88
|
+
date: string;
|
|
89
|
+
minuteOfDay: number;
|
|
90
|
+
} & ({
|
|
91
|
+
kind: "planned";
|
|
92
|
+
waiting: boolean;
|
|
93
|
+
} | {
|
|
94
|
+
kind: "execution";
|
|
95
|
+
waiting: false;
|
|
96
|
+
run: ScheduledTaskRunFactView;
|
|
97
|
+
});
|
|
98
|
+
export interface ScheduledTaskCalendarResponse {
|
|
99
|
+
tasks: ScheduledTaskSummary[];
|
|
100
|
+
entries: ScheduledCalendarItem[];
|
|
101
|
+
range: ScheduledTaskReadRange;
|
|
102
|
+
generatedAt: string;
|
|
103
|
+
issues: string[];
|
|
104
|
+
}
|
|
105
|
+
export interface ScheduledTaskActivityResponse {
|
|
106
|
+
runs: ScheduledTaskRunFactView[];
|
|
107
|
+
range: ScheduledTaskReadRange;
|
|
108
|
+
generatedAt: string;
|
|
109
|
+
}
|
|
@@ -63,8 +63,9 @@ export function registerAssetRoutes(app, deps) {
|
|
|
63
63
|
res.status(404).type("text/plain").send("Browser module not found; reload the page after updating.");
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
//
|
|
67
|
-
//
|
|
66
|
+
// Each directory fingerprints a module's static dependency component.
|
|
67
|
+
// Legacy whole-build directories remain readable from retained snapshots.
|
|
68
|
+
// Never substitute current bytes for a missing older immutable URL.
|
|
68
69
|
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
69
70
|
res.type("application/javascript").send(content);
|
|
70
71
|
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Express } from "express";
|
|
2
|
+
import { type ConversationOrganizationDependencies } from "./conversation-organize-service.js";
|
|
3
|
+
import type { WandStorage } from "./storage.js";
|
|
4
|
+
export declare function registerConversationOrganizeRoutes(app: Express, storage: WandStorage, deps: ConversationOrganizationDependencies): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { asyncRoute } from "./express-async.js";
|
|
2
|
+
import { badRequest, conflict, notFound } from "./http-error.js";
|
|
3
|
+
import { conversationOrganizationService } from "./conversation-organize-service.js";
|
|
4
|
+
function scopeFrom(body, storage) {
|
|
5
|
+
if (body.mode !== undefined && body.mode !== "auto" && body.mode !== "all")
|
|
6
|
+
throw badRequest("请选择默认范围或所有工作会话。");
|
|
7
|
+
const ids = (value) => {
|
|
8
|
+
if (value === undefined)
|
|
9
|
+
return undefined;
|
|
10
|
+
if (!Array.isArray(value) || value.length > 500 || value.some(id => typeof id !== "string" || !id.trim()))
|
|
11
|
+
throw badRequest("会话/任务ID列表无效或超过500个。");
|
|
12
|
+
return [...new Set(value)];
|
|
13
|
+
};
|
|
14
|
+
const taskIds = ids(body.taskIds), sessionIds = ids(body.sessionIds);
|
|
15
|
+
let target;
|
|
16
|
+
if (body.target !== undefined) {
|
|
17
|
+
if (!body.target || typeof body.target !== "object" || Array.isArray(body.target) || taskIds || sessionIds)
|
|
18
|
+
throw badRequest("目录范围与多选范围不能混用。");
|
|
19
|
+
const value = body.target;
|
|
20
|
+
if (value.kind === "all")
|
|
21
|
+
target = { kind: "all" };
|
|
22
|
+
else if (value.kind === "workspace" && typeof value.workspaceId === "string" && storage.getWorkspace(value.workspaceId))
|
|
23
|
+
target = { kind: "workspace", workspaceId: value.workspaceId };
|
|
24
|
+
else if (value.kind === "directory" && typeof value.cwd === "string" && value.cwd.trim())
|
|
25
|
+
target = { kind: "directory", cwd: value.cwd };
|
|
26
|
+
else
|
|
27
|
+
throw badRequest("会话归类目录无效。");
|
|
28
|
+
}
|
|
29
|
+
if (!target && !taskIds?.length && !sessionIds?.length)
|
|
30
|
+
throw badRequest("请选择会话、任务或目录范围。");
|
|
31
|
+
return { mode: body.mode === "all" ? "all" : "auto", target, taskIds, sessionIds };
|
|
32
|
+
}
|
|
33
|
+
export function registerConversationOrganizeRoutes(app, storage, deps) {
|
|
34
|
+
const service = conversationOrganizationService(storage, deps);
|
|
35
|
+
app.post("/api/sessions/auto-organize", asyncRoute(async (req, res) => {
|
|
36
|
+
const body = (req.body ?? {});
|
|
37
|
+
if (body.action === "start") {
|
|
38
|
+
if (typeof body.token !== "string" || !body.token.trim() || body.force !== undefined && typeof body.force !== "boolean")
|
|
39
|
+
throw badRequest("确认令牌或执行参数无效。");
|
|
40
|
+
try {
|
|
41
|
+
res.status(202).json({ kind: "job", job: service.start(body.token, body.force === true) });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw conflict(error instanceof Error ? error.message : "会话归类预览已失效。");
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (body.action !== undefined && body.action !== "preview")
|
|
49
|
+
throw badRequest("会话归类操作无效。");
|
|
50
|
+
try {
|
|
51
|
+
res.json({ kind: "preview", preview: service.prepare(scopeFrom(body, storage)) });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw badRequest(error instanceof Error ? error.message : "无法读取会话归类范围。");
|
|
55
|
+
}
|
|
56
|
+
}));
|
|
57
|
+
app.get("/api/sessions/auto-organize/by-token/:token", (req, res) => {
|
|
58
|
+
const job = storage.getConversationOrganizationJobByToken(req.params.token);
|
|
59
|
+
if (!job)
|
|
60
|
+
throw notFound("尚未找到该确认请求的已接受作业;不会自动重发。");
|
|
61
|
+
res.json({ job: service.get(job.id) ?? job });
|
|
62
|
+
});
|
|
63
|
+
app.get("/api/sessions/auto-organize/:id", (req, res) => {
|
|
64
|
+
const job = service.get(req.params.id);
|
|
65
|
+
if (!job)
|
|
66
|
+
throw notFound("归类作业不存在。");
|
|
67
|
+
res.json({ job });
|
|
68
|
+
});
|
|
69
|
+
app.post("/api/sessions/auto-organize/:id/cancel", (req, res) => {
|
|
70
|
+
const job = service.cancel(req.params.id);
|
|
71
|
+
if (!job)
|
|
72
|
+
throw notFound("归类作业不存在。");
|
|
73
|
+
res.json({ job });
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { AiTeamConflictError } from "./ai-team-runner.js";
|
|
3
|
+
import { parseConversationExecutionPreference } from "./conversation-execution-preference.js";
|
|
3
4
|
import { asyncRoute } from "./express-async.js";
|
|
4
5
|
import { bodyObject, sendRouteError, text } from "./server-request.js";
|
|
5
6
|
import { badRequest, notFound } from "./http-error.js";
|
|
@@ -68,7 +69,7 @@ export function registerConversationRoutes(app, service) {
|
|
|
68
69
|
post("/api/conversations", (_id, body) => service.createGroup(text(body.requestId), bodyObject(body.group)));
|
|
69
70
|
post("/api/conversations/:id/invitations", (id, body) => service.invite(id, text(body.requestId), Number(body.memberVersion), bodyObject(body.group)));
|
|
70
71
|
post("/api/conversations/:id/channel", (id, body) => service.prepare(id, text(body.requestId), text(body.cwd) || undefined));
|
|
71
|
-
post("/api/conversations/:id/messages", (id, body) => service.send(id, text(body.requestId), text(body.input), targetOf(body.target), text(body.cwd) || undefined));
|
|
72
|
+
post("/api/conversations/:id/messages", (id, body) => service.send(id, text(body.requestId), text(body.input), targetOf(body.target), text(body.cwd) || undefined, true, undefined, parseConversationExecutionPreference(body.executionPreference)));
|
|
72
73
|
post("/api/conversations/:id/tasks", (id, body) => service.dispatch(id, text(body.requestId), {
|
|
73
74
|
title: text(body.title), description: text(body.description), workspaceId: text(body.workspaceId), cwd: text(body.cwd) || undefined,
|
|
74
75
|
memberVersion: body.memberVersion === undefined ? undefined : Number(body.memberVersion),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Express, RequestHandler } from "express";
|
|
2
|
+
import type { EmployeeImService } from "./employee-im.js";
|
|
3
|
+
import type { WandStorage } from "./storage.js";
|
|
4
|
+
/** Credentials are admin-only, write-only and never part of employee list/detail responses. */
|
|
5
|
+
export declare function registerEmployeeImRoutes(app: Express, deps: {
|
|
6
|
+
storage: WandStorage;
|
|
7
|
+
im: EmployeeImService;
|
|
8
|
+
requireAdmin: RequestHandler;
|
|
9
|
+
}): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { asyncRoute } from "./express-async.js";
|
|
2
|
+
import { bodyObject } from "./server-request.js";
|
|
3
|
+
import { EmployeeImConfigError } from "./employee-im-config.js";
|
|
4
|
+
/** Credentials are admin-only, write-only and never part of employee list/detail responses. */
|
|
5
|
+
export function registerEmployeeImRoutes(app, deps) {
|
|
6
|
+
const path = "/api/silicon-employees/:id/im";
|
|
7
|
+
app.use(path, (_req, res, next) => { res.setHeader("Cache-Control", "no-store"); next(); }, deps.requireAdmin, (req, res, next) => {
|
|
8
|
+
if (!deps.storage.getSiliconEmployeeDefinition(String(req.params.id))) {
|
|
9
|
+
res.status(404).json({ error: "员工不存在。" });
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
next();
|
|
13
|
+
});
|
|
14
|
+
app.get(path, (req, res) => { res.json(deps.storage.getEmployeeImConfig(String(req.params.id))); });
|
|
15
|
+
app.put(path, (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
res.json(deps.storage.saveEmployeeImConfig(String(req.params.id), bodyObject(req.body)));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// Validation errors never contain request values. Crypto/storage failures receive a fixed safe message.
|
|
21
|
+
const message = error instanceof EmployeeImConfigError
|
|
22
|
+
? error.message : "IM 配置保存失败,请检查本机密钥与存储状态。";
|
|
23
|
+
res.status(400).json({ error: message });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
app.post(`${path}/test`, asyncRoute(async (req, res) => {
|
|
27
|
+
const provider = bodyObject(req.body).provider;
|
|
28
|
+
if (provider !== "telegram" && provider !== "dingtalk") {
|
|
29
|
+
res.status(400).json({ error: "请选择 Telegram 或钉钉。" });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
await deps.im.test(String(req.params.id), provider);
|
|
34
|
+
res.json({ ok: true });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
res.status(502).json({ error: "测试消息发送未确认,请检查已保存的配置与网络;不会自动重发。" });
|
|
38
|
+
}
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
@@ -30,7 +30,7 @@ export function employeeDraftAiOptions(storage, config, free) {
|
|
|
30
30
|
command: provider === "qoder" ? "qodercli" : provider,
|
|
31
31
|
selectedModel: null,
|
|
32
32
|
thinkingEffort: config.defaultThinkingEffort,
|
|
33
|
-
}, config, hr, free), opsPersona: hr.prompt };
|
|
33
|
+
}, config, hr, free, { storage }), opsPersona: hr.prompt };
|
|
34
34
|
}
|
|
35
35
|
function sendEmployeeError(res, error, context) {
|
|
36
36
|
const message = getErrorMessage(error, "硅基员工操作失败。");
|