@co0ontty/wand 0.2.1 → 0.4.0
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/README.md +25 -5
- package/dist/acp-protocol.d.ts +67 -0
- package/dist/acp-protocol.js +291 -0
- package/dist/avatar.d.ts +14 -0
- package/dist/avatar.js +110 -0
- package/dist/claude-pty-bridge.d.ts +137 -0
- package/dist/claude-pty-bridge.js +619 -0
- package/dist/claude-stream-adapter.d.ts +35 -0
- package/dist/claude-stream-adapter.js +153 -0
- package/dist/claude-structured-runner.d.ts +27 -0
- package/dist/claude-structured-runner.js +106 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +10 -2
- package/dist/config.js +8 -4
- package/dist/message-parser.js +16 -150
- package/dist/message-queue.d.ts +57 -0
- package/dist/message-queue.js +127 -0
- package/dist/middleware/path-safety.d.ts +6 -0
- package/dist/middleware/path-safety.js +19 -0
- package/dist/middleware/rate-limit.d.ts +8 -0
- package/dist/middleware/rate-limit.js +37 -0
- package/dist/process-manager.d.ts +82 -27
- package/dist/process-manager.js +1445 -822
- package/dist/pty-text-utils.d.ts +13 -0
- package/dist/pty-text-utils.js +84 -0
- package/dist/pwa.d.ts +5 -0
- package/dist/pwa.js +118 -0
- package/dist/server.js +511 -409
- package/dist/session-lifecycle.d.ts +81 -0
- package/dist/session-lifecycle.js +181 -0
- package/dist/session-logger.d.ts +13 -3
- package/dist/session-logger.js +56 -5
- package/dist/storage.d.ts +9 -0
- package/dist/storage.js +73 -7
- package/dist/types.d.ts +112 -6
- package/dist/web-ui/content/icon-192.png +0 -0
- package/dist/web-ui/content/icon-512.png +0 -0
- package/dist/web-ui/content/scripts.js +3770 -852
- package/dist/web-ui/content/styles.css +5505 -2779
- package/dist/web-ui/index.js +8 -5
- package/dist/web-ui/scripts.js +8 -1
- package/dist/ws-broadcast.d.ts +27 -0
- package/dist/ws-broadcast.js +160 -0
- package/package.json +2 -9
- package/dist/web-ui/utils.d.ts +0 -4
- package/dist/web-ui/utils.js +0 -12
- package/dist/web-ui.d.ts +0 -1
- package/dist/web-ui.js +0 -2
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
function normalizeToolInput(block) {
|
|
2
|
+
if (typeof block._partialJson === "string" && block._partialJson.length > 0) {
|
|
3
|
+
try {
|
|
4
|
+
const parsed = JSON.parse(block._partialJson);
|
|
5
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
6
|
+
return parsed;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
// Keep original input if partial JSON is incomplete.
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
if (block.input && typeof block.input === "object" && !Array.isArray(block.input)) {
|
|
14
|
+
return block.input;
|
|
15
|
+
}
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
function appendAssistantBlock(target, block) {
|
|
19
|
+
if (!block.type) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (block.type === "text") {
|
|
23
|
+
target.push({ type: "text", text: typeof block.text === "string" ? block.text : "" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (block.type === "thinking") {
|
|
27
|
+
target.push({ type: "thinking", thinking: typeof block.thinking === "string" ? block.thinking : "" });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (block.type === "tool_use") {
|
|
31
|
+
target.push({
|
|
32
|
+
type: "tool_use",
|
|
33
|
+
id: typeof block.id === "string" ? block.id : "",
|
|
34
|
+
name: typeof block.name === "string" ? block.name : "",
|
|
35
|
+
description: typeof block.description === "string" ? block.description : undefined,
|
|
36
|
+
input: normalizeToolInput({ input: block.input, _partialJson: block._partialJson }),
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (block.type === "tool_result") {
|
|
41
|
+
const content = typeof block.content === "string"
|
|
42
|
+
? block.content
|
|
43
|
+
: Array.isArray(block.content)
|
|
44
|
+
? block.content
|
|
45
|
+
: "";
|
|
46
|
+
target.push({
|
|
47
|
+
type: "tool_result",
|
|
48
|
+
tool_use_id: typeof block.tool_use_id === "string" ? block.tool_use_id : "",
|
|
49
|
+
content,
|
|
50
|
+
is_error: block.is_error === true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function buildUsage(event) {
|
|
55
|
+
if (!event.usage && event.total_cost_usd === undefined) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
inputTokens: typeof event.usage?.input_tokens === "number" ? event.usage.input_tokens : undefined,
|
|
60
|
+
outputTokens: typeof event.usage?.output_tokens === "number" ? event.usage.output_tokens : undefined,
|
|
61
|
+
cacheReadInputTokens: typeof event.usage?.cache_read_input_tokens === "number"
|
|
62
|
+
? event.usage.cache_read_input_tokens
|
|
63
|
+
: undefined,
|
|
64
|
+
cacheCreationInputTokens: typeof event.usage?.cache_creation_input_tokens === "number"
|
|
65
|
+
? event.usage.cache_creation_input_tokens
|
|
66
|
+
: undefined,
|
|
67
|
+
totalCostUsd: typeof event.total_cost_usd === "number" ? event.total_cost_usd : undefined,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export function updateClaudeStreamState(state, event) {
|
|
71
|
+
if (typeof event.session_id === "string" && event.session_id.length > 0) {
|
|
72
|
+
state.sessionId = event.session_id;
|
|
73
|
+
}
|
|
74
|
+
switch (event.type) {
|
|
75
|
+
case "assistant": {
|
|
76
|
+
const content = Array.isArray(event.message?.content) ? event.message?.content : [];
|
|
77
|
+
for (const block of content) {
|
|
78
|
+
if (block && typeof block === "object" && "type" in block) {
|
|
79
|
+
appendAssistantBlock(state.blocks, block);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "content_block_start": {
|
|
85
|
+
if (event.content_block && typeof event.content_block === "object") {
|
|
86
|
+
appendAssistantBlock(state.blocks, event.content_block);
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case "content_block_delta": {
|
|
91
|
+
const lastBlock = state.blocks[state.blocks.length - 1];
|
|
92
|
+
if (!lastBlock || !event.delta) {
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (lastBlock.type === "text" && event.delta.type === "text_delta" && typeof event.delta.text === "string") {
|
|
96
|
+
lastBlock.text += event.delta.text;
|
|
97
|
+
}
|
|
98
|
+
else if (lastBlock.type === "thinking" &&
|
|
99
|
+
event.delta.type === "thinking_delta" &&
|
|
100
|
+
typeof event.delta.thinking === "string") {
|
|
101
|
+
lastBlock.thinking += event.delta.thinking;
|
|
102
|
+
}
|
|
103
|
+
else if (lastBlock.type === "tool_use" &&
|
|
104
|
+
typeof event.delta.partial_json === "string") {
|
|
105
|
+
const nextPartial = (lastBlock._partialJson ?? "") + event.delta.partial_json;
|
|
106
|
+
lastBlock._partialJson = nextPartial;
|
|
107
|
+
lastBlock.input = normalizeToolInput({
|
|
108
|
+
input: lastBlock.input,
|
|
109
|
+
_partialJson: nextPartial,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "user": {
|
|
115
|
+
const content = Array.isArray(event.message?.content) ? event.message?.content : [];
|
|
116
|
+
for (const block of content) {
|
|
117
|
+
if (block &&
|
|
118
|
+
typeof block === "object" &&
|
|
119
|
+
"type" in block &&
|
|
120
|
+
block.type === "tool_result") {
|
|
121
|
+
appendAssistantBlock(state.blocks, block);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case "result": {
|
|
127
|
+
const usage = buildUsage(event);
|
|
128
|
+
if (usage) {
|
|
129
|
+
state.usage = usage;
|
|
130
|
+
}
|
|
131
|
+
if (typeof event.result === "string") {
|
|
132
|
+
const hasAssistantText = state.blocks.some((block) => block.type === "text");
|
|
133
|
+
if (!hasAssistantText && event.result.trim().length > 0) {
|
|
134
|
+
state.blocks.push({ type: "text", text: event.result });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else if (event.result && typeof event.result === "object") {
|
|
138
|
+
const resultContent = event.result.content;
|
|
139
|
+
if (Array.isArray(resultContent)) {
|
|
140
|
+
for (const block of resultContent) {
|
|
141
|
+
if (block && typeof block === "object" && "type" in block) {
|
|
142
|
+
appendAssistantBlock(state.blocks, block);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
return state;
|
|
153
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ChildProcess } from "node:child_process";
|
|
2
|
+
import type { ContentBlock, ConversationTurn } from "./types.js";
|
|
3
|
+
export interface ClaudeStructuredRunnerCallbacks {
|
|
4
|
+
onOutput: (text: string) => void;
|
|
5
|
+
onBlocks: (blocks: ContentBlock[], usage?: ConversationTurn["usage"]) => void;
|
|
6
|
+
onSessionId: (sessionId: string) => void;
|
|
7
|
+
onClose: (exitCode: number | null, state: ClaudeStructuredRunnerState) => void;
|
|
8
|
+
onError: (error: Error) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface ClaudeStructuredRunnerState {
|
|
11
|
+
stdoutBuffer: string;
|
|
12
|
+
assistantBlocks: ContentBlock[];
|
|
13
|
+
usage?: ConversationTurn["usage"];
|
|
14
|
+
sessionId: string | null;
|
|
15
|
+
}
|
|
16
|
+
export interface StartClaudeStructuredRunnerOptions {
|
|
17
|
+
command: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
callbacks: ClaudeStructuredRunnerCallbacks;
|
|
21
|
+
}
|
|
22
|
+
export interface ClaudeStructuredRunnerHandle {
|
|
23
|
+
child: ChildProcess;
|
|
24
|
+
getState: () => ClaudeStructuredRunnerState;
|
|
25
|
+
kill: () => void;
|
|
26
|
+
}
|
|
27
|
+
export declare function startClaudeStructuredRunner(options: StartClaudeStructuredRunnerOptions): ClaudeStructuredRunnerHandle;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { updateClaudeStreamState } from "./claude-stream-adapter.js";
|
|
3
|
+
function toContentBlocks(blocks) {
|
|
4
|
+
return blocks.map((block) => {
|
|
5
|
+
switch (block.type) {
|
|
6
|
+
case "text":
|
|
7
|
+
return { type: "text", text: typeof block.text === "string" ? block.text : "" };
|
|
8
|
+
case "thinking":
|
|
9
|
+
return { type: "thinking", thinking: typeof block.thinking === "string" ? block.thinking : "" };
|
|
10
|
+
case "tool_use":
|
|
11
|
+
return {
|
|
12
|
+
type: "tool_use",
|
|
13
|
+
id: typeof block.id === "string" ? block.id : "",
|
|
14
|
+
name: typeof block.name === "string" ? block.name : "",
|
|
15
|
+
description: typeof block.description === "string" ? block.description : undefined,
|
|
16
|
+
input: block.input && typeof block.input === "object" && !Array.isArray(block.input)
|
|
17
|
+
? block.input
|
|
18
|
+
: {},
|
|
19
|
+
};
|
|
20
|
+
case "tool_result":
|
|
21
|
+
return {
|
|
22
|
+
type: "tool_result",
|
|
23
|
+
tool_use_id: typeof block.tool_use_id === "string" ? block.tool_use_id : "",
|
|
24
|
+
content: typeof block.content === "string"
|
|
25
|
+
? block.content
|
|
26
|
+
: Array.isArray(block.content)
|
|
27
|
+
? block.content
|
|
28
|
+
: "",
|
|
29
|
+
is_error: block.is_error === true,
|
|
30
|
+
};
|
|
31
|
+
default:
|
|
32
|
+
return { type: "text", text: JSON.stringify(block) };
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function startClaudeStructuredRunner(options) {
|
|
37
|
+
const state = {
|
|
38
|
+
stdoutBuffer: "",
|
|
39
|
+
assistantBlocks: [],
|
|
40
|
+
usage: undefined,
|
|
41
|
+
sessionId: null,
|
|
42
|
+
};
|
|
43
|
+
const child = spawn(options.command, [], {
|
|
44
|
+
cwd: options.cwd,
|
|
45
|
+
env: options.env,
|
|
46
|
+
shell: true,
|
|
47
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
48
|
+
});
|
|
49
|
+
child.stdout?.on("data", (chunk) => {
|
|
50
|
+
state.stdoutBuffer += chunk.toString();
|
|
51
|
+
const lines = state.stdoutBuffer.split("\n");
|
|
52
|
+
state.stdoutBuffer = lines.pop() || "";
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const trimmed = line.trim();
|
|
55
|
+
if (!trimmed) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const event = JSON.parse(trimmed);
|
|
60
|
+
const nextState = updateClaudeStreamState({
|
|
61
|
+
blocks: state.assistantBlocks,
|
|
62
|
+
usage: state.usage,
|
|
63
|
+
sessionId: state.sessionId,
|
|
64
|
+
}, event);
|
|
65
|
+
state.assistantBlocks = nextState.blocks.map((block) => ({ ...block }));
|
|
66
|
+
state.usage = nextState.usage;
|
|
67
|
+
if (nextState.sessionId && nextState.sessionId !== state.sessionId) {
|
|
68
|
+
state.sessionId = nextState.sessionId;
|
|
69
|
+
options.callbacks.onSessionId(nextState.sessionId);
|
|
70
|
+
}
|
|
71
|
+
options.callbacks.onBlocks(state.assistantBlocks, state.usage);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
options.callbacks.onOutput(trimmed + "\n");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
child.stderr?.on("data", (chunk) => {
|
|
79
|
+
options.callbacks.onOutput(chunk.toString());
|
|
80
|
+
});
|
|
81
|
+
child.on("close", (code) => {
|
|
82
|
+
options.callbacks.onClose(code, {
|
|
83
|
+
stdoutBuffer: state.stdoutBuffer,
|
|
84
|
+
assistantBlocks: [...state.assistantBlocks],
|
|
85
|
+
usage: state.usage,
|
|
86
|
+
sessionId: state.sessionId,
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
child.on("error", (error) => {
|
|
90
|
+
options.callbacks.onError(error);
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
child,
|
|
94
|
+
getState: () => ({
|
|
95
|
+
stdoutBuffer: state.stdoutBuffer,
|
|
96
|
+
assistantBlocks: [...state.assistantBlocks],
|
|
97
|
+
usage: state.usage,
|
|
98
|
+
sessionId: state.sessionId,
|
|
99
|
+
}),
|
|
100
|
+
kill: () => {
|
|
101
|
+
if (!child.killed) {
|
|
102
|
+
child.kill();
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
export {};
|
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-warning=ExperimentalWarning
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { ensureConfig, hasConfigFile, isExecutionMode, resolveConfigPath, saveConfig } from "./config.js";
|
|
4
4
|
import { startServer } from "./server.js";
|
|
@@ -105,12 +105,20 @@ function setConfigValue(config, key, value) {
|
|
|
105
105
|
};
|
|
106
106
|
case "defaultMode":
|
|
107
107
|
if (!isExecutionMode(value)) {
|
|
108
|
-
throw new Error(
|
|
108
|
+
throw new Error(`defaultMode must be one of: assist, agent, agent-max, auto-edit, default, full-access, managed, native`);
|
|
109
109
|
}
|
|
110
110
|
return {
|
|
111
111
|
...config,
|
|
112
112
|
defaultMode: value
|
|
113
113
|
};
|
|
114
|
+
case "https":
|
|
115
|
+
if (value !== "true" && value !== "false") {
|
|
116
|
+
throw new Error("https must be 'true' or 'false'");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...config,
|
|
120
|
+
https: value === "true"
|
|
121
|
+
};
|
|
114
122
|
default:
|
|
115
123
|
throw new Error(`Unsupported config key: ${key}`);
|
|
116
124
|
}
|
package/dist/config.js
CHANGED
|
@@ -5,9 +5,9 @@ import process from "node:process";
|
|
|
5
5
|
const DEFAULT_CONFIG_DIR = ".wand";
|
|
6
6
|
const DEFAULT_CONFIG_FILE = "config.json";
|
|
7
7
|
export const defaultConfig = () => ({
|
|
8
|
-
host: "
|
|
8
|
+
host: "127.0.0.1",
|
|
9
9
|
port: 8443,
|
|
10
|
-
https:
|
|
10
|
+
https: false,
|
|
11
11
|
password: "change-me",
|
|
12
12
|
defaultMode: "default",
|
|
13
13
|
shell: process.env.SHELL || "/bin/bash",
|
|
@@ -60,7 +60,11 @@ export async function ensureConfig(configPath) {
|
|
|
60
60
|
try {
|
|
61
61
|
const raw = await readFile(configPath, "utf8");
|
|
62
62
|
const merged = mergeWithDefaults(JSON.parse(raw));
|
|
63
|
-
|
|
63
|
+
const normalized = `${JSON.stringify(merged, null, 2)}\n`;
|
|
64
|
+
// Only write if the file content actually changed
|
|
65
|
+
if (raw.trimEnd() !== normalized.trimEnd()) {
|
|
66
|
+
await writeFile(configPath, normalized, "utf8");
|
|
67
|
+
}
|
|
64
68
|
return merged;
|
|
65
69
|
}
|
|
66
70
|
catch {
|
|
@@ -102,7 +106,7 @@ function mergeWithDefaults(input) {
|
|
|
102
106
|
};
|
|
103
107
|
}
|
|
104
108
|
export function isExecutionMode(value) {
|
|
105
|
-
return value === "auto-edit" || value === "default" || value === "full-access" || value === "native" || value === "managed";
|
|
109
|
+
return value === "assist" || value === "agent" || value === "agent-max" || value === "auto-edit" || value === "default" || value === "full-access" || value === "native" || value === "managed";
|
|
106
110
|
}
|
|
107
111
|
function normalizePresetCommand(command) {
|
|
108
112
|
const trimmed = command.trim();
|
package/dist/message-parser.js
CHANGED
|
@@ -1,158 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
function stripAnsi(text) {
|
|
3
|
-
let stripped = "";
|
|
4
|
-
for (let i = 0; i < text.length; i++) {
|
|
5
|
-
const ch = text.charCodeAt(i);
|
|
6
|
-
if (ch === 27) {
|
|
7
|
-
i++;
|
|
8
|
-
if (i >= text.length)
|
|
9
|
-
break;
|
|
10
|
-
const next = text.charCodeAt(i);
|
|
11
|
-
if (next === 91) {
|
|
12
|
-
// CSI sequence: skip until final byte (64-126)
|
|
13
|
-
i++;
|
|
14
|
-
while (i < text.length) {
|
|
15
|
-
const c = text.charCodeAt(i);
|
|
16
|
-
if (c >= 64 && c <= 126)
|
|
17
|
-
break;
|
|
18
|
-
i++;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
else if (next === 93) {
|
|
22
|
-
// OSC sequence: skip until BEL (7) or ESC\ (27 92)
|
|
23
|
-
i++;
|
|
24
|
-
while (i < text.length) {
|
|
25
|
-
if (text.charCodeAt(i) === 7)
|
|
26
|
-
break;
|
|
27
|
-
if (text.charCodeAt(i) === 27 && i + 1 < text.length && text.charCodeAt(i + 1) === 92) {
|
|
28
|
-
i++;
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
i++;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Other escape sequences: skip the next character
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
// Skip control characters except \n, \r, \t
|
|
38
|
-
if (ch < 32 && ch !== 10 && ch !== 13 && ch !== 9)
|
|
39
|
-
continue;
|
|
40
|
-
stripped += text.charAt(i);
|
|
41
|
-
}
|
|
42
|
-
return stripped;
|
|
43
|
-
}
|
|
44
|
-
/** Check if a line is noise from Claude TUI */
|
|
45
|
-
function isNoiseLine(line) {
|
|
46
|
-
if (!line)
|
|
47
|
-
return true;
|
|
48
|
-
if (line.startsWith("────"))
|
|
49
|
-
return true;
|
|
50
|
-
if (line === "❯")
|
|
51
|
-
return true;
|
|
52
|
-
if (line.includes("esc to interrupt"))
|
|
53
|
-
return true;
|
|
54
|
-
if (line.includes("Claude Code v"))
|
|
55
|
-
return true;
|
|
56
|
-
if (/^Sonnet\b/.test(line))
|
|
57
|
-
return true;
|
|
58
|
-
if (line.startsWith("~/"))
|
|
59
|
-
return true;
|
|
60
|
-
if (line.includes("● high"))
|
|
61
|
-
return true;
|
|
62
|
-
if (line.includes("Failed to install Anthropic"))
|
|
63
|
-
return true;
|
|
64
|
-
if (line.includes("Claude Code has switched"))
|
|
65
|
-
return true;
|
|
66
|
-
if (line.includes("Fluttering"))
|
|
67
|
-
return true;
|
|
68
|
-
if (line.includes("? for shortcuts"))
|
|
69
|
-
return true;
|
|
70
|
-
if (line.startsWith("0;") || line.startsWith("9;"))
|
|
71
|
-
return true;
|
|
72
|
-
if (line.includes("Claude is waiting"))
|
|
73
|
-
return true;
|
|
74
|
-
if (/[✢✳✶✻✽]/.test(line))
|
|
75
|
-
return true;
|
|
76
|
-
if (/^[▐▝▘]/.test(line))
|
|
77
|
-
return true;
|
|
78
|
-
const singleCharNoise = ["lu", "ue", "tr", "ti", "g", "n", "i…", "…", "uts", "lt", "rg", "·"];
|
|
79
|
-
if (singleCharNoise.includes(line) && line.length < 4)
|
|
80
|
-
return true;
|
|
81
|
-
if (line.startsWith("✽F") || line.startsWith("✻F"))
|
|
82
|
-
return true;
|
|
83
|
-
if (line.includes("[wand]"))
|
|
84
|
-
return true;
|
|
85
|
-
if (line.includes("⏵"))
|
|
86
|
-
return true;
|
|
87
|
-
if (line.includes("acceptedit"))
|
|
88
|
-
return true;
|
|
89
|
-
if (line.includes("shift+tab"))
|
|
90
|
-
return true;
|
|
91
|
-
if (line.includes("tabtocycle"))
|
|
92
|
-
return true;
|
|
93
|
-
if (line.includes("ctrl+g"))
|
|
94
|
-
return true;
|
|
95
|
-
if (line.includes("/effort"))
|
|
96
|
-
return true;
|
|
97
|
-
if (line.includes("Haiku"))
|
|
98
|
-
return true;
|
|
99
|
-
if (line.includes("to cycle"))
|
|
100
|
-
return true;
|
|
101
|
-
if (/\bhigh\s*·/.test(line) || /\bmedium\s*·/.test(line) || /\blow\s*·/.test(line))
|
|
102
|
-
return true;
|
|
103
|
-
if (line.includes("npm WARN") || line.includes("npm notice"))
|
|
104
|
-
return true;
|
|
105
|
-
if (/^Using .* for .* session/.test(line))
|
|
106
|
-
return true;
|
|
107
|
-
if (line.includes("Permissions") && line.includes("mode"))
|
|
108
|
-
return true;
|
|
109
|
-
if (line.startsWith("Press ") && line.includes(" for"))
|
|
110
|
-
return true;
|
|
111
|
-
if (line.startsWith("type ") && line.includes(" to "))
|
|
112
|
-
return true;
|
|
113
|
-
if (line.length < 3 && !/^[a-zA-Z]{3}$/.test(line))
|
|
114
|
-
return true;
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
/** Filter assistant content line */
|
|
118
|
-
function isAssistantContent(line) {
|
|
119
|
-
if (line.includes("⏺"))
|
|
120
|
-
return true;
|
|
121
|
-
if (line.length < 8)
|
|
122
|
-
return false;
|
|
123
|
-
if (/[✢✳✶✻✽]/.test(line))
|
|
124
|
-
return false;
|
|
125
|
-
if (/^[▐▝▘]/.test(line))
|
|
126
|
-
return false;
|
|
127
|
-
if (line.startsWith("❯"))
|
|
128
|
-
return false;
|
|
129
|
-
if (line.includes("esctointerrupt"))
|
|
130
|
-
return false;
|
|
131
|
-
if (line.startsWith("?for") || line.startsWith("? for"))
|
|
132
|
-
return false;
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
1
|
+
import { stripAnsi, isNoiseLine } from "./pty-text-utils.js";
|
|
135
2
|
export function parseMessages(output) {
|
|
136
3
|
const messages = [];
|
|
137
4
|
if (!output)
|
|
138
5
|
return messages;
|
|
139
|
-
// Strip ANSI and normalize
|
|
140
6
|
const stripped = stripAnsi(output).replace(/\r/g, "\n");
|
|
141
|
-
const lines = stripped.split("\n")
|
|
142
|
-
|
|
143
|
-
const cleaned = lines.filter((line) => !isNoiseLine(line));
|
|
7
|
+
const lines = stripped.split("\n");
|
|
8
|
+
const cleaned = lines.filter((line) => !isNoiseLine(line.trim()));
|
|
144
9
|
if (!cleaned.length)
|
|
145
10
|
return messages;
|
|
146
11
|
const turns = [];
|
|
147
12
|
let currentUserText = null;
|
|
148
13
|
let currentAssistantLines = [];
|
|
149
|
-
for (const
|
|
14
|
+
for (const rawLine of cleaned) {
|
|
15
|
+
const line = rawLine.trim();
|
|
150
16
|
if (line.startsWith("❯")) {
|
|
151
17
|
const afterPrompt = line.replace(/^❯\s*/, "").trim();
|
|
152
|
-
// Skip prompt suggestions
|
|
153
18
|
if (afterPrompt.startsWith("Try"))
|
|
154
19
|
continue;
|
|
155
|
-
// Finalize previous turn
|
|
156
20
|
if (currentUserText !== null && currentAssistantLines.length > 0) {
|
|
157
21
|
turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
|
|
158
22
|
currentAssistantLines = [];
|
|
@@ -161,7 +25,6 @@ export function parseMessages(output) {
|
|
|
161
25
|
currentUserText = afterPrompt;
|
|
162
26
|
}
|
|
163
27
|
else {
|
|
164
|
-
// Standalone ❯ — finalize and reset
|
|
165
28
|
if (currentUserText !== null && currentAssistantLines.length > 0) {
|
|
166
29
|
turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
|
|
167
30
|
currentAssistantLines = [];
|
|
@@ -169,21 +32,24 @@ export function parseMessages(output) {
|
|
|
169
32
|
currentUserText = null;
|
|
170
33
|
}
|
|
171
34
|
}
|
|
172
|
-
else if (currentUserText !== null
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if (cleanLine)
|
|
176
|
-
currentAssistantLines.push(cleanLine);
|
|
35
|
+
else if (currentUserText !== null) {
|
|
36
|
+
const contentLine = rawLine.startsWith("⏺") ? rawLine.slice(1) : rawLine;
|
|
37
|
+
currentAssistantLines.push(contentLine);
|
|
177
38
|
}
|
|
178
39
|
}
|
|
179
|
-
// Finalize last turn
|
|
180
40
|
if (currentUserText !== null && currentAssistantLines.length > 0) {
|
|
181
41
|
turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
|
|
182
42
|
}
|
|
183
|
-
|
|
43
|
+
else if (currentUserText !== null) {
|
|
44
|
+
// User input exists but no assistant response yet — still record the turn
|
|
45
|
+
turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
|
|
46
|
+
}
|
|
184
47
|
for (const turn of turns) {
|
|
185
48
|
messages.push({ role: "user", content: turn.user });
|
|
186
|
-
|
|
49
|
+
const content = turn.assistantLines.join("\n").replace(/[ \t]+\n/g, "\n").replace(/[\n\s]+$/, "");
|
|
50
|
+
if (content) {
|
|
51
|
+
messages.push({ role: "assistant", content });
|
|
52
|
+
}
|
|
187
53
|
}
|
|
188
54
|
return messages;
|
|
189
55
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message Queue for managing user inputs
|
|
3
|
+
* Inspired by Happy's MessageQueue2 implementation
|
|
4
|
+
*/
|
|
5
|
+
import type { QueuedMessage, AutonomyPolicy, ApprovalPolicy, EscalationScope } from "./types.js";
|
|
6
|
+
export interface MessageQueueOptions {
|
|
7
|
+
autonomyPolicy?: AutonomyPolicy;
|
|
8
|
+
approvalPolicy?: ApprovalPolicy;
|
|
9
|
+
allowedScopes?: EscalationScope[];
|
|
10
|
+
}
|
|
11
|
+
export declare class MessageQueue {
|
|
12
|
+
private queue;
|
|
13
|
+
private processing;
|
|
14
|
+
private onMessageCallback;
|
|
15
|
+
private lastMessageId;
|
|
16
|
+
/**
|
|
17
|
+
* Add a message to the queue
|
|
18
|
+
*/
|
|
19
|
+
enqueue(content: string, options?: MessageQueueOptions): QueuedMessage;
|
|
20
|
+
/**
|
|
21
|
+
* Add a high-priority message (like /compact, /clear)
|
|
22
|
+
*/
|
|
23
|
+
enqueuePriority(content: string, options?: MessageQueueOptions): QueuedMessage;
|
|
24
|
+
/**
|
|
25
|
+
* Clear the queue and add a new message
|
|
26
|
+
* Used for /compact and /clear commands
|
|
27
|
+
*/
|
|
28
|
+
clearAndEnqueue(content: string, options?: MessageQueueOptions): QueuedMessage;
|
|
29
|
+
/**
|
|
30
|
+
* Set the message handler
|
|
31
|
+
*/
|
|
32
|
+
onMessage(handler: (message: QueuedMessage) => Promise<void>): void;
|
|
33
|
+
/**
|
|
34
|
+
* Process the next message in the queue
|
|
35
|
+
*/
|
|
36
|
+
private processNext;
|
|
37
|
+
/**
|
|
38
|
+
* Get the current queue length
|
|
39
|
+
*/
|
|
40
|
+
get length(): number;
|
|
41
|
+
/**
|
|
42
|
+
* Check if the queue is empty
|
|
43
|
+
*/
|
|
44
|
+
get isEmpty(): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a message is being processed
|
|
47
|
+
*/
|
|
48
|
+
get isProcessing(): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Clear all pending messages
|
|
51
|
+
*/
|
|
52
|
+
clear(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Get all pending messages (for debugging)
|
|
55
|
+
*/
|
|
56
|
+
getPending(): QueuedMessage[];
|
|
57
|
+
}
|