@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.
- package/dist/process-manager.js +23 -7
- package/dist/pwa.js +9 -2
- package/dist/server.js +15 -4
- package/dist/structured-session-manager.js +21 -4
- package/dist/web-ui/content/scripts.js +45 -18
- package/dist/web-ui/content/styles.css +8 -1
- package/package.json +3 -1
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
|
});
|
|
@@ -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
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 = () => {
|
|
@@ -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 &&
|
|
9910
|
-
var
|
|
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
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
//
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|