@co0ontty/wand 1.15.0 → 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.
@@ -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
  });
@@ -1458,12 +1458,23 @@ export class ProcessManager extends EventEmitter {
1458
1458
  // Sync record.output from bridge before emitting so the event carries fresh data
1459
1459
  record.output = record.ptyBridge?.getRawOutput() ?? record.output;
1460
1460
  const rawMessages = record.ptyBridge?.getMessages() ?? [];
1461
- const messages = truncateMessagesForTransport(rawMessages, this.config.cardDefaults ?? {}, rawMessages.length - 1);
1461
+ const isStreaming = record.status === "running";
1462
1462
  const data = {
1463
- output: record.output,
1464
- messages,
1465
1463
  permissionBlocked: this.isPermissionBlocked(record),
1466
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;
1472
+ }
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);
1477
+ }
1467
1478
  if (event.type === "output.raw") {
1468
1479
  data.chunk = event.data.chunk;
1469
1480
  }
@@ -1638,14 +1649,19 @@ export class ProcessManager extends EventEmitter {
1638
1649
  }
1639
1650
  }
1640
1651
  }
1652
+ const language = this.config.language?.trim();
1653
+ const isChinese = language === "中文";
1641
1654
  if (mode === "managed") {
1642
- 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.";
1643
1658
  const escaped = autonomousPrompt.replace(/'/g, "'\\''");
1644
1659
  result += ` --append-system-prompt '${escaped}'`;
1645
1660
  }
1646
- const language = this.config.language?.trim();
1647
1661
  if (language) {
1648
- 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.`;
1649
1665
  const escaped = langPrompt.replace(/'/g, "'\\''");
1650
1666
  result += ` --append-system-prompt '${escaped}'`;
1651
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 = () => {
@@ -9906,15 +9906,17 @@
9906
9906
  case 'output':
9907
9907
  // Update session output (for terminal display and local message parsing)
9908
9908
  // NOTE: For structured sessions, output may be "" during streaming — check messages too
9909
- if (msg.data && (msg.data.output || msg.data.messages) && msg.sessionId) {
9910
- var snapshot = { id: msg.sessionId, output: msg.data.output };
9909
+ if (msg.data && msg.sessionId) {
9910
+ var isIncremental = !!msg.data.incremental;
9911
+ var snapshot = { id: msg.sessionId };
9912
+
9913
+ // Carry over small metadata fields present in both modes
9914
+ if (!isIncremental && msg.data.output !== undefined) {
9915
+ snapshot.output = msg.data.output;
9916
+ }
9911
9917
  if (Object.prototype.hasOwnProperty.call(msg.data, 'permissionBlocked')) {
9912
9918
  snapshot.permissionBlocked = !!msg.data.permissionBlocked;
9913
9919
  }
9914
- // Pass structured messages if available from JSON chat mode
9915
- if (msg.data.messages) {
9916
- snapshot.messages = msg.data.messages;
9917
- }
9918
9920
  if (Object.prototype.hasOwnProperty.call(msg.data, 'queuedMessages')) {
9919
9921
  snapshot.queuedMessages = msg.data.queuedMessages || [];
9920
9922
  state.queueEpoch++;
@@ -9922,19 +9924,44 @@
9922
9924
  if (msg.data.structuredState) {
9923
9925
  snapshot.structuredState = msg.data.structuredState;
9924
9926
  }
9925
- updateSessionSnapshot(snapshot);
9926
- if (msg.sessionId === state.selectedId) {
9927
- var updatedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; }) || snapshot;
9928
- state.currentMessages = buildMessagesForRender(updatedSession, getPreferredMessages(updatedSession, msg.data.output, false));
9929
- updateTaskDisplay();
9930
- // Structured sessions: render immediately for responsiveness
9931
- if (updatedSession.sessionKind === 'structured' || msg.data.sessionKind === 'structured') {
9932
- renderChat();
9933
- } else {
9934
- scheduleChatRender();
9927
+ if (msg.data.sessionKind) {
9928
+ snapshot.sessionKind = msg.data.sessionKind;
9929
+ }
9930
+
9931
+ if (isIncremental && msg.data.lastMessage) {
9932
+ // Incremental mode: merge lastMessage into existing session messages
9933
+ var existingSession = state.sessions.find(function(s) { return s.id === msg.sessionId; });
9934
+ if (existingSession) {
9935
+ var msgs = Array.isArray(existingSession.messages) ? existingSession.messages.slice() : [];
9936
+ var expectedCount = msg.data.messageCount || 0;
9937
+ // Replace last turn if same role, or append if new turn
9938
+ if (msgs.length > 0 && msg.data.lastMessage.role && msgs[msgs.length - 1].role === msg.data.lastMessage.role) {
9939
+ msgs[msgs.length - 1] = msg.data.lastMessage;
9940
+ } else if (msgs.length < expectedCount) {
9941
+ msgs.push(msg.data.lastMessage);
9942
+ }
9943
+ snapshot.messages = msgs;
9935
9944
  }
9945
+ } else if (!isIncremental && msg.data.messages) {
9946
+ // Full mode (backward compatible)
9947
+ snapshot.messages = msg.data.messages;
9936
9948
  }
9937
9949
 
9950
+ // Only update if we have meaningful data
9951
+ if (snapshot.output !== undefined || snapshot.messages || isIncremental || msg.data.permissionBlocked !== undefined) {
9952
+ updateSessionSnapshot(snapshot);
9953
+ if (msg.sessionId === state.selectedId) {
9954
+ var updatedSession = state.sessions.find(function(s) { return s.id === msg.sessionId; }) || snapshot;
9955
+ state.currentMessages = buildMessagesForRender(updatedSession, getPreferredMessages(updatedSession, updatedSession.output, false));
9956
+ updateTaskDisplay();
9957
+ // Structured sessions: render immediately for responsiveness
9958
+ if (updatedSession.sessionKind === 'structured' || msg.data.sessionKind === 'structured') {
9959
+ renderChat();
9960
+ } else {
9961
+ scheduleChatRender();
9962
+ }
9963
+ }
9964
+ }
9938
9965
  }
9939
9966
  // Real-time terminal output
9940
9967
  if (msg.sessionId === state.selectedId && state.terminal && msg.data) {
@@ -9952,8 +9979,8 @@
9952
9979
  maybeScrollTerminalToBottom("output");
9953
9980
  updateTerminalJumpToBottomButton();
9954
9981
  scheduleMobileDomUpdate();
9955
- } else if (Object.prototype.hasOwnProperty.call(msg.data, "output")) {
9956
- // Fallback: no chunk available, use full-output comparison
9982
+ } else if (!msg.data.incremental && Object.prototype.hasOwnProperty.call(msg.data, "output")) {
9983
+ // Fallback: no chunk available, use full-output comparison (only in full mode)
9957
9984
  syncTerminalBuffer(msg.sessionId, msg.data.output || "", { mode: "append" });
9958
9985
  }
9959
9986
  }
@@ -7357,6 +7357,8 @@
7357
7357
  flex: 1 1 auto;
7358
7358
  min-height: 0;
7359
7359
  overflow: hidden;
7360
+ display: flex;
7361
+ flex-direction: column;
7360
7362
  }
7361
7363
 
7362
7364
  .settings-layout {
@@ -7364,7 +7366,7 @@
7364
7366
  grid-template-columns: 260px minmax(0, 1fr);
7365
7367
  gap: 22px;
7366
7368
  min-height: 0;
7367
- height: 100%;
7369
+ flex: 1 1 auto;
7368
7370
  align-items: stretch;
7369
7371
  }
7370
7372
 
@@ -7641,9 +7643,14 @@
7641
7643
  min-height: min(92vh, 920px);
7642
7644
  }
7643
7645
 
7646
+ .settings-modal-body {
7647
+ overflow-y: auto;
7648
+ }
7649
+
7644
7650
  .settings-layout {
7645
7651
  grid-template-columns: minmax(0, 1fr);
7646
7652
  gap: 14px;
7653
+ flex: 0 0 auto;
7647
7654
  }
7648
7655
 
7649
7656
  .settings-sidebar {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@co0ontty/wand",
3
- "version": "1.15.0",
3
+ "version": "1.15.1",
4
4
  "description": "A web terminal for local CLI tools like Claude.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,10 +33,12 @@
33
33
  "node": ">=22.5.0"
34
34
  },
35
35
  "dependencies": {
36
+ "@types/compression": "^1.8.1",
36
37
  "@types/cookie": "^0.6.0",
37
38
  "@xterm/addon-fit": "^0.11.0",
38
39
  "@xterm/addon-serialize": "^0.14.0",
39
40
  "@xterm/xterm": "^5.5.0",
41
+ "compression": "^1.8.1",
40
42
  "express": "^4.21.2",
41
43
  "node-pty": "^1.1.0",
42
44
  "puppeteer": "^24.40.0",