@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
|
@@ -7,6 +7,30 @@ function failure(res, error) {
|
|
|
7
7
|
/** Authentication and the sessions capability are enforced by the server route family. */
|
|
8
8
|
export function registerScheduledTaskRoutes(app, service) {
|
|
9
9
|
app.get("/api/scheduled-tasks", (req, res) => { res.json({ tasks: service.list(req.query.includeCancelled === "1") }); });
|
|
10
|
+
app.get("/api/scheduled-tasks/calendar", (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
res.json(service.calendar(req.query.from, req.query.to, req.query.timeZone));
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
failure(res, error);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
app.get("/api/scheduled-tasks/:id/activity", (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
res.json(service.activity(req.params.id, req.query.timeZone));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
failure(res, error);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
app.get("/api/scheduled-tasks/:id/runs/:runId", (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
res.json({ run: service.getRun(req.params.id, req.params.runId) });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
failure(res, error);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
10
34
|
app.get("/api/scheduled-tasks/:id", (req, res) => {
|
|
11
35
|
try {
|
|
12
36
|
res.json(service.get(req.params.id));
|
|
@@ -11,6 +11,7 @@ import { isProviderSessionId } from "./resume-policy.js";
|
|
|
11
11
|
import { sendRouteError } from "./server-request.js";
|
|
12
12
|
import { isSessionProvider } from "./session-provider.js";
|
|
13
13
|
import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
|
|
14
|
+
import { parseConversationExecutionPreference, withConversationExecutionPreference } from "./conversation-execution-preference.js";
|
|
14
15
|
import { projectCwdForSession, resolveWorkspaceIdForNewSession } from "./workspace-binding.js";
|
|
15
16
|
import { badRequest, surfaceDomainError } from "./http-error.js";
|
|
16
17
|
import { getInputDebugMeta, startResumedPtySession, restoreArchivedSession } from "./session-service.js";
|
|
@@ -36,7 +37,11 @@ export function registerSessionLifecycleRoutes(app, ctx) {
|
|
|
36
37
|
if (employeeId && (!employee || employee.archivedAt))
|
|
37
38
|
throw badRequest("硅基员工不存在或已归档。");
|
|
38
39
|
const selectedEmployeeCandidate = employee ? selectEmployeeCandidate(employee) : null;
|
|
39
|
-
const
|
|
40
|
+
const manualPreference = body.overrideCli === true ? parseConversationExecutionPreference({
|
|
41
|
+
provider: body.provider, engine: body.engine, model: body.model || "default", thinkingEffort: body.thinkingEffort || "off",
|
|
42
|
+
}) : undefined;
|
|
43
|
+
const executionEmployee = employee && manualPreference ? withConversationExecutionPreference(employee, manualPreference) : employee;
|
|
44
|
+
const employeeAgent = manualPreference && executionEmployee ? executionEmployee.agents[0] : selectedEmployeeCandidate?.agent;
|
|
40
45
|
if (body.provider && !isSessionProvider(body.provider)) {
|
|
41
46
|
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok、Qoder 或 Pi provider。" });
|
|
42
47
|
return;
|
|
@@ -92,8 +97,8 @@ export function registerSessionLifecycleRoutes(app, ctx) {
|
|
|
92
97
|
// 角色与规则走系统提示通道,不拼进 prompt(见 AGENTS.md「Session 输入契约」)。
|
|
93
98
|
systemPrompt: role?.prompt ?? body.systemPrompt,
|
|
94
99
|
...sessionEmployeeFields(role),
|
|
95
|
-
employeeCandidates:
|
|
96
|
-
employeeCandidateIndex:
|
|
100
|
+
employeeCandidates: executionEmployee?.agents,
|
|
101
|
+
employeeCandidateIndex: manualPreference ? 0 : selectedEmployeeCandidate?.index,
|
|
97
102
|
...(engineDecision ? { engine: engineDecision.engine } : {}),
|
|
98
103
|
...origin,
|
|
99
104
|
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSystemAiCall, SYSTEM_AI_READ_ONLY } from "./system-ai-call.js";
|
|
1
2
|
import { registerPiRecommendationRoute } from "./server-pi-recommendation-routes.js";
|
|
2
3
|
export { parseExecutionMode, parseSessionCreationOrigin } from "./session-request.js";
|
|
3
4
|
export * from "./session-service.js";
|
|
@@ -10,6 +11,23 @@ import { registerSessionWorktreeRoutes } from "./server-session-worktree-routes.
|
|
|
10
11
|
import { createSessionRouteContext } from "./server-session-context.js";
|
|
11
12
|
/** Registers all `/api/sessions` resource families. Order inside each family is preserved. */
|
|
12
13
|
export function registerSessionRoutes(app, deps) {
|
|
14
|
+
// Historical system calls have no interactive runner. Guard every mutation family, not only Send.
|
|
15
|
+
for (const prefix of ["/api/sessions/:id", "/api/structured-sessions/:id"]) {
|
|
16
|
+
app.use(prefix, (req, res, next) => {
|
|
17
|
+
const session = deps.sessions.getSlim(req.params.id);
|
|
18
|
+
if (!isSystemAiCall(session) || req.method === "GET" || req.method === "HEAD") {
|
|
19
|
+
next();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const allowed = req.method === "POST" && ["/archive", "/unarchive", "/completion/view"].includes(req.path)
|
|
23
|
+
|| req.method === "DELETE" && req.path === "/" && session?.status !== "running";
|
|
24
|
+
if (allowed) {
|
|
25
|
+
next();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
res.status(409).json({ error: SYSTEM_AI_READ_ONLY, code: "system_call_read_only" });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
13
31
|
registerPiRecommendationRoute(app, { structured: deps.structured, sessions: deps.sessions, config: deps.config, decisions: deps.decisions });
|
|
14
32
|
const ctx = createSessionRouteContext(deps);
|
|
15
33
|
registerSessionListRoutes(app, ctx);
|
|
@@ -110,7 +110,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
|
|
|
110
110
|
}
|
|
111
111
|
const body = (req.body ?? {});
|
|
112
112
|
try {
|
|
113
|
-
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free);
|
|
113
|
+
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage });
|
|
114
114
|
// 自动 message + 迭代模式:拿本轮提示词当输入;提交成功后标记已用掉。
|
|
115
115
|
const context = await iterationCommitInput(storage, snapshot, body);
|
|
116
116
|
const result = await runQuickCommitWithFallback({
|
|
@@ -194,7 +194,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
|
|
|
194
194
|
}
|
|
195
195
|
const body = (req.body ?? {});
|
|
196
196
|
try {
|
|
197
|
-
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free);
|
|
197
|
+
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage });
|
|
198
198
|
const context = await iterationCommitInput(storage, snapshot, body);
|
|
199
199
|
const result = await generateCommitMessageOnly(snapshot.cwd, config.language ?? "", {
|
|
200
200
|
...ai,
|
|
@@ -231,7 +231,7 @@ export function registerSessionWorktreeRoutes(app, ctx) {
|
|
|
231
231
|
}
|
|
232
232
|
const body = (req.body ?? {});
|
|
233
233
|
try {
|
|
234
|
-
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free);
|
|
234
|
+
const ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage });
|
|
235
235
|
const result = await runTagHead({
|
|
236
236
|
cwd: snapshot.cwd,
|
|
237
237
|
language: config.language ?? "",
|
|
@@ -3,6 +3,7 @@ import type { SessionRegistry } from "./session-registry.js";
|
|
|
3
3
|
import type { WandStorage } from "./storage.js";
|
|
4
4
|
import { type LayoutNode, type TaskWindowLayout } from "./types.js";
|
|
5
5
|
import { type AutoTaskTitleOptions } from "./task-title-service.js";
|
|
6
|
+
import type { ConversationGroupingJudge } from "./conversation-organize.js";
|
|
6
7
|
import { type TitleJudge } from "./task-title-organize.js";
|
|
7
8
|
/** 校验并清洗前端提交的布局树;非法输入返回 null(空工作空间)。 */
|
|
8
9
|
export declare function sanitizeLayout(value: unknown): LayoutNode | null;
|
|
@@ -17,4 +18,6 @@ export declare function sanitizeTaskLayout(value: unknown): TaskWindowLayout | n
|
|
|
17
18
|
*/
|
|
18
19
|
export declare function registerWorkspaceRoutes(app: Express, storage: WandStorage, sessions?: SessionRegistry, titleOptions?: AutoTaskTitleOptions, organize?: {
|
|
19
20
|
judge?: TitleJudge;
|
|
21
|
+
conversationJudge?: ConversationGroupingJudge;
|
|
22
|
+
summarize?: (text: string, index: number, total: number, signal: AbortSignal) => Promise<string>;
|
|
20
23
|
}): void;
|
|
@@ -20,6 +20,8 @@ import { firstLayoutTabId, wrapLegacyTaskLayout } from "./layout-tree.js";
|
|
|
20
20
|
import { parentTaskIdFrom } from "./task-agent-parsing.js";
|
|
21
21
|
import { taskTitleService, refreshAutoBoardTaskTitles } from "./task-title-service.js";
|
|
22
22
|
import { organizeSelectedByTitle, titleJudgeForStorage } from "./task-title-organize-service.js";
|
|
23
|
+
import { systemAiSessions } from "./system-ai-session-recorder.js";
|
|
24
|
+
import { registerConversationOrganizeRoutes } from "./server-conversation-organize-routes.js";
|
|
23
25
|
import { TITLE_ORGANIZE_MAX_SESSIONS, TITLE_ORGANIZE_MAX_TASKS } from "./task-title-organize.js";
|
|
24
26
|
import { provisionalTaskTitleFromDescription } from "./task-title.js";
|
|
25
27
|
import { badRequest, surfaceDomainError } from "./http-error.js";
|
|
@@ -326,6 +328,12 @@ export function sanitizeTaskLayout(value) {
|
|
|
326
328
|
*/
|
|
327
329
|
export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {}, organize = {}) {
|
|
328
330
|
taskTitleService(storage, titleOptions);
|
|
331
|
+
registerConversationOrganizeRoutes(app, storage, {
|
|
332
|
+
titleOptions, judge: organize.conversationJudge, summarize: organize.summarize,
|
|
333
|
+
listSessions: () => sessions?.listSlim() ?? storage.loadSessionsSlim(),
|
|
334
|
+
getSession: id => sessions?.get(id) ?? storage.getSession(id),
|
|
335
|
+
refreshSession: id => sessions?.refreshSessionWorkspace(id),
|
|
336
|
+
});
|
|
329
337
|
// 列出所有项目(按创建时间降序:新建在前,打开不会改位置)
|
|
330
338
|
app.get("/api/workspaces", (req, res) => {
|
|
331
339
|
const includeArchived = req.query.includeArchived === "1";
|
|
@@ -531,10 +539,15 @@ export function registerWorkspaceRoutes(app, storage, sessions, titleOptions = {
|
|
|
531
539
|
res.status(400).json({ error: `一次最多归类 ${TITLE_ORGANIZE_MAX_TASKS} 个任务、${TITLE_ORGANIZE_MAX_SESSIONS} 个会话。` });
|
|
532
540
|
return;
|
|
533
541
|
}
|
|
542
|
+
const callSessionIds = [];
|
|
534
543
|
const result = await organizeSelectedByTitle(storage, { taskIds, sessionIds, mode: body?.mode === "all" ? "all" : "auto" }, {
|
|
535
|
-
judge: organize.judge ?? titleJudgeForStorage(storage, titleOptions),
|
|
544
|
+
judge: organize.judge ?? titleJudgeForStorage(storage, titleOptions, id => callSessionIds.push(id)),
|
|
536
545
|
refreshSession: (sessionId) => sessions?.refreshSessionWorkspace(sessionId),
|
|
537
546
|
});
|
|
547
|
+
for (const id of callSessionIds)
|
|
548
|
+
systemAiSessions(storage).appendOutcome(id, "实际任务归类结果", result);
|
|
549
|
+
res.setHeader("X-Wand-System-Call-Ids", callSessionIds.slice(0, 64).join(","));
|
|
550
|
+
res.setHeader("X-Wand-System-Call-Count", String(callSessionIds.length));
|
|
538
551
|
if (!result.foundTasks && body?.target === undefined) {
|
|
539
552
|
res.status(404).json({ error: "未找到所选任务。" });
|
|
540
553
|
return;
|
package/dist/server.js
CHANGED
|
@@ -23,6 +23,7 @@ import { coreHarnessAgentDir } from "./harness-engine.js";
|
|
|
23
23
|
import { SessionRegistry } from "./session-registry.js";
|
|
24
24
|
import { SessionCompletionTracker } from "./session-completion.js";
|
|
25
25
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
26
|
+
import { systemAiSessions } from "./system-ai-session-recorder.js";
|
|
26
27
|
import { StructuredSessionManager } from "./structured-session-manager.js";
|
|
27
28
|
import { SettingsWebAccess, registerSettingsWebAccessRoute } from "./settings-web-access.js";
|
|
28
29
|
import { DecisionService } from "./decision-service.js";
|
|
@@ -62,6 +63,9 @@ import { forwardConversationRelay, registerConversationRoutes } from "./server-c
|
|
|
62
63
|
import { registerAiTeamRoutes } from "./server-ai-team-routes.js";
|
|
63
64
|
import { registerTeamDispatchRoutes } from "./server-team-dispatch-routes.js";
|
|
64
65
|
import { registerSiliconEmployeeRoutes } from "./server-employee-routes.js";
|
|
66
|
+
import { EmployeeImService } from "./employee-im.js";
|
|
67
|
+
import { EmployeeImLifecycle } from "./employee-im-lifecycle.js";
|
|
68
|
+
import { registerEmployeeImRoutes } from "./server-employee-im-routes.js";
|
|
65
69
|
import { defaultRoleForCli } from "./default-employee.js";
|
|
66
70
|
import { UserMemoryService } from "./user-memory.js";
|
|
67
71
|
import { registerUserMemoryRoutes, userMemoryOperationLog } from "./server-user-memory.js";
|
|
@@ -107,7 +111,7 @@ const RUNTIME_ROOT_DIR = path.resolve(SERVER_MODULE_DIR, "..");
|
|
|
107
111
|
const PKG_JSON = JSON.parse(readFileSync(path.join(RUNTIME_ROOT_DIR, "package.json"), "utf8"));
|
|
108
112
|
const PKG_NAME = PKG_JSON.name;
|
|
109
113
|
const PKG_VERSION = PKG_JSON.version;
|
|
110
|
-
const PKG_NODE_REQ = PKG_JSON.engines?.node ?? ">=
|
|
114
|
+
const PKG_NODE_REQ = PKG_JSON.engines?.node ?? ">=22.19.0";
|
|
111
115
|
const PKG_REPO_URL = "https://github.com/co0ontty/wand";
|
|
112
116
|
// ── Update check cache ──
|
|
113
117
|
const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
@@ -240,6 +244,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
240
244
|
// honoured for the configured proxy hops (see resolveTrustProxy).
|
|
241
245
|
app.set("trust proxy", resolveTrustProxy(config.trustProxy));
|
|
242
246
|
const storage = new WandStorage(resolveDatabasePath(configPath));
|
|
247
|
+
const systemCalls = systemAiSessions(storage);
|
|
248
|
+
systemCalls.recoverInterrupted();
|
|
243
249
|
if (!storage.getConfigValue("migration:session-workspaces:v1")) {
|
|
244
250
|
backfillSessionWorkspaces(storage);
|
|
245
251
|
storage.setConfigValue("migration:session-workspaces:v1", "done");
|
|
@@ -248,12 +254,14 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
248
254
|
const runtimeConfig = new RuntimeConfigState(config);
|
|
249
255
|
const openRouter = new OpenRouterFreeModelsService(storage, options.openRouterFetch);
|
|
250
256
|
const speechPolisher = new SpeechPolisherService({ config, free: openRouter,
|
|
251
|
-
employee: () => storage.getSystemSiliconEmployee(SPEECH_POLISHER_KEY)
|
|
257
|
+
employee: () => storage.getSystemSiliconEmployee(SPEECH_POLISHER_KEY),
|
|
258
|
+
trace: employee => systemCalls.forEmployee(employee) });
|
|
252
259
|
const authService = new AuthService(storage);
|
|
253
260
|
const settingsAccess = new SettingsWebAccess();
|
|
254
261
|
const decisions = new DecisionService(config.localDecision);
|
|
255
262
|
const decisionExpert = new DecisionExpertService({ local: decisions, free: openRouter, config,
|
|
256
|
-
employee: () => storage.getSystemSiliconEmployee(DECISION_EXPERT_KEY)
|
|
263
|
+
employee: () => storage.getSystemSiliconEmployee(DECISION_EXPERT_KEY),
|
|
264
|
+
trace: employee => systemCalls.forEmployee(employee) });
|
|
257
265
|
const getModelRefreshOptions = () => {
|
|
258
266
|
const injected = options.modelRefreshOptions?.() ?? {};
|
|
259
267
|
const currentDefaults = readProviderModelPreferences(config, storage);
|
|
@@ -317,6 +325,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
317
325
|
let autoAssignEvaluate;
|
|
318
326
|
const processes = new ProcessManager(config, storage, configDir, terminalHost, { resolveDecisionEvaluate: () => autoAssignEvaluate, free: openRouter });
|
|
319
327
|
const structuredLogger = new SessionLogger(configDir, config.shortcutLogMaxBytes);
|
|
328
|
+
systemCalls.attach({ logger: structuredLogger });
|
|
320
329
|
// Production startup provides a daemon-backed host for structured CLI runs
|
|
321
330
|
// even when Render owns every PTY. In-process hosts are for test injection.
|
|
322
331
|
const legacyStructuredHost = ptyHosts.legacyHost
|
|
@@ -327,6 +336,16 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
327
336
|
// probes the in-process harness; CLI startup/settings must not depend on it.
|
|
328
337
|
const sessionRegistry = new SessionRegistry(processes, structuredSessions, storage);
|
|
329
338
|
const sessionCompletions = new SessionCompletionTracker(storage, (id) => sessionRegistry.get(id));
|
|
339
|
+
let nextEmployeeImWarningAt = 0;
|
|
340
|
+
const employeeIm = new EmployeeImService(storage, { onFailure: () => {
|
|
341
|
+
if (Date.now() < nextEmployeeImWarningAt)
|
|
342
|
+
return;
|
|
343
|
+
nextEmployeeImWarningAt = Date.now() + 60_000;
|
|
344
|
+
wandWarn("员工 IM 通知发送未确认或队列已满;不会自动重发。");
|
|
345
|
+
} });
|
|
346
|
+
const employeeImLifecycle = new EmployeeImLifecycle({ im: employeeIm, getSession: id => sessionRegistry.get(id),
|
|
347
|
+
getRunSteps: id => storage.listAiTeamSteps(id) });
|
|
348
|
+
employeeImLifecycle.seed(sessionRegistry.listSlim(), storage.listAiTeamRuns());
|
|
330
349
|
const missions = new Missions(storage, structuredSessions, sessionRegistry);
|
|
331
350
|
// 任务保留的立即扫描也晚绑定:设置路由先注册,WebSocket 和会话表稍后才就绪。
|
|
332
351
|
let runTaskRetentionSweep = () => ({
|
|
@@ -354,6 +373,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
354
373
|
selfAuthor: () => userAuthor(config.userProfile),
|
|
355
374
|
notify: (run) => {
|
|
356
375
|
conversations?.importRun(run);
|
|
376
|
+
employeeImLifecycle.ingestRun(run);
|
|
357
377
|
notifyAiTeamRun({ kind: "ai-team-run", runId: run.id, taskId: run.taskId });
|
|
358
378
|
},
|
|
359
379
|
notifyLive: notifier.aiTeamLive,
|
|
@@ -543,6 +563,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
543
563
|
free: openRouter,
|
|
544
564
|
notifyEmployeeChanged: (employeeId) => notifyAiTeamRun({ kind: "silicon-employee-definition", employeeId }),
|
|
545
565
|
});
|
|
566
|
+
registerEmployeeImRoutes(app, { storage, im: employeeIm, requireAdmin });
|
|
546
567
|
registerAttentionRoutes(app, { sessions: sessionRegistry, runner: aiTeams });
|
|
547
568
|
registerConversationRoutes(app, conversationService);
|
|
548
569
|
registerScheduledTaskRoutes(app, scheduledTasks);
|
|
@@ -596,7 +617,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
596
617
|
if (snapshot?.cwd)
|
|
597
618
|
cwd = snapshot.cwd;
|
|
598
619
|
if (snapshot)
|
|
599
|
-
ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), openRouter);
|
|
620
|
+
ai = resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), openRouter, { storage });
|
|
600
621
|
}
|
|
601
622
|
if (!ai) {
|
|
602
623
|
const defaultSession = {
|
|
@@ -607,13 +628,19 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
607
628
|
selectedModel: null,
|
|
608
629
|
thinkingEffort: config.defaultThinkingEffort,
|
|
609
630
|
};
|
|
610
|
-
ai = resolveSystemAiContext(defaultSession, config, storage.getSystemSiliconEmployee(), openRouter);
|
|
631
|
+
ai = resolveSystemAiContext(defaultSession, config, storage.getSystemSiliconEmployee(), openRouter, { storage });
|
|
611
632
|
}
|
|
633
|
+
const callSessionIds = [];
|
|
634
|
+
ai.onCallSessionCreated = id => callSessionIds.push(id);
|
|
612
635
|
try {
|
|
613
636
|
const optimized = await optimizePrompt(text, config.language ?? "", cwd, ai);
|
|
637
|
+
res.setHeader("X-Wand-System-Call-Ids", callSessionIds.slice(0, 64).join(","));
|
|
638
|
+
res.setHeader("X-Wand-System-Call-Count", String(callSessionIds.length));
|
|
614
639
|
res.json({ optimized });
|
|
615
640
|
}
|
|
616
641
|
catch (error) {
|
|
642
|
+
res.setHeader("X-Wand-System-Call-Ids", callSessionIds.slice(0, 64).join(","));
|
|
643
|
+
res.setHeader("X-Wand-System-Call-Count", String(callSessionIds.length));
|
|
617
644
|
if (error instanceof PromptOptimizeError) {
|
|
618
645
|
const status = error.code === "EMPTY_INPUT" || error.code === "INPUT_TOO_LONG" ? 400 : 500;
|
|
619
646
|
res.status(status).json({ error: error.message, errorCode: error.code });
|
|
@@ -786,6 +813,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
786
813
|
sessionCompletions.ingest(event);
|
|
787
814
|
missions.ingest(event);
|
|
788
815
|
aiTeams.ingest(event);
|
|
816
|
+
employeeImLifecycle.ingest(event);
|
|
789
817
|
wsManager.emitEvent(event);
|
|
790
818
|
});
|
|
791
819
|
structuredSessions.setEventEmitter((event) => {
|
|
@@ -795,9 +823,11 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
795
823
|
sessionCompletions.ingest(event);
|
|
796
824
|
missions.ingest(event);
|
|
797
825
|
aiTeams.ingest(event);
|
|
826
|
+
employeeImLifecycle.ingest(event);
|
|
798
827
|
wsManager.emitEvent(event);
|
|
799
828
|
});
|
|
800
829
|
notifier.attach((event) => wsManager.emitEvent(event));
|
|
830
|
+
systemCalls.attach({ emit: event => wsManager.emitEvent(event), notifyConversation: notifier.conversationSession });
|
|
801
831
|
// Re-attach structured CLI runs that kept going inside terminald while the
|
|
802
832
|
// previous web process was down; fire-and-forget, failures are logged inside.
|
|
803
833
|
void structuredSessions.recoverDetachedRuns();
|
|
@@ -875,6 +905,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
875
905
|
await new Promise((resolve, reject) => {
|
|
876
906
|
const cleanupFailedListen = async () => {
|
|
877
907
|
shuttingDown = true;
|
|
908
|
+
employeeImLifecycle.dispose();
|
|
909
|
+
employeeIm.dispose();
|
|
878
910
|
await scheduledTasks.dispose();
|
|
879
911
|
decisions.dispose();
|
|
880
912
|
openRouter.dispose();
|
|
@@ -1135,6 +1167,8 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
1135
1167
|
return closePromise;
|
|
1136
1168
|
closePromise = (async () => {
|
|
1137
1169
|
shuttingDown = true;
|
|
1170
|
+
employeeImLifecycle.dispose();
|
|
1171
|
+
employeeIm.dispose();
|
|
1138
1172
|
taskTitles.dispose();
|
|
1139
1173
|
modelCatalog.dispose();
|
|
1140
1174
|
const teamShutdown = aiTeams.dispose();
|
|
@@ -2,6 +2,8 @@ import type { EmployeeTextDeps } from "./employee-text.js";
|
|
|
2
2
|
import type { ModelGroup } from "./model-groups.js";
|
|
3
3
|
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
4
4
|
import type { AiCliCandidate, SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
|
|
5
|
+
import type { WandStorage } from "./storage.js";
|
|
6
|
+
import type { AiCallTraceFactory, SystemAiPurpose } from "./system-ai-call.js";
|
|
5
7
|
export interface SessionAiContext {
|
|
6
8
|
provider: SessionProvider;
|
|
7
9
|
model?: string;
|
|
@@ -15,6 +17,9 @@ export interface SessionAiContext {
|
|
|
15
17
|
/** 系统应用必须经员工渠道;候选为空时禁止退回当前会话/默认 provider。 */
|
|
16
18
|
employeeChannelOnly?: boolean;
|
|
17
19
|
modelGroups?: ModelGroup[];
|
|
20
|
+
purpose?: SystemAiPurpose;
|
|
21
|
+
traceFactory?: AiCallTraceFactory;
|
|
22
|
+
onCallSessionCreated?(sessionId: string): void;
|
|
18
23
|
}
|
|
19
24
|
/**
|
|
20
25
|
* Resolve the provider from every representation used by current and legacy
|
|
@@ -25,4 +30,9 @@ export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "
|
|
|
25
30
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
26
31
|
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultThinkingEffort" | "inheritEnv" | "modelGroups">): SessionAiContext;
|
|
27
32
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
28
|
-
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0]
|
|
33
|
+
export declare function resolveSystemAiContext(snapshot: Parameters<typeof resolveSessionAiContext>[0] & {
|
|
34
|
+
id?: string;
|
|
35
|
+
}, config: Parameters<typeof resolveSessionAiContext>[1] & Pick<WandConfig, "systemAiCli" | "systemAiModel"> & Partial<Pick<WandConfig, "defaultCwd" | "defaultProvider" | "harness">>, systemEmployee?: SiliconEmployee | null, free?: EmployeeTextDeps["free"], recording?: {
|
|
36
|
+
storage: WandStorage;
|
|
37
|
+
purpose?: SystemAiPurpose;
|
|
38
|
+
}): SessionAiContext;
|
|
@@ -2,6 +2,7 @@ import { getDefaultModelForProvider } from "./config.js";
|
|
|
2
2
|
import { providerCliInstalled, isSessionProvider } from "./session-provider.js";
|
|
3
3
|
import { isSystemSiliconEmployee, systemEmployeeCandidates } from "./system-employee.js";
|
|
4
4
|
import { inferProviderFromCommand, inferProviderFromRunner } from "./session-provider.js";
|
|
5
|
+
import { systemAiSessions } from "./system-ai-session-recorder.js";
|
|
5
6
|
/**
|
|
6
7
|
* Resolve the provider from every representation used by current and legacy
|
|
7
8
|
* sessions. Older persisted sessions may not have the top-level provider, but
|
|
@@ -45,8 +46,7 @@ export function resolveSessionAiContext(snapshot, config) {
|
|
|
45
46
|
};
|
|
46
47
|
}
|
|
47
48
|
/** Build the source order for Wand-owned AI features such as titles. */
|
|
48
|
-
export function resolveSystemAiContext(snapshot, config, systemEmployee, free) {
|
|
49
|
-
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
49
|
+
export function resolveSystemAiContext(snapshot, config, systemEmployee, free, recording) {
|
|
50
50
|
// 候选里的「跟随默认模型」在这里就换成具体的 Wand 默认模型:降级到下一个 provider
|
|
51
51
|
// 时不能拿上一个 provider 的模型,也不能把决定权交给 CLI 自己的默认值。
|
|
52
52
|
// Keep the configured chain here; model groups are expanded once at execution.
|
|
@@ -54,12 +54,31 @@ export function resolveSystemAiContext(snapshot, config, systemEmployee, free) {
|
|
|
54
54
|
...candidate,
|
|
55
55
|
model: candidate.model ?? normalizeModel(getDefaultModelForProvider(config, candidate.provider)),
|
|
56
56
|
}));
|
|
57
|
+
// System work belongs to its configured role, not the source session's possibly removed provider/model.
|
|
58
|
+
const fallbackProvider = isSessionProvider(config.defaultProvider) ? config.defaultProvider
|
|
59
|
+
: isSessionProvider(snapshot.provider) ? snapshot.provider : "pi";
|
|
60
|
+
const sessionContext = systemEmployee ? {
|
|
61
|
+
provider: chain[0]?.provider ?? fallbackProvider,
|
|
62
|
+
model: chain[0]?.model,
|
|
63
|
+
thinkingEffort: config.defaultThinkingEffort,
|
|
64
|
+
inheritEnv: config.inheritEnv,
|
|
65
|
+
...(config.modelGroups?.length ? { modelGroups: config.modelGroups } : {}),
|
|
66
|
+
} : resolveSessionAiContext(snapshot, config);
|
|
57
67
|
const owned = {
|
|
58
68
|
...sessionContext,
|
|
59
69
|
...(chain.length ? { cliCandidates: chain } : {}),
|
|
60
70
|
...(chain.some(candidate => candidate.engine === "sdk") ? { employeeText: { config, ...(free ? { free } : {}) } } : {}),
|
|
61
71
|
...(systemEmployee ? { employeeChannelOnly: true } : {}),
|
|
62
72
|
...(isSystemSiliconEmployee(systemEmployee) ? { opsPersona: systemEmployee.prompt } : {}),
|
|
73
|
+
...(recording ? {
|
|
74
|
+
employeeChannelOnly: true,
|
|
75
|
+
purpose: recording.purpose,
|
|
76
|
+
traceFactory: { start: (purpose, request, cwd) => {
|
|
77
|
+
if (!systemEmployee)
|
|
78
|
+
throw new Error("系统默认 AI 员工尚未初始化,未执行模型;请检查服务初始化。");
|
|
79
|
+
return systemAiSessions(recording.storage).forEmployee(systemEmployee, snapshot.id).start(purpose, request, cwd);
|
|
80
|
+
} },
|
|
81
|
+
} : {}),
|
|
63
82
|
};
|
|
64
83
|
// 内置员工可用时,Wand 自有调用一律按它的候选链:provider/model 取首个已安装的
|
|
65
84
|
// 候选(两条路都一致),整条链留给运行期降级。
|
|
@@ -41,7 +41,7 @@ export class SessionMetadataService {
|
|
|
41
41
|
input: prompt,
|
|
42
42
|
cwd: snapshot.cwd,
|
|
43
43
|
language: config.language,
|
|
44
|
-
ai: resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free),
|
|
44
|
+
ai: resolveSystemAiContext(snapshot, config, storage.getSystemSiliconEmployee(), free, { storage }),
|
|
45
45
|
readNativeTitle: () => readNativeSessionTitle(resolveSessionProvider(snapshot), snapshot.claudeSessionId, snapshot.cwd),
|
|
46
46
|
hasNativeTitle,
|
|
47
47
|
onGenerating: (generating) => {
|
package/dist/session-registry.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isArchiveBlocked } from "./archive-policy.js";
|
|
2
|
+
import { isSystemAiCall } from "./system-ai-call.js";
|
|
3
|
+
import { systemAiSessions } from "./system-ai-session-recorder.js";
|
|
2
4
|
import { cleanupWorktreeSync } from "./git-worktree.js";
|
|
3
5
|
import { inferProviderFromCommand } from "./session-provider.js";
|
|
4
6
|
export function deleteProviderNativeHistory(deleter, provider, providerSessionId, cwd) {
|
|
@@ -230,6 +232,10 @@ export class SessionRegistry {
|
|
|
230
232
|
const snapshot = this.get(id);
|
|
231
233
|
if (!snapshot)
|
|
232
234
|
return null;
|
|
235
|
+
if (isSystemAiCall(snapshot)) {
|
|
236
|
+
systemAiSessions(this.storage).remove(id);
|
|
237
|
+
return snapshot;
|
|
238
|
+
}
|
|
233
239
|
// Best-effort: tear down the session's isolated worktree (directory +
|
|
234
240
|
// branch) before removing the session record. Without this, deleting a
|
|
235
241
|
// session that was never merged leaves an orphan under `.wand-worktrees/`
|
package/dist/session-request.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isExecutionMode } from "./config.js";
|
|
2
|
+
import { isSystemAiCall } from "./system-ai-call.js";
|
|
2
3
|
export function parseExecutionMode(value, fallback) {
|
|
3
4
|
if (value === undefined)
|
|
4
5
|
return fallback;
|
|
@@ -17,6 +18,8 @@ export function parseSessionCreationOrigin(body) {
|
|
|
17
18
|
throw new Error("automationId 必须是非空字符串。");
|
|
18
19
|
}
|
|
19
20
|
const automationId = rawAutomationId?.trim();
|
|
21
|
+
if (isSystemAiCall({ automationId }))
|
|
22
|
+
throw new Error("automationId 使用了系统调用的保留命名空间。");
|
|
20
23
|
if (rawAutomationId !== undefined && !automationId) {
|
|
21
24
|
throw new Error("automationId 必须是非空字符串。");
|
|
22
25
|
}
|
package/dist/session-topic.js
CHANGED
|
@@ -257,7 +257,7 @@ export async function generateSessionTopic(userMessages, cwd, language, ai = {})
|
|
|
257
257
|
].join("\n"),
|
|
258
258
|
prompt: `按发送顺序排列的用户消息:\n${input}`,
|
|
259
259
|
};
|
|
260
|
-
return callConfiguredAiText(request, cwd ?? process.cwd(), language ?? "", ai, (raw) => {
|
|
260
|
+
return callConfiguredAiText(request, cwd ?? process.cwd(), language ?? "", { ...ai, purpose: "session-title" }, (raw) => {
|
|
261
261
|
const topic = parseTopic(raw);
|
|
262
262
|
if (!topic)
|
|
263
263
|
throw new Error("模型返回的会话主题格式无效。");
|
|
@@ -217,7 +217,7 @@ export async function generateSiliconEmployeeDraft(expectation, ai = {}, options
|
|
|
217
217
|
const providers = available.length ? available : [options.preferredProvider ?? "claude", ...SESSION_PROVIDERS];
|
|
218
218
|
const request = buildEmployeeDraftPrompt(trimmed, providers, options.existingNames);
|
|
219
219
|
try {
|
|
220
|
-
return await callConfiguredAiText(request, options.cwd || process.cwd(), options.language ?? "", ai, (raw) => parseSiliconEmployeeDraft(raw, {
|
|
220
|
+
return await callConfiguredAiText(request, options.cwd || process.cwd(), options.language ?? "", { ...ai, purpose: "employee-draft" }, (raw) => parseSiliconEmployeeDraft(raw, {
|
|
221
221
|
allowedProviders: providers, fallbackProvider: providers[0], existingNames: options.existingNames,
|
|
222
222
|
}));
|
|
223
223
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
2
2
|
import type { OpenRouterFreeModelsService } from "./openrouter-free-models.js";
|
|
3
3
|
import type { WandTaskAgent } from "./task-types.js";
|
|
4
|
+
import { type AiCallTraceFactory } from "./system-ai-call.js";
|
|
4
5
|
import type { AiTextRequest, WandConfig } from "./types.js";
|
|
5
6
|
export interface SpeechPolishResult {
|
|
6
7
|
text: string;
|
|
@@ -11,6 +12,7 @@ export interface SpeechPolishResult {
|
|
|
11
12
|
optimizationError?: string;
|
|
12
13
|
}
|
|
13
14
|
interface SpeechPolisherDeps {
|
|
15
|
+
trace?(employee: SiliconEmployee): AiCallTraceFactory;
|
|
14
16
|
employee(): SiliconEmployee | null;
|
|
15
17
|
config: WandConfig;
|
|
16
18
|
free: Pick<OpenRouterFreeModelsService, "resolveForCall">;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { abortableEmployeeText, EmployeeCandidateRejected, employeeTextCandidates, prepareEmployeeTextCandidate } from "./employee-text.js";
|
|
2
2
|
import { SPEECH_POLISHER_ID, SPEECH_POLISHER_KEY } from "./speech-polisher-identity.js";
|
|
3
|
+
import { AiCallTraceError } from "./system-ai-call.js";
|
|
3
4
|
export function parseSpeechPolishOutput(raw) {
|
|
4
5
|
if (raw.length > 32_768)
|
|
5
6
|
throw new Error("口述整理结果过长。");
|
|
@@ -33,16 +34,22 @@ export class SpeechPolisherService {
|
|
|
33
34
|
const combined = AbortSignal.any([this.lifetime.signal, deadline, ...(signal ? [signal] : [])]);
|
|
34
35
|
const request = { system: `${employee.prompt}\n输出一个 JSON 对象:{"text":"整理后的完整文字"},不加其他字段或说明。`,
|
|
35
36
|
prompt: JSON.stringify({ transcript: text }) };
|
|
37
|
+
let trace;
|
|
36
38
|
try {
|
|
39
|
+
trace = this.deps.trace?.(employee).start("speech-polish", request, this.deps.config.defaultCwd);
|
|
37
40
|
for (const candidate of employeeTextCandidates(employee.agents, this.deps.config)) {
|
|
38
41
|
combined.throwIfAborted();
|
|
39
42
|
const agent = candidate.agent;
|
|
40
43
|
let generate;
|
|
44
|
+
trace?.candidateStarted(agent);
|
|
41
45
|
// An unavailable candidate has accepted no user input: continue the configured order.
|
|
42
46
|
try {
|
|
43
|
-
generate = await this.prepareCandidate(agent, request, combined);
|
|
47
|
+
generate = await this.prepareCandidate(agent, request, combined, trace?.sessionId, usage => trace?.usageReported(usage));
|
|
44
48
|
}
|
|
45
|
-
catch {
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof AiCallTraceError)
|
|
51
|
+
throw error;
|
|
52
|
+
trace?.candidateFailed(agent, error, "availability");
|
|
46
53
|
combined.throwIfAborted();
|
|
47
54
|
continue;
|
|
48
55
|
}
|
|
@@ -53,18 +60,33 @@ export class SpeechPolisherService {
|
|
|
53
60
|
raw = await abortableEmployeeText(generate(), combined);
|
|
54
61
|
}
|
|
55
62
|
catch (error) {
|
|
63
|
+
if (error instanceof AiCallTraceError)
|
|
64
|
+
throw error;
|
|
65
|
+
trace?.candidateFailed(agent, error, "execution");
|
|
56
66
|
combined.throwIfAborted();
|
|
57
67
|
if (error instanceof EmployeeCandidateRejected && error.failure.retryable)
|
|
58
68
|
continue;
|
|
59
69
|
throw error;
|
|
60
70
|
}
|
|
61
71
|
combined.throwIfAborted();
|
|
62
|
-
|
|
72
|
+
let polished;
|
|
73
|
+
try {
|
|
74
|
+
polished = parseSpeechPolishOutput(raw);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
trace?.candidateFailed(agent, error, "result");
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
trace?.candidateSucceeded(raw, polished);
|
|
81
|
+
trace?.complete();
|
|
82
|
+
return { text: polished, originalText: text, optimized: true, employeeId: employee.id,
|
|
63
83
|
candidate: candidate.index };
|
|
64
84
|
}
|
|
85
|
+
trace?.fail(new Error("NoAvailableCandidate"));
|
|
65
86
|
return { ...original, optimizationError: "口述整理师的候选均不可用,已保留原始转写。" };
|
|
66
87
|
}
|
|
67
|
-
catch {
|
|
88
|
+
catch (error) {
|
|
89
|
+
trace?.fail(error, combined.aborted);
|
|
68
90
|
signal?.throwIfAborted();
|
|
69
91
|
this.lifetime.signal.throwIfAborted();
|
|
70
92
|
return { ...original, optimizationError: "口述整理失败或超时,已保留原始转写。" };
|
|
@@ -73,12 +95,12 @@ export class SpeechPolisherService {
|
|
|
73
95
|
this.active -= 1;
|
|
74
96
|
}
|
|
75
97
|
}
|
|
76
|
-
async prepareCandidate(agent, request, signal) {
|
|
98
|
+
async prepareCandidate(agent, request, signal, callSessionId, onUsage) {
|
|
77
99
|
const deadline = new AbortController();
|
|
78
100
|
const timer = setTimeout(() => deadline.abort(new Error("候选可用性检查超时。")), 8000);
|
|
79
101
|
const checkedSignal = AbortSignal.any([signal, deadline.signal]);
|
|
80
102
|
try {
|
|
81
|
-
return await abortableEmployeeText((this.deps.prepare ?? ((candidate, input, checked) => prepareEmployeeTextCandidate(this.deps, candidate, input, checked)))(agent, request, checkedSignal), checkedSignal);
|
|
103
|
+
return await abortableEmployeeText((this.deps.prepare ?? ((candidate, input, checked) => prepareEmployeeTextCandidate({ ...this.deps, callSessionId, onUsage }, candidate, input, checked)))(agent, request, checkedSignal), checkedSignal);
|
|
82
104
|
}
|
|
83
105
|
finally {
|
|
84
106
|
clearTimeout(timer);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import type { ConversationOrganizationJob } from "../conversation-organize-contract.js";
|
|
3
|
+
/** Operation receipts, not a second source of tasks or membership. Task mutations share this transaction. */
|
|
4
|
+
export declare class ConversationOrganizationRepository {
|
|
5
|
+
private readonly db;
|
|
6
|
+
constructor(db: DatabaseSync);
|
|
7
|
+
saveConversationOrganizationJob(job: ConversationOrganizationJob, requestToken?: string, scopeKey?: string): void;
|
|
8
|
+
getConversationOrganizationJob(id: string): ConversationOrganizationJob | null;
|
|
9
|
+
getConversationOrganizationJobByToken(token: string): ConversationOrganizationJob | null;
|
|
10
|
+
listRunningConversationOrganizationJobs(): ConversationOrganizationJob[];
|
|
11
|
+
getConversationOrganizationFingerprint(scope: string): string | null;
|
|
12
|
+
saveConversationOrganizationFingerprint(scope: string, fingerprint: string): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Operation receipts, not a second source of tasks or membership. Task mutations share this transaction. */
|
|
2
|
+
export class ConversationOrganizationRepository {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
saveConversationOrganizationJob(job, requestToken, scopeKey) {
|
|
8
|
+
this.db.prepare(`INSERT INTO conversation_organization_jobs(id, request_token, scope_key, status, state_json, updated_at)
|
|
9
|
+
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET status=excluded.status,
|
|
10
|
+
state_json=excluded.state_json, updated_at=excluded.updated_at`)
|
|
11
|
+
.run(job.id, requestToken ?? job.id, scopeKey ?? "", job.status, JSON.stringify(job), new Date().toISOString());
|
|
12
|
+
}
|
|
13
|
+
getConversationOrganizationJob(id) {
|
|
14
|
+
const row = this.db.prepare("SELECT state_json FROM conversation_organization_jobs WHERE id=?").get(id);
|
|
15
|
+
return row ? JSON.parse(row.state_json) : null;
|
|
16
|
+
}
|
|
17
|
+
getConversationOrganizationJobByToken(token) {
|
|
18
|
+
const row = this.db.prepare("SELECT state_json FROM conversation_organization_jobs WHERE request_token=?").get(token);
|
|
19
|
+
return row ? JSON.parse(row.state_json) : null;
|
|
20
|
+
}
|
|
21
|
+
listRunningConversationOrganizationJobs() {
|
|
22
|
+
return this.db.prepare("SELECT state_json FROM conversation_organization_jobs WHERE status='running'").all().map(row => JSON.parse(row.state_json));
|
|
23
|
+
}
|
|
24
|
+
getConversationOrganizationFingerprint(scope) {
|
|
25
|
+
const row = this.db.prepare("SELECT fingerprint FROM conversation_organization_cache WHERE scope_key=?").get(scope);
|
|
26
|
+
return row?.fingerprint ?? null;
|
|
27
|
+
}
|
|
28
|
+
saveConversationOrganizationFingerprint(scope, fingerprint) {
|
|
29
|
+
this.db.prepare(`INSERT INTO conversation_organization_cache(scope_key,fingerprint,updated_at) VALUES(?,?,?)
|
|
30
|
+
ON CONFLICT(scope_key) DO UPDATE SET fingerprint=excluded.fingerprint, updated_at=excluded.updated_at`)
|
|
31
|
+
.run(scope, fingerprint, new Date().toISOString());
|
|
32
|
+
}
|
|
33
|
+
}
|