@co0ontty/wand 0.2.0 → 0.3.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.
@@ -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/config.js CHANGED
@@ -5,7 +5,7 @@ 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: "0.0.0.0",
8
+ host: "127.0.0.1",
9
9
  port: 8443,
10
10
  https: true,
11
11
  password: "change-me",
@@ -102,7 +102,7 @@ function mergeWithDefaults(input) {
102
102
  };
103
103
  }
104
104
  export function isExecutionMode(value) {
105
- return value === "auto-edit" || value === "default" || value === "full-access" || value === "native" || value === "managed";
105
+ return value === "assist" || value === "agent" || value === "agent-max" || value === "auto-edit" || value === "default" || value === "full-access" || value === "native" || value === "managed";
106
106
  }
107
107
  function normalizePresetCommand(command) {
108
108
  const trimmed = command.trim();
@@ -31,7 +31,6 @@ function stripAnsi(text) {
31
31
  i++;
32
32
  }
33
33
  }
34
- // Other escape sequences: skip the next character
35
34
  continue;
36
35
  }
37
36
  // Skip control characters except \n, \r, \t
@@ -41,10 +40,10 @@ function stripAnsi(text) {
41
40
  }
42
41
  return stripped;
43
42
  }
44
- /** Check if a line is noise from Claude TUI */
43
+ /** Lines considered as UI noise (pass in trimmed) */
45
44
  function isNoiseLine(line) {
46
- if (!line)
47
- return true;
45
+ if (line.length === 0)
46
+ return false;
48
47
  if (line.startsWith("────"))
49
48
  return true;
50
49
  if (line === "❯")
@@ -55,104 +54,55 @@ function isNoiseLine(line) {
55
54
  return true;
56
55
  if (/^Sonnet\b/.test(line))
57
56
  return true;
58
- if (line.startsWith("~/"))
59
- return true;
60
- if (line.includes("● high"))
61
- return true;
62
57
  if (line.includes("Failed to install Anthropic"))
63
58
  return true;
64
59
  if (line.includes("Claude Code has switched"))
65
60
  return true;
66
- if (line.includes("Fluttering"))
67
- return true;
68
61
  if (line.includes("? for shortcuts"))
69
62
  return true;
70
- if (line.startsWith("0;") || line.startsWith("9;"))
71
- return true;
72
63
  if (line.includes("Claude is waiting"))
73
64
  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
65
  if (line.includes("[wand]"))
84
66
  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"))
67
+ if (line.startsWith("0;") || line.startsWith("9;"))
92
68
  return true;
93
69
  if (line.includes("ctrl+g"))
94
70
  return true;
95
71
  if (line.includes("/effort"))
96
72
  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
73
  if (/^Using .* for .* session/.test(line))
106
74
  return true;
107
- if (line.includes("Permissions") && line.includes("mode"))
108
- return true;
109
75
  if (line.startsWith("Press ") && line.includes(" for"))
110
76
  return true;
111
77
  if (line.startsWith("type ") && line.includes(" to "))
112
78
  return true;
113
- if (line.length < 3 && !/^[a-zA-Z]{3}$/.test(line))
114
- return true;
115
79
  return false;
116
80
  }
117
- /** Filter assistant content line */
118
81
  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
82
  if (line.startsWith("❯"))
128
83
  return false;
129
84
  if (line.includes("esctointerrupt"))
130
85
  return false;
131
- if (line.startsWith("?for") || line.startsWith("? for"))
132
- return false;
133
86
  return true;
134
87
  }
135
88
  export function parseMessages(output) {
136
89
  const messages = [];
137
90
  if (!output)
138
91
  return messages;
139
- // Strip ANSI and normalize
140
92
  const stripped = stripAnsi(output).replace(/\r/g, "\n");
141
- const lines = stripped.split("\n").map((l) => l.trim()).filter(Boolean);
142
- // Filter noise
143
- const cleaned = lines.filter((line) => !isNoiseLine(line));
93
+ const lines = stripped.split("\n");
94
+ const cleaned = lines.filter((line) => !isNoiseLine(line.trim()));
144
95
  if (!cleaned.length)
145
96
  return messages;
146
97
  const turns = [];
147
98
  let currentUserText = null;
148
99
  let currentAssistantLines = [];
149
- for (const line of cleaned) {
100
+ for (const rawLine of cleaned) {
101
+ const line = rawLine.trim();
150
102
  if (line.startsWith("❯")) {
151
103
  const afterPrompt = line.replace(/^❯\s*/, "").trim();
152
- // Skip prompt suggestions
153
104
  if (afterPrompt.startsWith("Try"))
154
105
  continue;
155
- // Finalize previous turn
156
106
  if (currentUserText !== null && currentAssistantLines.length > 0) {
157
107
  turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
158
108
  currentAssistantLines = [];
@@ -161,7 +111,6 @@ export function parseMessages(output) {
161
111
  currentUserText = afterPrompt;
162
112
  }
163
113
  else {
164
- // Standalone ❯ — finalize and reset
165
114
  if (currentUserText !== null && currentAssistantLines.length > 0) {
166
115
  turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
167
116
  currentAssistantLines = [];
@@ -170,20 +119,17 @@ export function parseMessages(output) {
170
119
  }
171
120
  }
172
121
  else if (currentUserText !== null && isAssistantContent(line)) {
173
- // Cleanprefix
174
- const cleanLine = line.startsWith("⏺") ? line.slice(1).trim() : line;
175
- if (cleanLine)
176
- currentAssistantLines.push(cleanLine);
122
+ const contentLine = rawLine.startsWith("") ? rawLine.slice(1) : rawLine;
123
+ currentAssistantLines.push(contentLine);
177
124
  }
178
125
  }
179
- // Finalize last turn
180
126
  if (currentUserText !== null && currentAssistantLines.length > 0) {
181
127
  turns.push({ user: currentUserText, assistantLines: currentAssistantLines });
182
128
  }
183
- // Convert to messages
184
129
  for (const turn of turns) {
185
130
  messages.push({ role: "user", content: turn.user });
186
- messages.push({ role: "assistant", content: turn.assistantLines.join("\n") });
131
+ const content = turn.assistantLines.join("\n").replace(/[ \t]+\n/g, "\n").replace(/[\n\s]+$/, "");
132
+ messages.push({ role: "assistant", content });
187
133
  }
188
134
  return messages;
189
135
  }
@@ -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
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Message Queue for managing user inputs
3
+ * Inspired by Happy's MessageQueue2 implementation
4
+ */
5
+ export class MessageQueue {
6
+ queue = [];
7
+ processing = false;
8
+ onMessageCallback = null;
9
+ lastMessageId = 0;
10
+ /**
11
+ * Add a message to the queue
12
+ */
13
+ enqueue(content, options = {}) {
14
+ const message = {
15
+ id: `msg-${++this.lastMessageId}-${Date.now()}`,
16
+ content,
17
+ priority: 1,
18
+ timestamp: Date.now(),
19
+ metadata: {
20
+ autonomyPolicy: options.autonomyPolicy,
21
+ approvalPolicy: options.approvalPolicy,
22
+ allowedScopes: options.allowedScopes,
23
+ },
24
+ };
25
+ this.queue.push(message);
26
+ this.processNext();
27
+ return message;
28
+ }
29
+ /**
30
+ * Add a high-priority message (like /compact, /clear)
31
+ */
32
+ enqueuePriority(content, options = {}) {
33
+ const message = {
34
+ id: `msg-priority-${++this.lastMessageId}-${Date.now()}`,
35
+ content,
36
+ priority: 10,
37
+ timestamp: Date.now(),
38
+ isolate: true,
39
+ metadata: {
40
+ autonomyPolicy: options.autonomyPolicy,
41
+ approvalPolicy: options.approvalPolicy,
42
+ allowedScopes: options.allowedScopes,
43
+ },
44
+ };
45
+ // Insert at the beginning of the queue
46
+ this.queue.unshift(message);
47
+ this.processNext();
48
+ return message;
49
+ }
50
+ /**
51
+ * Clear the queue and add a new message
52
+ * Used for /compact and /clear commands
53
+ */
54
+ clearAndEnqueue(content, options = {}) {
55
+ const clearedCount = this.queue.length;
56
+ this.queue = [];
57
+ if (clearedCount > 0) {
58
+ console.error(`[MessageQueue] Cleared ${clearedCount} pending messages`);
59
+ }
60
+ return this.enqueuePriority(content, options);
61
+ }
62
+ /**
63
+ * Set the message handler
64
+ */
65
+ onMessage(handler) {
66
+ this.onMessageCallback = handler;
67
+ this.processNext();
68
+ }
69
+ /**
70
+ * Process the next message in the queue
71
+ */
72
+ async processNext() {
73
+ if (this.processing || this.queue.length === 0 || !this.onMessageCallback) {
74
+ return;
75
+ }
76
+ this.processing = true;
77
+ // Sort by priority (higher first), then by timestamp
78
+ this.queue.sort((a, b) => {
79
+ if (a.priority !== b.priority) {
80
+ return b.priority - a.priority;
81
+ }
82
+ return a.timestamp - b.timestamp;
83
+ });
84
+ const message = this.queue.shift();
85
+ try {
86
+ await this.onMessageCallback(message);
87
+ }
88
+ catch (error) {
89
+ console.error(`[MessageQueue] Error processing message ${message.id}:`, error);
90
+ }
91
+ finally {
92
+ this.processing = false;
93
+ // Process next message
94
+ this.processNext();
95
+ }
96
+ }
97
+ /**
98
+ * Get the current queue length
99
+ */
100
+ get length() {
101
+ return this.queue.length;
102
+ }
103
+ /**
104
+ * Check if the queue is empty
105
+ */
106
+ get isEmpty() {
107
+ return this.queue.length === 0;
108
+ }
109
+ /**
110
+ * Check if a message is being processed
111
+ */
112
+ get isProcessing() {
113
+ return this.processing;
114
+ }
115
+ /**
116
+ * Clear all pending messages
117
+ */
118
+ clear() {
119
+ this.queue = [];
120
+ }
121
+ /**
122
+ * Get all pending messages (for debugging)
123
+ */
124
+ getPending() {
125
+ return [...this.queue];
126
+ }
127
+ }