@co0ontty/wand 4.53.0 → 4.54.0-beta.gcb99d89
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/git-quick-commit.js +1 -1
- package/dist/process-manager.js +6 -3
- package/dist/server-session-routes.js +1 -1
- package/dist/server-workspace-routes.d.ts +1 -1
- package/dist/server-workspace-routes.js +91 -37
- package/dist/session-topic.d.ts +14 -2
- package/dist/session-topic.js +107 -2
- package/dist/storage.d.ts +7 -1
- package/dist/storage.js +57 -11
- package/dist/structured-grok-adapter.js +116 -0
- package/dist/structured-opencode-adapter.js +1 -1
- package/dist/structured-pi-adapter.js +84 -16
- package/dist/structured-qoder-adapter.js +4 -0
- package/dist/terminal-daemon-client.d.ts +1 -0
- package/dist/terminal-daemon-client.js +33 -9
- package/dist/types.d.ts +6 -0
- package/dist/types.js +2 -1
- package/dist/web-ui/content/scripts.js +178 -74
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/workspace-binding.js +2 -1
- package/package.json +1 -1
|
@@ -6,6 +6,113 @@ function asRecord(value) {
|
|
|
6
6
|
? value
|
|
7
7
|
: null;
|
|
8
8
|
}
|
|
9
|
+
function grokText(value) {
|
|
10
|
+
if (typeof value === "string")
|
|
11
|
+
return value;
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return value.map(grokText).filter(Boolean).join("\n");
|
|
14
|
+
const record = asRecord(value);
|
|
15
|
+
if (!record)
|
|
16
|
+
return "";
|
|
17
|
+
if (typeof record.text === "string" && record.text)
|
|
18
|
+
return record.text;
|
|
19
|
+
if (record.content !== undefined)
|
|
20
|
+
return grokText(record.content);
|
|
21
|
+
if (record.stdout !== undefined || record.stderr !== undefined) {
|
|
22
|
+
return [grokText(record.stdout), grokText(record.stderr)].filter(Boolean).join("\n");
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
return JSON.stringify(record);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function grokToolName(event) {
|
|
32
|
+
const raw = [
|
|
33
|
+
typeof event.toolName === "string" ? event.toolName : "",
|
|
34
|
+
typeof event.kind === "string" ? event.kind : "",
|
|
35
|
+
typeof event.title === "string" ? event.title : "",
|
|
36
|
+
].find((value) => value.trim());
|
|
37
|
+
if (!raw)
|
|
38
|
+
return null;
|
|
39
|
+
const mapped = {
|
|
40
|
+
run_terminal_command: "Bash",
|
|
41
|
+
bash: "Bash",
|
|
42
|
+
shell: "Bash",
|
|
43
|
+
read_file: "Read",
|
|
44
|
+
read: "Read",
|
|
45
|
+
search_replace: "Edit",
|
|
46
|
+
edit: "Edit",
|
|
47
|
+
write: "Write",
|
|
48
|
+
list_dir: "Glob",
|
|
49
|
+
glob: "Glob",
|
|
50
|
+
grep: "Grep",
|
|
51
|
+
web_search: "WebSearch",
|
|
52
|
+
webfetch: "WebFetch",
|
|
53
|
+
todo_write: "TodoWrite",
|
|
54
|
+
};
|
|
55
|
+
return mapped[raw.toLowerCase()] ?? raw;
|
|
56
|
+
}
|
|
57
|
+
function grokToolInput(event) {
|
|
58
|
+
return asRecord(event.rawInput) ?? asRecord(event.input) ?? {};
|
|
59
|
+
}
|
|
60
|
+
function grokToolOutput(event) {
|
|
61
|
+
const fromRaw = grokText(event.rawOutput);
|
|
62
|
+
if (fromRaw)
|
|
63
|
+
return fromRaw;
|
|
64
|
+
return grokText(event.content);
|
|
65
|
+
}
|
|
66
|
+
function upsertGrokTool(state, event, completed) {
|
|
67
|
+
const id = typeof event.toolCallId === "string" && event.toolCallId
|
|
68
|
+
? event.toolCallId
|
|
69
|
+
: typeof event.id === "string" && event.id ? event.id : "tool";
|
|
70
|
+
const input = grokToolInput(event);
|
|
71
|
+
const name = grokToolName(event);
|
|
72
|
+
const title = typeof event.title === "string" ? event.title : undefined;
|
|
73
|
+
const existingUse = state.blocks.find((block) => block.type === "tool_use" && block.id === id);
|
|
74
|
+
if (existingUse?.type === "tool_use") {
|
|
75
|
+
if (name)
|
|
76
|
+
existingUse.name = name;
|
|
77
|
+
existingUse.description = title ?? existingUse.description;
|
|
78
|
+
existingUse.input = Object.keys(input).length ? input : existingUse.input;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
state.blocks.push({ type: "tool_use", id, name: name ?? "tool", description: title, input });
|
|
82
|
+
}
|
|
83
|
+
if (!completed)
|
|
84
|
+
return;
|
|
85
|
+
const content = grokToolOutput(event);
|
|
86
|
+
const failed = event.status === "failed" || event.status === "error";
|
|
87
|
+
const existingResult = state.blocks.find((block) => block.type === "tool_result" && block.tool_use_id === id);
|
|
88
|
+
if (existingResult?.type === "tool_result") {
|
|
89
|
+
existingResult.content = content || existingResult.content;
|
|
90
|
+
existingResult.is_error = failed;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
state.blocks.push({ type: "tool_result", tool_use_id: id, content, is_error: failed });
|
|
94
|
+
}
|
|
95
|
+
/** Headless `--output-format streaming-json` is type-tagged; ACP stdio uses session/update. */
|
|
96
|
+
function normalizeGrokEvent(event) {
|
|
97
|
+
if (typeof event.type === "string" && event.type)
|
|
98
|
+
return event;
|
|
99
|
+
if (event.method !== "session/update")
|
|
100
|
+
return event;
|
|
101
|
+
const params = asRecord(event.params);
|
|
102
|
+
const update = asRecord(params?.update) ?? asRecord(event.update);
|
|
103
|
+
if (!update)
|
|
104
|
+
return event;
|
|
105
|
+
const kind = typeof update.sessionUpdate === "string" ? update.sessionUpdate : "";
|
|
106
|
+
const content = asRecord(update.content);
|
|
107
|
+
const text = typeof content?.text === "string" ? content.text : grokText(update.content);
|
|
108
|
+
if (kind === "agent_message_chunk")
|
|
109
|
+
return { type: "text", data: text };
|
|
110
|
+
if (kind === "agent_thought_chunk" || kind === "thought")
|
|
111
|
+
return { type: "thought", data: text };
|
|
112
|
+
if (kind === "tool_call" || kind === "tool_call_update")
|
|
113
|
+
return { type: kind, ...update };
|
|
114
|
+
return event;
|
|
115
|
+
}
|
|
9
116
|
export function buildGrokArgs(session, prompt) {
|
|
10
117
|
const args = ["--no-auto-update", "-p", prompt, "--output-format", "streaming-json"];
|
|
11
118
|
const model = session.selectedModel?.trim();
|
|
@@ -26,6 +133,7 @@ export function buildGrokArgs(session, prompt) {
|
|
|
26
133
|
}
|
|
27
134
|
/** Apply one official Grok Build `streaming-json` event. */
|
|
28
135
|
export function applyGrokEvent(state, event) {
|
|
136
|
+
event = normalizeGrokEvent(event);
|
|
29
137
|
const type = typeof event.type === "string" ? event.type : "";
|
|
30
138
|
if (type === "text" && typeof event.data === "string" && event.data) {
|
|
31
139
|
const previous = state.blocks.at(-1);
|
|
@@ -44,6 +152,14 @@ export function applyGrokEvent(state, event) {
|
|
|
44
152
|
state.blocks.push({ type: "thinking", thinking: event.data });
|
|
45
153
|
return null;
|
|
46
154
|
}
|
|
155
|
+
if (type === "tool_call") {
|
|
156
|
+
upsertGrokTool(state, event, event.status === "completed" || event.status === "failed" || event.status === "error");
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
if (type === "tool_call_update") {
|
|
160
|
+
upsertGrokTool(state, event, event.status !== "in_progress");
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
47
163
|
if (type === "end") {
|
|
48
164
|
if (typeof event.sessionId === "string" && event.sessionId)
|
|
49
165
|
state.sessionId = event.sessionId;
|
|
@@ -34,7 +34,7 @@ export function buildOpenCodeArgs(session) {
|
|
|
34
34
|
|| session.mode === "full-access"
|
|
35
35
|
|| session.mode === "managed"
|
|
36
36
|
|| session.mode === "auto-edit") {
|
|
37
|
-
args.push("--
|
|
37
|
+
args.push("--auto");
|
|
38
38
|
}
|
|
39
39
|
if (session.claudeSessionId)
|
|
40
40
|
args.push("--session", session.claudeSessionId);
|
|
@@ -33,6 +33,62 @@ export function piToolName(name) {
|
|
|
33
33
|
const mapped = { bash: "Bash", read: "Read", edit: "Edit", write: "Write", grep: "Grep", find: "Glob", ls: "Glob" };
|
|
34
34
|
return mapped[name.toLowerCase()] ?? `Pi/${name}`;
|
|
35
35
|
}
|
|
36
|
+
function applyPiAssistantMessage(state, message) {
|
|
37
|
+
if (typeof message.model === "string")
|
|
38
|
+
state.model = message.model;
|
|
39
|
+
const usage = record(message.usage);
|
|
40
|
+
const cost = record(usage?.cost);
|
|
41
|
+
if (usage) {
|
|
42
|
+
state.usage = {
|
|
43
|
+
inputTokens: typeof usage.input === "number" ? usage.input : 0,
|
|
44
|
+
outputTokens: typeof usage.output === "number" ? usage.output : 0,
|
|
45
|
+
cacheReadInputTokens: typeof usage.cacheRead === "number" ? usage.cacheRead : 0,
|
|
46
|
+
cacheCreationInputTokens: typeof usage.cacheWrite === "number" ? usage.cacheWrite : 0,
|
|
47
|
+
totalCostUsd: typeof cost?.total === "number" ? cost.total : 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const texts = [];
|
|
51
|
+
const thinkings = [];
|
|
52
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
53
|
+
for (const part of parts) {
|
|
54
|
+
const item = record(part);
|
|
55
|
+
if (!item)
|
|
56
|
+
continue;
|
|
57
|
+
if (item.type === "text") {
|
|
58
|
+
const text = typeof item.text === "string" ? item.text : textContent(item);
|
|
59
|
+
if (text)
|
|
60
|
+
texts.push(text);
|
|
61
|
+
}
|
|
62
|
+
else if (item.type === "thinking") {
|
|
63
|
+
const thinking = typeof item.thinking === "string"
|
|
64
|
+
? item.thinking
|
|
65
|
+
: typeof item.text === "string" ? item.text : "";
|
|
66
|
+
if (thinking)
|
|
67
|
+
thinkings.push(thinking);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const thinking = thinkings.join("");
|
|
71
|
+
if (thinking && !state.blocks.some((block) => block.type === "thinking")) {
|
|
72
|
+
state.blocks.push({ type: "thinking", thinking });
|
|
73
|
+
}
|
|
74
|
+
const text = texts.join("");
|
|
75
|
+
if (text) {
|
|
76
|
+
const lastText = [...state.blocks].reverse().find((block) => block.type === "text");
|
|
77
|
+
if (lastText?.type === "text") {
|
|
78
|
+
if (text.length >= lastText.text.length)
|
|
79
|
+
lastText.text = text;
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
state.blocks.push({ type: "text", text });
|
|
83
|
+
}
|
|
84
|
+
if (text.length >= state.result.length)
|
|
85
|
+
state.result = text;
|
|
86
|
+
}
|
|
87
|
+
if (message.stopReason === "error") {
|
|
88
|
+
return typeof message.errorMessage === "string" ? message.errorMessage : "Pi CLI execution failed";
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
36
92
|
export function applyPiEvent(state, event) {
|
|
37
93
|
if (event.type === "session" && typeof event.id === "string")
|
|
38
94
|
state.sessionId = event.id;
|
|
@@ -54,6 +110,22 @@ export function applyPiEvent(state, event) {
|
|
|
54
110
|
else
|
|
55
111
|
state.blocks.push({ type: "thinking", thinking: delta });
|
|
56
112
|
}
|
|
113
|
+
else if (update?.type === "text_end" && typeof update.content === "string" && update.content) {
|
|
114
|
+
const last = state.blocks.at(-1);
|
|
115
|
+
if (last?.type === "text")
|
|
116
|
+
last.text = update.content;
|
|
117
|
+
else
|
|
118
|
+
state.blocks.push({ type: "text", text: update.content });
|
|
119
|
+
if (update.content.length >= state.result.length)
|
|
120
|
+
state.result = update.content;
|
|
121
|
+
}
|
|
122
|
+
else if (update?.type === "thinking_end" && typeof update.content === "string" && update.content) {
|
|
123
|
+
const last = state.blocks.at(-1);
|
|
124
|
+
if (last?.type === "thinking")
|
|
125
|
+
last.thinking = update.content;
|
|
126
|
+
else
|
|
127
|
+
state.blocks.push({ type: "thinking", thinking: update.content });
|
|
128
|
+
}
|
|
57
129
|
}
|
|
58
130
|
if (event.type === "tool_execution_start") {
|
|
59
131
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : crypto.randomUUID();
|
|
@@ -64,23 +136,19 @@ export function applyPiEvent(state, event) {
|
|
|
64
136
|
const id = typeof event.toolCallId === "string" ? event.toolCallId : "unknown";
|
|
65
137
|
state.blocks.push({ type: "tool_result", tool_use_id: id, content: textContent(event.result), is_error: event.isError === true });
|
|
66
138
|
}
|
|
67
|
-
if (event.type === "message_end") {
|
|
139
|
+
if (event.type === "message_end" || event.type === "turn_end") {
|
|
68
140
|
const message = record(event.message);
|
|
69
|
-
if (message?.role === "assistant")
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
totalCostUsd: typeof cost?.total === "number" ? cost.total : 0,
|
|
81
|
-
};
|
|
82
|
-
if (message.stopReason === "error")
|
|
83
|
-
return typeof message.errorMessage === "string" ? message.errorMessage : "Pi CLI execution failed";
|
|
141
|
+
if (message?.role === "assistant")
|
|
142
|
+
return applyPiAssistantMessage(state, message);
|
|
143
|
+
}
|
|
144
|
+
if (event.type === "agent_end" && Array.isArray(event.messages)) {
|
|
145
|
+
for (const raw of event.messages) {
|
|
146
|
+
const message = record(raw);
|
|
147
|
+
if (message?.role === "assistant") {
|
|
148
|
+
const error = applyPiAssistantMessage(state, message);
|
|
149
|
+
if (error)
|
|
150
|
+
return error;
|
|
151
|
+
}
|
|
84
152
|
}
|
|
85
153
|
}
|
|
86
154
|
return null;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { ClaudeCliProtocolReducer } from "./structured-claude-protocol.js";
|
|
3
3
|
import { startStructuredCli } from "./structured-exec-pump.js";
|
|
4
|
+
import { thinkingEffortToQoderEffort } from "./structured-provider-common.js";
|
|
4
5
|
export function buildQoderArgs(session, prompt) {
|
|
5
6
|
const args = ["-p", prompt, "--output-format", "stream-json"];
|
|
6
7
|
const model = session.selectedModel?.trim();
|
|
7
8
|
if (model && model !== "default")
|
|
8
9
|
args.push("--model", model);
|
|
10
|
+
const effort = thinkingEffortToQoderEffort(session.thinkingEffort);
|
|
11
|
+
if (effort)
|
|
12
|
+
args.push("--reasoning-effort", effort);
|
|
9
13
|
if (session.autoApprovePermissions === true
|
|
10
14
|
|| session.mode === "full-access"
|
|
11
15
|
|| session.mode === "managed") {
|
|
@@ -32,6 +32,7 @@ export declare class TerminalDaemonClient implements TerminalHost, StructuredExe
|
|
|
32
32
|
/** Refresh the structured inventory and catch live handles up after a reconnect. */
|
|
33
33
|
private refreshStructuredAfterReconnect;
|
|
34
34
|
private handleFromState;
|
|
35
|
+
private deliverStructuredEvent;
|
|
35
36
|
disconnect(): void;
|
|
36
37
|
/**
|
|
37
38
|
* Socket teardown path. Without a reconnect, a daemon restart would leave
|
|
@@ -377,21 +377,45 @@ export class TerminalDaemonClient {
|
|
|
377
377
|
}
|
|
378
378
|
handleFromState(state, reuse) {
|
|
379
379
|
this.structuredInventory.set(state.runId, state);
|
|
380
|
-
if (state.status !== "running") {
|
|
381
|
-
this.structuredHandles.delete(state.runId);
|
|
382
|
-
return reuse ?? new RemoteStructuredProcess(state.runId, state.incarnationId, state.pid, this);
|
|
383
|
-
}
|
|
384
380
|
let process = this.structuredHandles.get(state.runId) ?? null;
|
|
385
381
|
if (!process || process.incarnationId !== state.incarnationId) {
|
|
386
|
-
process =
|
|
382
|
+
process = reuse && reuse.incarnationId === state.incarnationId
|
|
383
|
+
? reuse
|
|
384
|
+
: new RemoteStructuredProcess(state.runId, state.incarnationId, state.pid, this);
|
|
385
|
+
}
|
|
386
|
+
if (state.status === "running")
|
|
387
387
|
this.structuredHandles.set(state.runId, process);
|
|
388
|
-
|
|
389
|
-
this.
|
|
390
|
-
|
|
391
|
-
|
|
388
|
+
else
|
|
389
|
+
this.structuredHandles.delete(state.runId);
|
|
390
|
+
// Events can arrive (and the child can even exit) before this RPC returns.
|
|
391
|
+
// Always drain the backlog onto THIS handle. If the run already finished,
|
|
392
|
+
// also replay daemon logs and synthesize exit so a fast Codex/Pi turn is
|
|
393
|
+
// not lost just because listeners were not attached yet.
|
|
394
|
+
const pending = this.pendingStructuredEvents.get(state.runId) ?? [];
|
|
395
|
+
this.pendingStructuredEvents.delete(state.runId);
|
|
396
|
+
for (const event of pending)
|
|
397
|
+
this.deliverStructuredEvent(process, event);
|
|
398
|
+
if (state.status !== "running") {
|
|
399
|
+
process.replayFromLogs(state);
|
|
400
|
+
process.acceptExit({ exitCode: state.exitCode, signal: state.signal });
|
|
392
401
|
}
|
|
393
402
|
return process;
|
|
394
403
|
}
|
|
404
|
+
deliverStructuredEvent(handle, event) {
|
|
405
|
+
if (event.incarnationId !== handle.incarnationId)
|
|
406
|
+
return;
|
|
407
|
+
if (event.event === "sdata" && typeof event.data === "string" && typeof event.seq === "number") {
|
|
408
|
+
handle.acceptStream({
|
|
409
|
+
stream: event.stream === "stderr" ? "stderr" : "stdout",
|
|
410
|
+
data: event.data,
|
|
411
|
+
seq: event.seq,
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (event.event === "sexit") {
|
|
416
|
+
handle.acceptExit({ exitCode: event.exitCode ?? null, signal: event.signal ?? null });
|
|
417
|
+
}
|
|
418
|
+
}
|
|
395
419
|
disconnect() {
|
|
396
420
|
this.disposed = true;
|
|
397
421
|
if (this.reconnectTimer) {
|
package/dist/types.d.ts
CHANGED
|
@@ -648,11 +648,15 @@ export interface TaskWindowLayout {
|
|
|
648
648
|
windows: WorkWindowLayout[];
|
|
649
649
|
activeWindowId: string | null;
|
|
650
650
|
}
|
|
651
|
+
export type WorkspaceKind = "project" | "global";
|
|
652
|
+
/** Hidden workspace that owns standalone tasks without a user project. */
|
|
653
|
+
export declare const GLOBAL_WORKSPACE_ID = "wand-global";
|
|
651
654
|
/** 项目 / 工作空间。锚定一个目录(如 wand 仓库),内含多个并行「任务」。 */
|
|
652
655
|
export interface Workspace {
|
|
653
656
|
id: string;
|
|
654
657
|
name: string;
|
|
655
658
|
cwd: string;
|
|
659
|
+
kind?: WorkspaceKind;
|
|
656
660
|
defaultProvider?: WorkspaceDefaultProvider;
|
|
657
661
|
/**
|
|
658
662
|
* 工作空间级的布局占位(保留字段)。标签 / 分屏布局实际挂在 Task 上
|
|
@@ -674,6 +678,8 @@ export interface WorkspaceTask {
|
|
|
674
678
|
workspaceId: string;
|
|
675
679
|
name: string;
|
|
676
680
|
worktree: WorkspaceTaskWorktree | null;
|
|
681
|
+
/** Optional directory override; empty/undefined falls back to the workspace cwd. */
|
|
682
|
+
cwd?: string;
|
|
677
683
|
/** 该任务的工作窗口 Tabs;每个窗口内部可含一棵分屏树。 */
|
|
678
684
|
layout: TaskWindowLayout | null;
|
|
679
685
|
status: WorkspaceTaskStatus;
|
package/dist/types.js
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
/** Hidden workspace that owns standalone tasks without a user project. */
|
|
2
|
+
export const GLOBAL_WORKSPACE_ID = "wand-global";
|