@co0ontty/wand 1.14.6 → 1.15.1
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/message-truncator.js +0 -2
- package/dist/process-manager.js +38 -65
- package/dist/pwa.js +9 -2
- package/dist/server.js +15 -4
- package/dist/structured-session-manager.js +21 -4
- package/dist/types.d.ts +0 -2
- package/dist/web-ui/content/scripts.js +464 -145
- package/dist/web-ui/content/styles.css +596 -53
- package/package.json +3 -1
|
@@ -42,7 +42,6 @@ export function truncateMessagesForTransport(messages, cardDefaults, streamingTu
|
|
|
42
42
|
// Never truncate the currently streaming turn
|
|
43
43
|
if (turnIndex === streamingTurnIndex)
|
|
44
44
|
return turn;
|
|
45
|
-
// Build tool_use_id → tool_name map from this turn's blocks
|
|
46
45
|
const toolNameMap = new Map();
|
|
47
46
|
for (const block of turn.content) {
|
|
48
47
|
if (block.type === "tool_use") {
|
|
@@ -68,7 +67,6 @@ export function truncateMessagesForTransport(messages, cardDefaults, streamingTu
|
|
|
68
67
|
...result,
|
|
69
68
|
content: contentStr.slice(0, SUMMARY_LENGTH) + "…",
|
|
70
69
|
_truncated: true,
|
|
71
|
-
_originalSize: contentStr.length,
|
|
72
70
|
};
|
|
73
71
|
});
|
|
74
72
|
return changed ? { ...turn, content: truncatedContent } : turn;
|
package/dist/process-manager.js
CHANGED
|
@@ -787,8 +787,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
787
787
|
type: "output",
|
|
788
788
|
sessionId: id,
|
|
789
789
|
data: {
|
|
790
|
+
incremental: true,
|
|
790
791
|
chunk,
|
|
791
|
-
output: rec.output,
|
|
792
792
|
permissionBlocked: this.isPermissionBlocked(rec),
|
|
793
793
|
},
|
|
794
794
|
});
|
|
@@ -1184,38 +1184,11 @@ export class ProcessManager extends EventEmitter {
|
|
|
1184
1184
|
}
|
|
1185
1185
|
/** Lightweight snapshot for list views — omits output and messages. */
|
|
1186
1186
|
snapshotSlim(record) {
|
|
1187
|
-
const
|
|
1187
|
+
const snapshot = this.snapshot(record);
|
|
1188
1188
|
return {
|
|
1189
|
-
|
|
1190
|
-
sessionKind: "pty",
|
|
1191
|
-
provider: record.provider,
|
|
1192
|
-
runner: "pty",
|
|
1193
|
-
command: record.command,
|
|
1194
|
-
cwd: record.cwd,
|
|
1195
|
-
mode: record.mode,
|
|
1196
|
-
worktreeEnabled: record.worktreeEnabled ?? false,
|
|
1197
|
-
worktree: record.worktree ?? null,
|
|
1198
|
-
autonomyPolicy: record.autonomyPolicy,
|
|
1199
|
-
approvalPolicy: record.approvalPolicy,
|
|
1200
|
-
allowedScopes: record.allowedScopes,
|
|
1201
|
-
status: record.status,
|
|
1202
|
-
exitCode: record.exitCode,
|
|
1203
|
-
startedAt: record.startedAt,
|
|
1204
|
-
endedAt: record.endedAt,
|
|
1189
|
+
...snapshot,
|
|
1205
1190
|
output: "",
|
|
1206
|
-
|
|
1207
|
-
archivedAt: record.archivedAt,
|
|
1208
|
-
permissionBlocked: this.isPermissionBlocked(record),
|
|
1209
|
-
pendingEscalation: record.pendingEscalation || undefined,
|
|
1210
|
-
lastEscalationResult: record.lastEscalationResult || undefined,
|
|
1211
|
-
claudeSessionId: record.claudeSessionId || null,
|
|
1212
|
-
resumedFromSessionId: record.resumedFromSessionId ?? undefined,
|
|
1213
|
-
resumedToSessionId: record.resumedToSessionId ?? undefined,
|
|
1214
|
-
autoRecovered: record.autoRecovered ?? false,
|
|
1215
|
-
autoApprovePermissions: record.autoApprovePermissions || undefined,
|
|
1216
|
-
approvalStats: record.approvalStats.total > 0 ? record.approvalStats : undefined,
|
|
1217
|
-
summary: deriveSessionSummary(messages, record.currentTask?.title ?? null),
|
|
1218
|
-
currentTaskTitle: record.status === "running" ? record.currentTask?.title ?? undefined : undefined,
|
|
1191
|
+
messages: undefined,
|
|
1219
1192
|
};
|
|
1220
1193
|
}
|
|
1221
1194
|
isPermissionBlocked(record) {
|
|
@@ -1481,42 +1454,37 @@ export class ProcessManager extends EventEmitter {
|
|
|
1481
1454
|
handleBridgeEvent(record, event) {
|
|
1482
1455
|
switch (event.type) {
|
|
1483
1456
|
case "output.raw":
|
|
1457
|
+
case "output.chat": {
|
|
1484
1458
|
// Sync record.output from bridge before emitting so the event carries fresh data
|
|
1485
1459
|
record.output = record.ptyBridge?.getRawOutput() ?? record.output;
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
},
|
|
1499
|
-
});
|
|
1460
|
+
const rawMessages = record.ptyBridge?.getMessages() ?? [];
|
|
1461
|
+
const isStreaming = record.status === "running";
|
|
1462
|
+
const data = {
|
|
1463
|
+
permissionBlocked: this.isPermissionBlocked(record),
|
|
1464
|
+
};
|
|
1465
|
+
if (isStreaming && rawMessages.length > 0) {
|
|
1466
|
+
// Incremental mode: send only chunk + last (streaming) turn
|
|
1467
|
+
data.incremental = true;
|
|
1468
|
+
const lastTurn = rawMessages[rawMessages.length - 1];
|
|
1469
|
+
const truncatedLast = truncateMessagesForTransport([lastTurn], this.config.cardDefaults ?? {}, 0);
|
|
1470
|
+
data.lastMessage = truncatedLast[0];
|
|
1471
|
+
data.messageCount = rawMessages.length;
|
|
1500
1472
|
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
{
|
|
1506
|
-
const rawMessages = record.ptyBridge?.getMessages() ?? [];
|
|
1507
|
-
const messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
|
|
1508
|
-
// Emit output event with updated messages for chat view
|
|
1509
|
-
this.emitEvent({
|
|
1510
|
-
type: "output",
|
|
1511
|
-
sessionId: event.sessionId,
|
|
1512
|
-
data: {
|
|
1513
|
-
output: record.output,
|
|
1514
|
-
messages,
|
|
1515
|
-
permissionBlocked: this.isPermissionBlocked(record),
|
|
1516
|
-
},
|
|
1517
|
-
});
|
|
1473
|
+
else {
|
|
1474
|
+
// Full mode: non-streaming or empty messages
|
|
1475
|
+
data.output = record.output;
|
|
1476
|
+
data.messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
|
|
1518
1477
|
}
|
|
1478
|
+
if (event.type === "output.raw") {
|
|
1479
|
+
data.chunk = event.data.chunk;
|
|
1480
|
+
}
|
|
1481
|
+
this.emitEvent({
|
|
1482
|
+
type: "output",
|
|
1483
|
+
sessionId: event.sessionId,
|
|
1484
|
+
data,
|
|
1485
|
+
});
|
|
1519
1486
|
break;
|
|
1487
|
+
}
|
|
1520
1488
|
case "permission.prompt": {
|
|
1521
1489
|
const data = event.data;
|
|
1522
1490
|
record.pendingEscalation = {
|
|
@@ -1681,14 +1649,19 @@ export class ProcessManager extends EventEmitter {
|
|
|
1681
1649
|
}
|
|
1682
1650
|
}
|
|
1683
1651
|
}
|
|
1652
|
+
const language = this.config.language?.trim();
|
|
1653
|
+
const isChinese = language === "中文";
|
|
1684
1654
|
if (mode === "managed") {
|
|
1685
|
-
const autonomousPrompt =
|
|
1655
|
+
const autonomousPrompt = isChinese
|
|
1656
|
+
? "你正在完全托管的自主模式下运行。用户可能无法及时回复问题或确认。你必须独立做出所有决策——自行选择最佳方案,而不是向用户询问偏好、确认或澄清。如果有多种可行方案,选择你认为最合适的并继续执行。除非任务本身存在根本性的歧义且无法合理推断,否则不要等待用户输入。果断行动,自主决策。"
|
|
1657
|
+
: "You are running in a fully managed, autonomous mode. The user may not be available to respond to questions or confirmations in a timely manner. You MUST make all decisions independently — choose the best approach yourself instead of asking the user for preferences, confirmations, or clarifications. If multiple approaches are viable, pick the one you judge most appropriate and proceed. Never block on user input unless the task is fundamentally ambiguous and cannot be reasonably inferred. Be decisive and self-directed.";
|
|
1686
1658
|
const escaped = autonomousPrompt.replace(/'/g, "'\\''");
|
|
1687
1659
|
result += ` --append-system-prompt '${escaped}'`;
|
|
1688
1660
|
}
|
|
1689
|
-
const language = this.config.language?.trim();
|
|
1690
1661
|
if (language) {
|
|
1691
|
-
const langPrompt =
|
|
1662
|
+
const langPrompt = isChinese
|
|
1663
|
+
? "请使用中文回复。所有解释、注释和对话文本都使用中文。"
|
|
1664
|
+
: `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`;
|
|
1692
1665
|
const escaped = langPrompt.replace(/'/g, "'\\''");
|
|
1693
1666
|
result += ` --append-system-prompt '${escaped}'`;
|
|
1694
1667
|
}
|
package/dist/pwa.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PWA manifest and Service Worker generation.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
10
|
+
const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf-8")).version ?? "0";
|
|
11
|
+
/** Cache version derived from package version — only busts on actual releases */
|
|
12
|
+
const CACHE_VERSION = createHash("md5").update(pkgVersion).digest("hex").slice(0, 8);
|
|
6
13
|
export function generatePwaManifest() {
|
|
7
14
|
return JSON.stringify({
|
|
8
15
|
id: "/wand",
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import compression from "compression";
|
|
2
3
|
import express from "express";
|
|
3
4
|
import { createReadStream, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
@@ -485,9 +486,11 @@ export async function startServer(config, configPath) {
|
|
|
485
486
|
const protocol = useHttps ? "https" : "http";
|
|
486
487
|
const nodeModulesDir = path.join(RUNTIME_ROOT_DIR, "node_modules");
|
|
487
488
|
app.use(express.json({ limit: "1mb" }));
|
|
488
|
-
app.use(
|
|
489
|
-
|
|
490
|
-
app.use("/vendor/xterm
|
|
489
|
+
app.use(compression({ threshold: 1024 }));
|
|
490
|
+
const vendorCacheOpts = { maxAge: "7d", immutable: true };
|
|
491
|
+
app.use("/vendor/xterm", express.static(path.join(nodeModulesDir, "@xterm", "xterm"), vendorCacheOpts));
|
|
492
|
+
app.use("/vendor/xterm-addon-fit", express.static(path.join(nodeModulesDir, "@xterm", "addon-fit"), vendorCacheOpts));
|
|
493
|
+
app.use("/vendor/xterm-addon-serialize", express.static(path.join(nodeModulesDir, "@xterm", "addon-serialize"), vendorCacheOpts));
|
|
491
494
|
// ── Web UI and PWA endpoints ──
|
|
492
495
|
app.get("/", (_req, res) => {
|
|
493
496
|
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
|
@@ -1142,7 +1145,15 @@ export async function startServer(config, configPath) {
|
|
|
1142
1145
|
return createHttpsServer({ key: ssl.key, cert: ssl.cert }, app);
|
|
1143
1146
|
})()
|
|
1144
1147
|
: createHttpServer(app);
|
|
1145
|
-
const wss = new WebSocketServer({
|
|
1148
|
+
const wss = new WebSocketServer({
|
|
1149
|
+
server,
|
|
1150
|
+
path: "/ws",
|
|
1151
|
+
perMessageDeflate: {
|
|
1152
|
+
zlibDeflateOptions: { level: 1 },
|
|
1153
|
+
threshold: 512,
|
|
1154
|
+
concurrencyLimit: 10,
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1146
1157
|
const wsManager = new WsBroadcastManager(wss, () => config.cardDefaults ?? {});
|
|
1147
1158
|
wsManager.setup((id) => structuredSessions.get(id) ?? processes.get(id));
|
|
1148
1159
|
// Wire process events to WebSocket broadcast
|
|
@@ -35,6 +35,17 @@ function buildStructuredOutputPayload(snapshot) {
|
|
|
35
35
|
structuredState: snapshot.structuredState,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
function buildIncrementalStructuredPayload(snapshot) {
|
|
39
|
+
const messages = snapshot.messages ?? [];
|
|
40
|
+
return {
|
|
41
|
+
incremental: true,
|
|
42
|
+
queuedMessages: snapshot.queuedMessages,
|
|
43
|
+
sessionKind: "structured",
|
|
44
|
+
structuredState: snapshot.structuredState,
|
|
45
|
+
lastMessage: messages.length > 0 ? messages[messages.length - 1] : undefined,
|
|
46
|
+
messageCount: messages.length,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
38
49
|
export class StructuredSessionManager {
|
|
39
50
|
storage;
|
|
40
51
|
config;
|
|
@@ -474,14 +485,20 @@ export class StructuredSessionManager {
|
|
|
474
485
|
// Add permission args based on mode + autoApprovePermissions toggle
|
|
475
486
|
const permArgs = this.buildPermissionArgs(session.mode, session.autoApprovePermissions ?? false);
|
|
476
487
|
args.push(...permArgs);
|
|
488
|
+
// Append language-aware system prompts
|
|
489
|
+
const language = this.config.language?.trim();
|
|
490
|
+
const isChinese = language === "中文";
|
|
477
491
|
// In managed mode, append autonomous system prompt
|
|
478
492
|
if (session.mode === "managed") {
|
|
479
|
-
args.push("--append-system-prompt",
|
|
493
|
+
args.push("--append-system-prompt", isChinese
|
|
494
|
+
? "你正在完全托管的自主模式下运行。用户可能无法及时回复问题或确认。你必须独立做出所有决策——自行选择最佳方案,而不是向用户询问偏好、确认或澄清。如果有多种可行方案,选择你认为最合适的并继续执行。除非任务本身存在根本性的歧义且无法合理推断,否则不要等待用户输入。果断行动,自主决策。"
|
|
495
|
+
: "You are running in a fully managed, autonomous mode. The user may not be available to respond to questions or confirmations in a timely manner. You MUST make all decisions independently — choose the best approach yourself instead of asking the user for preferences, confirmations, or clarifications. If multiple approaches are viable, pick the one you judge most appropriate and proceed. Never block on user input unless the task is fundamentally ambiguous and cannot be reasonably inferred. Be decisive and self-directed.");
|
|
480
496
|
}
|
|
481
497
|
// Append language preference if configured
|
|
482
|
-
const language = this.config.language?.trim();
|
|
483
498
|
if (language) {
|
|
484
|
-
args.push("--append-system-prompt",
|
|
499
|
+
args.push("--append-system-prompt", isChinese
|
|
500
|
+
? "请使用中文回复。所有解释、注释和对话文本都使用中文。"
|
|
501
|
+
: `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`);
|
|
485
502
|
}
|
|
486
503
|
if (session.claudeSessionId) {
|
|
487
504
|
args.push("--resume", session.claudeSessionId);
|
|
@@ -516,7 +533,7 @@ export class StructuredSessionManager {
|
|
|
516
533
|
this.emit({
|
|
517
534
|
type: "output",
|
|
518
535
|
sessionId,
|
|
519
|
-
data:
|
|
536
|
+
data: buildIncrementalStructuredPayload(current),
|
|
520
537
|
});
|
|
521
538
|
};
|
|
522
539
|
const scheduleEmit = () => {
|
package/dist/types.d.ts
CHANGED
|
@@ -191,8 +191,6 @@ export interface ToolResultBlock {
|
|
|
191
191
|
is_error?: boolean;
|
|
192
192
|
/** When true, content has been truncated for transport. Client should fetch full content via API. */
|
|
193
193
|
_truncated?: boolean;
|
|
194
|
-
/** Original content size in bytes, provided when truncated. */
|
|
195
|
-
_originalSize?: number;
|
|
196
194
|
}
|
|
197
195
|
export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
|
|
198
196
|
export interface ConversationTurn {
|