@co0ontty/wand 4.16.1 → 4.16.2-beta.gec15519
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +4 -4
- package/dist/claude-skills.d.ts +6 -0
- package/dist/claude-skills.js +53 -0
- package/dist/server-session-routes.js +35 -1
- package/dist/server.js +12 -0
- package/dist/storage.js +23 -5
- package/dist/structured-session-manager.d.ts +3 -0
- package/dist/structured-session-manager.js +70 -13
- package/dist/types.d.ts +2 -0
- package/dist/web-ui/content/scripts.js +340 -100
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +3 -3
- package/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "4.16.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "ec1551937e1195d7d3a02f421e76660799daa6d6",
|
|
3
|
+
"builtAt": "2026-07-18T12:12:46.898Z",
|
|
4
|
+
"version": "4.16.2-beta.gec15519",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function parseFrontmatter(source) {
|
|
5
|
+
if (!source.startsWith("---"))
|
|
6
|
+
return {};
|
|
7
|
+
const end = source.indexOf("\n---", 3);
|
|
8
|
+
if (end === -1)
|
|
9
|
+
return {};
|
|
10
|
+
const frontmatter = source.slice(3, end);
|
|
11
|
+
const name = frontmatter.match(/^name:\s*(.+?)\s*$/m)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
12
|
+
const description = frontmatter.match(/^description:\s*(.+?)\s*$/m)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
13
|
+
return { name, description };
|
|
14
|
+
}
|
|
15
|
+
function readSkills(directory, source) {
|
|
16
|
+
if (!existsSync(directory))
|
|
17
|
+
return [];
|
|
18
|
+
let entries;
|
|
19
|
+
try {
|
|
20
|
+
entries = readdirSync(directory, { withFileTypes: true, encoding: "utf8" });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return entries.flatMap((entry) => {
|
|
26
|
+
if (!entry.isDirectory())
|
|
27
|
+
return [];
|
|
28
|
+
const skillFile = path.join(directory, entry.name, "SKILL.md");
|
|
29
|
+
if (!existsSync(skillFile))
|
|
30
|
+
return [];
|
|
31
|
+
try {
|
|
32
|
+
const metadata = parseFrontmatter(readFileSync(skillFile, "utf8"));
|
|
33
|
+
return [{
|
|
34
|
+
name: metadata.name || entry.name,
|
|
35
|
+
description: metadata.description || "",
|
|
36
|
+
source,
|
|
37
|
+
}];
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export function listClaudeSkills(cwd) {
|
|
45
|
+
const byName = new Map();
|
|
46
|
+
for (const skill of [
|
|
47
|
+
...readSkills(path.join(os.homedir(), ".claude", "skills"), "user"),
|
|
48
|
+
...readSkills(path.join(cwd, ".claude", "skills"), "project"),
|
|
49
|
+
]) {
|
|
50
|
+
byName.set(skill.name, skill);
|
|
51
|
+
}
|
|
52
|
+
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
53
|
+
}
|
|
@@ -73,6 +73,24 @@ function getInputErrorResponse(error, sessionId) {
|
|
|
73
73
|
},
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
function parseClaudeSdkSkills(value) {
|
|
77
|
+
if (!Array.isArray(value)) {
|
|
78
|
+
throw new Error("skills 必须是数组。");
|
|
79
|
+
}
|
|
80
|
+
if (value.length > 50) {
|
|
81
|
+
throw new Error("最多选择 50 个 skills。");
|
|
82
|
+
}
|
|
83
|
+
const names = new Set();
|
|
84
|
+
for (const item of value) {
|
|
85
|
+
if (typeof item !== "string")
|
|
86
|
+
throw new Error("skills 只能包含字符串。");
|
|
87
|
+
const name = item.trim();
|
|
88
|
+
if (!name || name.length > 128)
|
|
89
|
+
throw new Error("skill 名称无效。");
|
|
90
|
+
names.add(name);
|
|
91
|
+
}
|
|
92
|
+
return Array.from(names);
|
|
93
|
+
}
|
|
76
94
|
function getInputDebugMeta(error) {
|
|
77
95
|
if (error instanceof Error) {
|
|
78
96
|
return { name: error.name, message: error.message, stack: error.stack };
|
|
@@ -497,7 +515,23 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
497
515
|
const preserveQueue = !!req.body?.preserveQueue;
|
|
498
516
|
const idempotencyKey = typeof req.body?.idempotencyKey === "string" ? req.body.idempotencyKey : undefined;
|
|
499
517
|
try {
|
|
500
|
-
const
|
|
518
|
+
const session = structured.get(req.params.id);
|
|
519
|
+
if (!session) {
|
|
520
|
+
res.status(404).json({ error: "未找到该结构化会话。" });
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const hasSkills = Object.prototype.hasOwnProperty.call(req.body ?? {}, "skills");
|
|
524
|
+
if (hasSkills && (session.provider !== "claude" || session.runner !== "claude-sdk")) {
|
|
525
|
+
res.status(400).json({ error: "skills 仅支持 Claude SDK 结构化会话。" });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const skills = hasSkills ? parseClaudeSdkSkills(req.body.skills) : [];
|
|
529
|
+
const snapshot = await structured.sendMessage(req.params.id, input, {
|
|
530
|
+
interrupt,
|
|
531
|
+
preserveQueue,
|
|
532
|
+
idempotencyKey,
|
|
533
|
+
skills,
|
|
534
|
+
});
|
|
501
535
|
res.json(sessionResponseDTO(snapshot));
|
|
502
536
|
}
|
|
503
537
|
catch (error) {
|
package/dist/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import process from "node:process";
|
|
|
12
12
|
import { WebSocketServer } from "ws";
|
|
13
13
|
import { AuthService, BROWSER_ADMIN_PRINCIPAL, CONNECTED_APP_PRINCIPAL, principalHasScope, readSessionCookie, SESSION_COOKIE_HTTP, SESSION_COOKIE_HTTPS, SESSION_COOKIE_LEGACY, } from "./auth.js";
|
|
14
14
|
import { ensureCertificates } from "./cert.js";
|
|
15
|
+
import { listClaudeSkills } from "./claude-skills.js";
|
|
15
16
|
import { getDefaultModelForProvider, getProviderDefaultModels, isExecutionMode, resolveConfigDir, } from "./config.js";
|
|
16
17
|
import { refreshModels } from "./models.js";
|
|
17
18
|
import { ProcessManager } from "./process-manager.js";
|
|
@@ -22,6 +23,7 @@ import { recordRecentPath, registerFileRoutes } from "./server-file-routes.js";
|
|
|
22
23
|
import { registerSettingsRoutes } from "./server-settings-routes.js";
|
|
23
24
|
import { refreshProviderCliUpdateState, registerAdminUpdateRoutes, registerPublicUpdateRoutes, ServerUpdateState, } from "./server-update-routes.js";
|
|
24
25
|
import { parseSessionCreationOrigin, registerClaudeHistoryRoutes, registerSessionRoutes } from "./server-session-routes.js";
|
|
26
|
+
import { resolveSessionCwd } from "./session-cwd.js";
|
|
25
27
|
import { getErrorMessage } from "./error-utils.js";
|
|
26
28
|
import { asyncRoute, jsonErrorHandler } from "./express-async.js";
|
|
27
29
|
import { checkPackageUpdateAsync, installPackageGloballyAsync, normalizeUpdateChannel, resolveGlobalWandCli, } from "./npm-update-utils.js";
|
|
@@ -688,6 +690,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
688
690
|
"/api/sessions",
|
|
689
691
|
"/api/structured-sessions",
|
|
690
692
|
"/api/commands",
|
|
693
|
+
"/api/claude-skills",
|
|
691
694
|
"/api/claude-history",
|
|
692
695
|
"/api/codex-history",
|
|
693
696
|
"/api/claude-sessions",
|
|
@@ -747,6 +750,15 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
747
750
|
});
|
|
748
751
|
}));
|
|
749
752
|
// ── Browser extension password vault endpoints ──
|
|
753
|
+
app.get("/api/claude-skills", (req, res) => {
|
|
754
|
+
try {
|
|
755
|
+
const cwd = resolveSessionCwd(typeof req.query.cwd === "string" ? req.query.cwd : undefined, config.defaultCwd);
|
|
756
|
+
res.json({ skills: listClaudeSkills(cwd) });
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
res.status(400).json({ error: getErrorMessage(error, "无法读取 skills。") });
|
|
760
|
+
}
|
|
761
|
+
});
|
|
750
762
|
app.get("/api/browser-extension/status", (_req, res) => {
|
|
751
763
|
res.json({
|
|
752
764
|
ok: true,
|
package/dist/storage.js
CHANGED
|
@@ -168,6 +168,17 @@ function parseQueuedMessages(raw) {
|
|
|
168
168
|
const parsed = safeJsonParse(raw);
|
|
169
169
|
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : undefined;
|
|
170
170
|
}
|
|
171
|
+
function parseQueuedMessageSkills(raw, queueLength) {
|
|
172
|
+
const parsed = safeJsonParse(raw);
|
|
173
|
+
if (!Array.isArray(parsed))
|
|
174
|
+
return undefined;
|
|
175
|
+
return Array.from({ length: queueLength }, (_, index) => {
|
|
176
|
+
const skills = parsed[index];
|
|
177
|
+
return Array.isArray(skills)
|
|
178
|
+
? skills.filter((skill) => typeof skill === "string")
|
|
179
|
+
: [];
|
|
180
|
+
});
|
|
181
|
+
}
|
|
171
182
|
function inferSessionProvider(row) {
|
|
172
183
|
if (row.provider === "claude" || row.provider === "codex" || row.provider === "opencode" || row.provider === "grok" || row.provider === "qoder") {
|
|
173
184
|
return row.provider;
|
|
@@ -227,12 +238,12 @@ function mapWorktreeMergeFields(row) {
|
|
|
227
238
|
};
|
|
228
239
|
}
|
|
229
240
|
function sessionSelectFields() {
|
|
230
|
-
return `id, session_source, automation_id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages, queued_messages, structured_state
|
|
241
|
+
return `id, session_source, automation_id, provider, session_kind, runner, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages, queued_messages, queued_message_skills, structured_state
|
|
231
242
|
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options`;
|
|
232
243
|
}
|
|
233
244
|
function sessionPersistFields() {
|
|
234
245
|
return `id, session_source, automation_id, command, cwd, mode, status, exit_code, started_at, ended_at, output
|
|
235
|
-
, archived, archived_at, claude_session_id, provider, session_kind, runner, messages, queued_messages, structured_state
|
|
246
|
+
, archived, archived_at, claude_session_id, provider, session_kind, runner, messages, queued_messages, queued_message_skills, structured_state
|
|
236
247
|
, resumed_from_session_id, auto_recovered, worktree_enabled, worktree_info, worktree_merge_status, worktree_merge_info, title, description, session_options`;
|
|
237
248
|
}
|
|
238
249
|
function sessionPersistAssignments() {
|
|
@@ -254,6 +265,7 @@ function sessionPersistAssignments() {
|
|
|
254
265
|
runner = excluded.runner,
|
|
255
266
|
messages = excluded.messages,
|
|
256
267
|
queued_messages = excluded.queued_messages,
|
|
268
|
+
queued_message_skills = excluded.queued_message_skills,
|
|
257
269
|
structured_state = excluded.structured_state,
|
|
258
270
|
resumed_from_session_id = excluded.resumed_from_session_id,
|
|
259
271
|
auto_recovered = excluded.auto_recovered,
|
|
@@ -270,7 +282,7 @@ function sessionRuntimeMetadataAssignments() {
|
|
|
270
282
|
command = ?, cwd = ?, mode = ?, status = ?, exit_code = ?,
|
|
271
283
|
started_at = ?, ended_at = ?,
|
|
272
284
|
archived = ?, archived_at = ?, claude_session_id = ?,
|
|
273
|
-
provider = ?, session_kind = ?, runner = ?, queued_messages = ?, structured_state = ?,
|
|
285
|
+
provider = ?, session_kind = ?, runner = ?, queued_messages = ?, queued_message_skills = ?, structured_state = ?,
|
|
274
286
|
resumed_from_session_id = ?, auto_recovered = ?,
|
|
275
287
|
worktree_enabled = ?, worktree_info = ?, worktree_merge_status = ?, worktree_merge_info = ?,
|
|
276
288
|
title = ?, description = ?, session_options = ?`;
|
|
@@ -296,6 +308,7 @@ function sessionPersistValues(snapshot) {
|
|
|
296
308
|
snapshot.runner ?? null,
|
|
297
309
|
snapshot.messages ? JSON.stringify(snapshot.messages) : null,
|
|
298
310
|
snapshot.queuedMessages ? JSON.stringify(snapshot.queuedMessages) : null,
|
|
311
|
+
snapshot.queuedMessageSkills ? JSON.stringify(snapshot.queuedMessageSkills) : null,
|
|
299
312
|
snapshot.structuredState ? JSON.stringify(snapshot.structuredState) : null,
|
|
300
313
|
snapshot.resumedFromSessionId ?? null,
|
|
301
314
|
snapshot.autoRecovered ? 1 : 0,
|
|
@@ -326,6 +339,7 @@ function sessionRuntimeMetadataValues(snapshot) {
|
|
|
326
339
|
snapshot.sessionKind ?? "pty",
|
|
327
340
|
snapshot.runner ?? null,
|
|
328
341
|
snapshot.queuedMessages ? JSON.stringify(snapshot.queuedMessages) : null,
|
|
342
|
+
snapshot.queuedMessageSkills ? JSON.stringify(snapshot.queuedMessageSkills) : null,
|
|
329
343
|
snapshot.structuredState ? JSON.stringify(snapshot.structuredState) : null,
|
|
330
344
|
snapshot.resumedFromSessionId ?? null,
|
|
331
345
|
snapshot.autoRecovered ? 1 : 0,
|
|
@@ -342,6 +356,7 @@ function sessionRuntimeMetadataValues(snapshot) {
|
|
|
342
356
|
function mapSessionCore(row) {
|
|
343
357
|
const provider = inferSessionProvider(row);
|
|
344
358
|
const sessionOptions = parseSessionOptions(row.session_options);
|
|
359
|
+
const queuedMessages = parseQueuedMessages(row.queued_messages);
|
|
345
360
|
return {
|
|
346
361
|
id: row.id,
|
|
347
362
|
sessionSource: normalizeSessionSource(row.session_source),
|
|
@@ -361,7 +376,8 @@ function mapSessionCore(row) {
|
|
|
361
376
|
archivedAt: row.archived_at,
|
|
362
377
|
claudeSessionId: row.claude_session_id,
|
|
363
378
|
messages: safeJsonParse(row.messages),
|
|
364
|
-
queuedMessages
|
|
379
|
+
queuedMessages,
|
|
380
|
+
queuedMessageSkills: parseQueuedMessageSkills(row.queued_message_skills, queuedMessages?.length ?? 0),
|
|
365
381
|
structuredState: safeJsonParse(row.structured_state),
|
|
366
382
|
resumedFromSessionId: row.resumed_from_session_id ?? undefined,
|
|
367
383
|
autoRecovered: Boolean(row.auto_recovered),
|
|
@@ -411,6 +427,7 @@ const INIT_SQL = `
|
|
|
411
427
|
runner TEXT,
|
|
412
428
|
messages TEXT,
|
|
413
429
|
queued_messages TEXT,
|
|
430
|
+
queued_message_skills TEXT,
|
|
414
431
|
structured_state TEXT,
|
|
415
432
|
resumed_from_session_id TEXT,
|
|
416
433
|
resumed_to_session_id TEXT,
|
|
@@ -730,7 +747,7 @@ export class WandStorage {
|
|
|
730
747
|
this.db
|
|
731
748
|
.prepare(`INSERT INTO command_sessions (
|
|
732
749
|
${sessionPersistFields()}
|
|
733
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
750
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
734
751
|
ON CONFLICT(id) DO UPDATE SET
|
|
735
752
|
${sessionPersistAssignments()}`)
|
|
736
753
|
.run(...sessionPersistValues(snapshot));
|
|
@@ -841,6 +858,7 @@ const SCHEMA_MIGRATIONS = [
|
|
|
841
858
|
["runner", "ALTER TABLE command_sessions ADD COLUMN runner TEXT"],
|
|
842
859
|
["messages", "ALTER TABLE command_sessions ADD COLUMN messages TEXT"],
|
|
843
860
|
["queued_messages", "ALTER TABLE command_sessions ADD COLUMN queued_messages TEXT"],
|
|
861
|
+
["queued_message_skills", "ALTER TABLE command_sessions ADD COLUMN queued_message_skills TEXT"],
|
|
844
862
|
["structured_state", "ALTER TABLE command_sessions ADD COLUMN structured_state TEXT"],
|
|
845
863
|
["resumed_from_session_id", "ALTER TABLE command_sessions ADD COLUMN resumed_from_session_id TEXT"],
|
|
846
864
|
["resumed_to_session_id", "ALTER TABLE command_sessions ADD COLUMN resumed_to_session_id TEXT"],
|
|
@@ -56,6 +56,7 @@ export declare class StructuredSessionManager {
|
|
|
56
56
|
*/
|
|
57
57
|
private readonly pendingSdkQueries;
|
|
58
58
|
private readonly interruptedWith;
|
|
59
|
+
private readonly interruptedSkills;
|
|
59
60
|
/**
|
|
60
61
|
* Sessions where the current interrupt is a "queue promote" (用户从排队条点了「立即」
|
|
61
62
|
* 把队首插队到 now)。退出处理三个分支默认会把 queuedMessages 清空——因为常规的
|
|
@@ -118,6 +119,7 @@ export declare class StructuredSessionManager {
|
|
|
118
119
|
idempotencyKey?: string;
|
|
119
120
|
preserveQueue?: boolean;
|
|
120
121
|
queueAlreadyRemoved?: boolean;
|
|
122
|
+
skills?: string[];
|
|
121
123
|
}): Promise<SessionSnapshot>;
|
|
122
124
|
/**
|
|
123
125
|
* Reorder the pending queued messages. `order` is a permutation of the current
|
|
@@ -200,6 +202,7 @@ export declare class StructuredSessionManager {
|
|
|
200
202
|
private compactContentBlocks;
|
|
201
203
|
private buildCompletedAssistantMessages;
|
|
202
204
|
private resolveQueuedMessagesAfterInterrupt;
|
|
205
|
+
private resolveQueuedMessageSkillsAfterInterrupt;
|
|
203
206
|
private normalizeToolResultContent;
|
|
204
207
|
/**
|
|
205
208
|
* 组装结构化 runner 退出失败时的可读错误字符串。
|
|
@@ -97,6 +97,7 @@ function buildStructuredOutputPayload(snapshot) {
|
|
|
97
97
|
output: snapshot.output,
|
|
98
98
|
messages: snapshot.messages ? enrichStructuredMessages(snapshot.messages) : undefined,
|
|
99
99
|
queuedMessages: snapshot.queuedMessages,
|
|
100
|
+
queuedMessageSkills: snapshot.queuedMessageSkills,
|
|
100
101
|
sessionKind: "structured",
|
|
101
102
|
structuredState: snapshot.structuredState,
|
|
102
103
|
title: snapshot.title,
|
|
@@ -156,6 +157,7 @@ function buildIncrementalStructuredPayload(snapshot, cardDefaults) {
|
|
|
156
157
|
wandProtocolVersion: WAND_PROTOCOL_VERSION,
|
|
157
158
|
incremental: true,
|
|
158
159
|
queuedMessages: snapshot.queuedMessages,
|
|
160
|
+
queuedMessageSkills: snapshot.queuedMessageSkills,
|
|
159
161
|
sessionKind: "structured",
|
|
160
162
|
structuredState: snapshot.structuredState,
|
|
161
163
|
lastMessage,
|
|
@@ -177,6 +179,7 @@ export class StructuredSessionManager {
|
|
|
177
179
|
*/
|
|
178
180
|
pendingSdkQueries = new Map();
|
|
179
181
|
interruptedWith = new Map();
|
|
182
|
+
interruptedSkills = new Map();
|
|
180
183
|
/**
|
|
181
184
|
* Sessions where the current interrupt is a "queue promote" (用户从排队条点了「立即」
|
|
182
185
|
* 把队首插队到 now)。退出处理三个分支默认会把 queuedMessages 清空——因为常规的
|
|
@@ -242,6 +245,7 @@ export class StructuredSessionManager {
|
|
|
242
245
|
autoApprovePermissions: snapshot.autoApprovePermissions ?? shouldAutoApproveForMode(snapshot.mode),
|
|
243
246
|
approvalStats: snapshot.approvalStats ?? { tool: 0, command: 0, file: 0, total: 0 },
|
|
244
247
|
queuedMessages: snapshot.queuedMessages ?? [],
|
|
248
|
+
queuedMessageSkills: (snapshot.queuedMessages ?? []).map((_, index) => snapshot.queuedMessageSkills?.[index] ?? []),
|
|
245
249
|
pendingEscalation: null,
|
|
246
250
|
permissionBlocked: false,
|
|
247
251
|
structuredState: {
|
|
@@ -343,6 +347,7 @@ export class StructuredSessionManager {
|
|
|
343
347
|
this.pendingSdkQueries.clear();
|
|
344
348
|
this.pendingSdkAbort.clear();
|
|
345
349
|
this.interruptedWith.clear();
|
|
350
|
+
this.interruptedSkills.clear();
|
|
346
351
|
this.preserveQueueOnInterrupt.clear();
|
|
347
352
|
for (const timer of this.streamCheckpointTimers.values())
|
|
348
353
|
clearTimeout(timer);
|
|
@@ -590,6 +595,7 @@ export class StructuredSessionManager {
|
|
|
590
595
|
const prompt = input.trim();
|
|
591
596
|
if (!prompt)
|
|
592
597
|
return session;
|
|
598
|
+
const skills = opts?.skills ?? [];
|
|
593
599
|
if (opts?.idempotencyKey) {
|
|
594
600
|
const mapKey = `${id}:${opts.idempotencyKey}`;
|
|
595
601
|
if (this.seenIdempotencyKeys.has(mapKey)) {
|
|
@@ -636,6 +642,7 @@ export class StructuredSessionManager {
|
|
|
636
642
|
}
|
|
637
643
|
else if (opts?.interrupt) {
|
|
638
644
|
this.interruptedWith.set(id, prompt);
|
|
645
|
+
this.interruptedSkills.set(id, skills);
|
|
639
646
|
if (opts.preserveQueue) {
|
|
640
647
|
this.preserveQueueOnInterrupt.add(id);
|
|
641
648
|
// 「立即发送」排队条某一条:interrupt 把它作为新输入重发,但该条仍留在
|
|
@@ -648,7 +655,12 @@ export class StructuredSessionManager {
|
|
|
648
655
|
const removeAt = queue.indexOf(prompt);
|
|
649
656
|
if (removeAt !== -1) {
|
|
650
657
|
const trimmedQueue = queue.slice(0, removeAt).concat(queue.slice(removeAt + 1));
|
|
651
|
-
|
|
658
|
+
const queuedMessageSkills = session.queuedMessageSkills ?? [];
|
|
659
|
+
session = {
|
|
660
|
+
...session,
|
|
661
|
+
queuedMessages: trimmedQueue,
|
|
662
|
+
queuedMessageSkills: queuedMessageSkills.slice(0, removeAt).concat(queuedMessageSkills.slice(removeAt + 1)),
|
|
663
|
+
};
|
|
652
664
|
this.sessions.set(id, session);
|
|
653
665
|
this.storage.updateSessionRuntimeMetadata(session);
|
|
654
666
|
this.emitStructuredSnapshot(session);
|
|
@@ -679,6 +691,7 @@ export class StructuredSessionManager {
|
|
|
679
691
|
const queued = {
|
|
680
692
|
...session,
|
|
681
693
|
queuedMessages: [...queue, prompt],
|
|
694
|
+
queuedMessageSkills: [...queue.map((_, index) => session.queuedMessageSkills?.[index] ?? []), skills],
|
|
682
695
|
};
|
|
683
696
|
this.sessions.set(id, queued);
|
|
684
697
|
this.storage.updateSessionRuntimeMetadata(queued);
|
|
@@ -763,7 +776,7 @@ export class StructuredSessionManager {
|
|
|
763
776
|
});
|
|
764
777
|
}
|
|
765
778
|
else if (runner === "claude-sdk") {
|
|
766
|
-
await this.runClaudeSdkStreaming(id, updated, prompt, requestId);
|
|
779
|
+
await this.runClaudeSdkStreaming(id, updated, prompt, requestId, skills);
|
|
767
780
|
}
|
|
768
781
|
else {
|
|
769
782
|
await this.runClaudeStreaming(id, updated, cliClaudePrompt, requestId);
|
|
@@ -831,7 +844,11 @@ export class StructuredSessionManager {
|
|
|
831
844
|
seen.add(idx);
|
|
832
845
|
}
|
|
833
846
|
const reordered = order.map((idx) => queue[idx]);
|
|
834
|
-
const updated = {
|
|
847
|
+
const updated = {
|
|
848
|
+
...session,
|
|
849
|
+
queuedMessages: reordered,
|
|
850
|
+
queuedMessageSkills: order.map((idx) => (session.queuedMessageSkills ?? [])[idx] ?? []),
|
|
851
|
+
};
|
|
835
852
|
this.sessions.set(sessionId, updated);
|
|
836
853
|
this.storage.updateSessionRuntimeMetadata(updated);
|
|
837
854
|
this.emitStructuredSnapshot(updated);
|
|
@@ -845,7 +862,12 @@ export class StructuredSessionManager {
|
|
|
845
862
|
throw new Error("队列中没有该条消息(可能已被处理)。");
|
|
846
863
|
}
|
|
847
864
|
const next = queue.slice(0, index).concat(queue.slice(index + 1));
|
|
848
|
-
const
|
|
865
|
+
const skills = session.queuedMessageSkills ?? [];
|
|
866
|
+
const updated = {
|
|
867
|
+
...session,
|
|
868
|
+
queuedMessages: next,
|
|
869
|
+
queuedMessageSkills: skills.slice(0, index).concat(skills.slice(index + 1)),
|
|
870
|
+
};
|
|
849
871
|
this.sessions.set(sessionId, updated);
|
|
850
872
|
this.storage.updateSessionRuntimeMetadata(updated);
|
|
851
873
|
this.emitStructuredSnapshot(updated);
|
|
@@ -869,9 +891,16 @@ export class StructuredSessionManager {
|
|
|
869
891
|
throw new Error("排队消息已变化,请按最新顺序重试。");
|
|
870
892
|
}
|
|
871
893
|
const prompt = queue[index];
|
|
894
|
+
const skills = (session.queuedMessageSkills ?? [])[index] ?? [];
|
|
872
895
|
const remaining = queue.slice(0, index).concat(queue.slice(index + 1));
|
|
896
|
+
const remainingSkills = (session.queuedMessageSkills ?? []).slice(0, index)
|
|
897
|
+
.concat((session.queuedMessageSkills ?? []).slice(index + 1));
|
|
873
898
|
const inFlight = session.status === "running" && session.structuredState?.inFlight === true;
|
|
874
|
-
const updated = {
|
|
899
|
+
const updated = {
|
|
900
|
+
...session,
|
|
901
|
+
queuedMessages: remaining,
|
|
902
|
+
queuedMessageSkills: remainingSkills,
|
|
903
|
+
};
|
|
875
904
|
this.sessions.set(sessionId, updated);
|
|
876
905
|
this.storage.updateSessionRuntimeMetadata(updated);
|
|
877
906
|
this.emitStructuredSnapshot(updated);
|
|
@@ -881,6 +910,7 @@ export class StructuredSessionManager {
|
|
|
881
910
|
preserveQueue: inFlight,
|
|
882
911
|
queueAlreadyRemoved: true,
|
|
883
912
|
idempotencyKey,
|
|
913
|
+
skills,
|
|
884
914
|
});
|
|
885
915
|
}
|
|
886
916
|
catch {
|
|
@@ -895,7 +925,7 @@ export class StructuredSessionManager {
|
|
|
895
925
|
if (!session.queuedMessages || session.queuedMessages.length === 0) {
|
|
896
926
|
return session;
|
|
897
927
|
}
|
|
898
|
-
const updated = { ...session, queuedMessages: [] };
|
|
928
|
+
const updated = { ...session, queuedMessages: [], queuedMessageSkills: [] };
|
|
899
929
|
this.sessions.set(sessionId, updated);
|
|
900
930
|
this.storage.updateSessionRuntimeMetadata(updated);
|
|
901
931
|
this.emitStructuredSnapshot(updated);
|
|
@@ -1015,6 +1045,7 @@ export class StructuredSessionManager {
|
|
|
1015
1045
|
stop(id) {
|
|
1016
1046
|
const session = this.requireSession(id);
|
|
1017
1047
|
this.interruptedWith.delete(id);
|
|
1048
|
+
this.interruptedSkills.delete(id);
|
|
1018
1049
|
this.preserveQueueOnInterrupt.delete(id);
|
|
1019
1050
|
// Clearing activeRequestId is the generation barrier: late data/close callbacks
|
|
1020
1051
|
// from the cancelled runner can no longer mutate this session or a replacement turn.
|
|
@@ -1078,6 +1109,7 @@ export class StructuredSessionManager {
|
|
|
1078
1109
|
}
|
|
1079
1110
|
this.clearStreamingCheckpoint(id);
|
|
1080
1111
|
this.interruptedWith.delete(id);
|
|
1112
|
+
this.interruptedSkills.delete(id);
|
|
1081
1113
|
this.preserveQueueOnInterrupt.delete(id);
|
|
1082
1114
|
this.storage.deleteSession(id);
|
|
1083
1115
|
this.logger?.deleteSession(id);
|
|
@@ -1150,15 +1182,17 @@ export class StructuredSessionManager {
|
|
|
1150
1182
|
if (!nextInput) {
|
|
1151
1183
|
return;
|
|
1152
1184
|
}
|
|
1185
|
+
const [nextSkills = [], ...restSkills] = current.queuedMessageSkills ?? [];
|
|
1153
1186
|
const nextSession = {
|
|
1154
1187
|
...current,
|
|
1155
1188
|
queuedMessages: restQueue,
|
|
1189
|
+
queuedMessageSkills: restSkills,
|
|
1156
1190
|
};
|
|
1157
1191
|
this.sessions.set(sessionId, nextSession);
|
|
1158
1192
|
this.storage.updateSessionRuntimeMetadata(nextSession);
|
|
1159
1193
|
this.emitStructuredSnapshot(nextSession);
|
|
1160
1194
|
try {
|
|
1161
|
-
await this.sendMessage(sessionId, nextInput);
|
|
1195
|
+
await this.sendMessage(sessionId, nextInput, { skills: nextSkills });
|
|
1162
1196
|
}
|
|
1163
1197
|
catch (error) {
|
|
1164
1198
|
console.error("[WAND] flushNextQueuedMessage failed:", error);
|
|
@@ -1168,6 +1202,7 @@ export class StructuredSessionManager {
|
|
|
1168
1202
|
const rescued = {
|
|
1169
1203
|
...afterFail,
|
|
1170
1204
|
queuedMessages: [nextInput, ...(afterFail.queuedMessages ?? [])],
|
|
1205
|
+
queuedMessageSkills: [nextSkills, ...(afterFail.queuedMessageSkills ?? [])],
|
|
1171
1206
|
};
|
|
1172
1207
|
this.sessions.set(sessionId, rescued);
|
|
1173
1208
|
this.storage.updateSessionRuntimeMetadata(rescued);
|
|
@@ -1327,6 +1362,7 @@ export class StructuredSessionManager {
|
|
|
1327
1362
|
claudeSessionId: result.state.sessionId ?? current.claudeSessionId,
|
|
1328
1363
|
messages,
|
|
1329
1364
|
queuedMessages: this.resolveQueuedMessagesAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1365
|
+
queuedMessageSkills: this.resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1330
1366
|
pendingEscalation: null,
|
|
1331
1367
|
permissionBlocked: false,
|
|
1332
1368
|
structuredState: {
|
|
@@ -1343,10 +1379,12 @@ export class StructuredSessionManager {
|
|
|
1343
1379
|
if (!keepRunning)
|
|
1344
1380
|
this.emitStructuredSnapshot(finished, "ended");
|
|
1345
1381
|
if (interruptPrompt) {
|
|
1382
|
+
const interruptedSkills = this.interruptedSkills.get(sessionId) ?? [];
|
|
1346
1383
|
this.interruptedWith.delete(sessionId);
|
|
1384
|
+
this.interruptedSkills.delete(sessionId);
|
|
1347
1385
|
this.preserveQueueOnInterrupt.delete(sessionId);
|
|
1348
1386
|
setImmediate(() => {
|
|
1349
|
-
this.sendMessage(sessionId, interruptPrompt).catch((error) => {
|
|
1387
|
+
this.sendMessage(sessionId, interruptPrompt, { skills: interruptedSkills }).catch((error) => {
|
|
1350
1388
|
console.error("[WAND] codex interrupt-and-send failed:", error);
|
|
1351
1389
|
});
|
|
1352
1390
|
});
|
|
@@ -1473,6 +1511,7 @@ export class StructuredSessionManager {
|
|
|
1473
1511
|
claudeSessionId: result.state.sessionId ?? current.claudeSessionId,
|
|
1474
1512
|
messages,
|
|
1475
1513
|
queuedMessages: this.resolveQueuedMessagesAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1514
|
+
queuedMessageSkills: this.resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1476
1515
|
pendingEscalation: null,
|
|
1477
1516
|
permissionBlocked: false,
|
|
1478
1517
|
structuredState: {
|
|
@@ -1489,9 +1528,11 @@ export class StructuredSessionManager {
|
|
|
1489
1528
|
if (!keepRunning)
|
|
1490
1529
|
this.emitStructuredSnapshot(finished, "ended");
|
|
1491
1530
|
if (interruptPrompt) {
|
|
1531
|
+
const interruptedSkills = this.interruptedSkills.get(sessionId) ?? [];
|
|
1492
1532
|
this.interruptedWith.delete(sessionId);
|
|
1533
|
+
this.interruptedSkills.delete(sessionId);
|
|
1493
1534
|
this.preserveQueueOnInterrupt.delete(sessionId);
|
|
1494
|
-
setImmediate(() => this.sendMessage(sessionId, interruptPrompt).catch((error) => {
|
|
1535
|
+
setImmediate(() => this.sendMessage(sessionId, interruptPrompt, { skills: interruptedSkills }).catch((error) => {
|
|
1495
1536
|
console.error("[WAND] grok interrupt-and-send failed:", error);
|
|
1496
1537
|
}));
|
|
1497
1538
|
return;
|
|
@@ -1625,6 +1666,7 @@ export class StructuredSessionManager {
|
|
|
1625
1666
|
claudeSessionId: result.state.sessionId ?? current.claudeSessionId,
|
|
1626
1667
|
messages,
|
|
1627
1668
|
queuedMessages: this.resolveQueuedMessagesAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1669
|
+
queuedMessageSkills: this.resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1628
1670
|
pendingEscalation: null,
|
|
1629
1671
|
permissionBlocked: false,
|
|
1630
1672
|
structuredState: {
|
|
@@ -1641,10 +1683,12 @@ export class StructuredSessionManager {
|
|
|
1641
1683
|
if (!keepRunning)
|
|
1642
1684
|
this.emitStructuredSnapshot(finished, "ended");
|
|
1643
1685
|
if (interruptPrompt) {
|
|
1686
|
+
const interruptedSkills = this.interruptedSkills.get(sessionId) ?? [];
|
|
1644
1687
|
this.interruptedWith.delete(sessionId);
|
|
1688
|
+
this.interruptedSkills.delete(sessionId);
|
|
1645
1689
|
this.preserveQueueOnInterrupt.delete(sessionId);
|
|
1646
1690
|
setImmediate(() => {
|
|
1647
|
-
this.sendMessage(sessionId, interruptPrompt).catch((error) => {
|
|
1691
|
+
this.sendMessage(sessionId, interruptPrompt, { skills: interruptedSkills }).catch((error) => {
|
|
1648
1692
|
console.error("[WAND] opencode interrupt-and-send failed:", error);
|
|
1649
1693
|
});
|
|
1650
1694
|
});
|
|
@@ -1805,6 +1849,7 @@ export class StructuredSessionManager {
|
|
|
1805
1849
|
claudeSessionId: result.state.sessionId ?? current.claudeSessionId,
|
|
1806
1850
|
messages,
|
|
1807
1851
|
queuedMessages: this.resolveQueuedMessagesAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1852
|
+
queuedMessageSkills: this.resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt),
|
|
1808
1853
|
pendingEscalation: null,
|
|
1809
1854
|
permissionBlocked: false,
|
|
1810
1855
|
structuredState: {
|
|
@@ -1821,10 +1866,12 @@ export class StructuredSessionManager {
|
|
|
1821
1866
|
if (!keepRunning)
|
|
1822
1867
|
this.emitStructuredSnapshot(finished, "ended");
|
|
1823
1868
|
if (interruptPrompt) {
|
|
1869
|
+
const interruptedSkills = this.interruptedSkills.get(sessionId) ?? [];
|
|
1824
1870
|
this.interruptedWith.delete(sessionId);
|
|
1871
|
+
this.interruptedSkills.delete(sessionId);
|
|
1825
1872
|
this.preserveQueueOnInterrupt.delete(sessionId);
|
|
1826
1873
|
setImmediate(() => {
|
|
1827
|
-
this.sendMessage(sessionId, interruptPrompt).catch((error) => {
|
|
1874
|
+
this.sendMessage(sessionId, interruptPrompt, { skills: interruptedSkills }).catch((error) => {
|
|
1828
1875
|
console.error("[WAND] interrupt-and-send failed:", error);
|
|
1829
1876
|
});
|
|
1830
1877
|
});
|
|
@@ -1860,7 +1907,7 @@ export class StructuredSessionManager {
|
|
|
1860
1907
|
* payloads for incremental text/thinking/tool_use updates, followed by a final
|
|
1861
1908
|
* SDKAssistantMessage with the authoritative complete content.
|
|
1862
1909
|
*/
|
|
1863
|
-
async runClaudeSdkStreaming(sessionId, session, prompt, requestId) {
|
|
1910
|
+
async runClaudeSdkStreaming(sessionId, session, prompt, requestId, skills) {
|
|
1864
1911
|
const abortController = new AbortController();
|
|
1865
1912
|
this.pendingSdkAbort.set(sessionId, abortController);
|
|
1866
1913
|
const isManaged = session.mode === "managed";
|
|
@@ -1881,6 +1928,7 @@ export class StructuredSessionManager {
|
|
|
1881
1928
|
...(permPolicy.permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {}),
|
|
1882
1929
|
...(permPolicy.allowedTools ? { allowedTools: permPolicy.allowedTools } : {}),
|
|
1883
1930
|
...(isManaged ? { disallowedTools: ["AskUserQuestion"] } : {}),
|
|
1931
|
+
skills,
|
|
1884
1932
|
thinking: sdkThinking,
|
|
1885
1933
|
includePartialMessages: true,
|
|
1886
1934
|
// 把子 agent 的 text/thinking 也转发回来,UI 才能把"被 Task 召唤来的协作者"
|
|
@@ -2284,6 +2332,7 @@ export class StructuredSessionManager {
|
|
|
2284
2332
|
claudeSessionId: turnState.sessionId ?? current.claudeSessionId,
|
|
2285
2333
|
messages: msgs,
|
|
2286
2334
|
queuedMessages: this.resolveQueuedMessagesAfterInterrupt(sessionId, current, interruptPrompt),
|
|
2335
|
+
queuedMessageSkills: this.resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt),
|
|
2287
2336
|
pendingEscalation: null,
|
|
2288
2337
|
permissionBlocked: false,
|
|
2289
2338
|
structuredState: {
|
|
@@ -2300,11 +2349,13 @@ export class StructuredSessionManager {
|
|
|
2300
2349
|
if (!keepRunning)
|
|
2301
2350
|
this.emitStructuredSnapshot(finished, "ended");
|
|
2302
2351
|
if (interruptPrompt) {
|
|
2352
|
+
const interruptedSkills = this.interruptedSkills.get(sessionId) ?? [];
|
|
2303
2353
|
this.interruptedWith.delete(sessionId);
|
|
2354
|
+
this.interruptedSkills.delete(sessionId);
|
|
2304
2355
|
// 与 codex/cli runner 对齐:清掉"保留队列"标记,避免 stale flag 影响下一次普通 interrupt。
|
|
2305
2356
|
this.preserveQueueOnInterrupt.delete(sessionId);
|
|
2306
2357
|
setImmediate(() => {
|
|
2307
|
-
this.sendMessage(sessionId, interruptPrompt).catch((err) => {
|
|
2358
|
+
this.sendMessage(sessionId, interruptPrompt, { skills: interruptedSkills }).catch((err) => {
|
|
2308
2359
|
console.error("[WAND] sdk interrupt-and-send failed:", err);
|
|
2309
2360
|
});
|
|
2310
2361
|
});
|
|
@@ -2380,6 +2431,12 @@ export class StructuredSessionManager {
|
|
|
2380
2431
|
return [];
|
|
2381
2432
|
return current.queuedMessages;
|
|
2382
2433
|
}
|
|
2434
|
+
resolveQueuedMessageSkillsAfterInterrupt(sessionId, current, interruptPrompt) {
|
|
2435
|
+
if (interruptPrompt && !this.preserveQueueOnInterrupt.has(sessionId))
|
|
2436
|
+
return [];
|
|
2437
|
+
const queue = current.queuedMessages ?? [];
|
|
2438
|
+
return queue.map((_, index) => current.queuedMessageSkills?.[index] ?? []);
|
|
2439
|
+
}
|
|
2383
2440
|
normalizeToolResultContent(content) {
|
|
2384
2441
|
return normalizeStructuredToolResultContent(content);
|
|
2385
2442
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -514,6 +514,8 @@ export interface SessionSnapshot {
|
|
|
514
514
|
messages?: ConversationTurn[];
|
|
515
515
|
/** Pending structured user inputs queued while an assistant response is in flight. */
|
|
516
516
|
queuedMessages?: string[];
|
|
517
|
+
/** Per-message Claude Agent SDK skill allowlists aligned with queuedMessages. */
|
|
518
|
+
queuedMessageSkills?: string[][];
|
|
517
519
|
structuredState?: StructuredSessionState;
|
|
518
520
|
/** 此会话是从哪个 Wand 会话恢复而来 */
|
|
519
521
|
resumedFromSessionId?: string | null;
|