@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.
@@ -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;
@@ -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 messages = record.ptyBridge?.getMessages() ?? record.messages;
1187
+ const snapshot = this.snapshot(record);
1188
1188
  return {
1189
- id: record.id,
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
- archived: record.archived,
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
- const rawMessages = record.ptyBridge?.getMessages() ?? [];
1488
- const messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
1489
- // Emit output event for terminal view
1490
- this.emitEvent({
1491
- type: "output",
1492
- sessionId: event.sessionId,
1493
- data: {
1494
- chunk: event.data.chunk,
1495
- output: record.output,
1496
- messages,
1497
- permissionBlocked: this.isPermissionBlocked(record),
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
- break;
1502
- case "output.chat":
1503
- // Sync record.output from bridge before emitting so the event carries fresh data
1504
- record.output = record.ptyBridge?.getRawOutput() ?? record.output;
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 = "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.";
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 = `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`;
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
- /** Cache version is fixed per server process to avoid mid-session cache busting */
5
- const CACHE_VERSION = Date.now().toString(36);
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("/vendor/xterm", express.static(path.join(nodeModulesDir, "@xterm", "xterm")));
489
- app.use("/vendor/xterm-addon-fit", express.static(path.join(nodeModulesDir, "@xterm", "addon-fit")));
490
- app.use("/vendor/xterm-addon-serialize", express.static(path.join(nodeModulesDir, "@xterm", "addon-serialize")));
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({ server, path: "/ws" });
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", "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.");
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", `Please respond in ${language}. Use ${language} for all your explanations, comments, and conversational text.`);
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: buildStructuredOutputPayload(current),
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 {