@rind-ai/cli 0.4.1 → 0.6.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.
Files changed (49) hide show
  1. package/bin/rind.js +5 -5
  2. package/lib/assistant-renderer.js +179 -265
  3. package/lib/choice-menu-state.js +46 -46
  4. package/lib/cli-input-actions.js +548 -0
  5. package/lib/cli-output-controller.js +460 -0
  6. package/lib/cli-runtime-controller.js +350 -0
  7. package/lib/cli-state-store.js +32 -0
  8. package/lib/cli-state.js +41 -0
  9. package/lib/command-controller.js +159 -126
  10. package/lib/compact-context-state.js +22 -22
  11. package/lib/components/assistant-message.js +169 -0
  12. package/lib/components/composer-area.js +25 -0
  13. package/lib/components/dynamic-block.js +20 -0
  14. package/lib/components/monitor-stack.js +35 -0
  15. package/lib/components/text-block.js +47 -0
  16. package/lib/components/tool-block.js +122 -0
  17. package/lib/composer-terminal.js +224 -203
  18. package/lib/event-controller.js +243 -242
  19. package/lib/frontend-cli-implementation.js +656 -1111
  20. package/lib/input-controller.js +75 -94
  21. package/lib/input-errors.js +3 -3
  22. package/lib/interrupt-state.js +9 -9
  23. package/lib/line-editor.js +541 -541
  24. package/lib/local-slash-commands.js +217 -0
  25. package/lib/markdown-lines.js +103 -0
  26. package/lib/model-menu-state.js +50 -50
  27. package/lib/one-shot-progress.js +145 -0
  28. package/lib/one-shot.js +228 -0
  29. package/lib/question-menu-state.js +61 -0
  30. package/lib/rendering.js +1309 -1060
  31. package/lib/runtime-client.js +241 -193
  32. package/lib/runtime-env.js +21 -21
  33. package/lib/runtime-protocol.js +122 -15
  34. package/lib/slash-command-mode.js +0 -11
  35. package/lib/slash-menu-state.js +59 -59
  36. package/lib/{background-controller.js → task-monitor-controller.js} +411 -289
  37. package/lib/terminal-key.js +97 -97
  38. package/lib/text-width.js +335 -151
  39. package/lib/theme-menu-state.js +31 -0
  40. package/lib/theme.js +134 -0
  41. package/lib/tool-display.js +675 -0
  42. package/lib/tui/component.js +55 -0
  43. package/lib/tui/cursor.js +29 -0
  44. package/lib/tui/input-buffer.js +172 -0
  45. package/lib/tui/tui.js +591 -0
  46. package/lib/turn-controller.js +68 -78
  47. package/package.json +28 -28
  48. package/lib/assistant-stream-buffer.js +0 -25
  49. package/lib/terminal-ui.js +0 -581
@@ -1,201 +1,249 @@
1
- import { spawn, spawnSync } from "node:child_process";
2
- import path from "node:path";
3
-
4
- import { buildRuntimeEnv } from "./runtime-env.js";
5
- import { createRuntimeRequest, runtimeRequestId } from "./runtime-protocol.js";
6
-
7
- export function resolveRuntimeLaunch({ python, repoRoot, runtimePath = "", cliArgs = [] }) {
8
- const executable = runtimePath
9
- ? { command: runtimePath, args: [] }
10
- : { command: python, args: [path.join(repoRoot, "main.py")] };
11
- const traceFlag = isTraceLlmEnvSet() ? ["--trace-llm"] : [];
12
- return {
13
- command: executable.command,
14
- args: [...executable.args, "app-server", "--stdio", ...traceFlag, ...cliArgs],
15
- };
16
- }
17
-
18
- const TRACE_TRUTHY = new Set(["1", "true", "yes", "on"]);
19
- export function isTraceLlmEnvSet() {
20
- return TRACE_TRUTHY.has(String(process.env.RIND_TRACE_LLM || "").trim().toLowerCase());
21
- }
22
-
23
- export function runHelpVersion({ python, repoRoot, runtimePath = "", cliArgs, cwd = process.cwd() }) {
24
- const executable = runtimePath
25
- ? { command: runtimePath, args: [] }
26
- : { command: python, args: [path.join(repoRoot, "main.py")] };
27
- const result = spawnSync(executable.command, [...executable.args, ...cliArgs], {
28
- cwd,
29
- env: buildRuntimeEnv(repoRoot, process.env, { sourceRuntime: !runtimePath }),
30
- stdio: "inherit",
31
- });
32
- return result.status ?? 1;
33
- }
34
-
35
- export function createRuntimeClient({
36
- python,
37
- repoRoot,
38
- cliArgs = [],
39
- cwd = process.cwd(),
40
- rindHome = process.env.RIND_HOME,
41
- runtimePath = process.env.RIND_RUNTIME_PATH || "",
42
- onEvent = () => {},
43
- onMessage = null,
44
- onStderr = () => {},
45
- onExit = () => {},
46
- }) {
47
- const handleEvent = onMessage || onEvent;
48
- const launch = resolveRuntimeLaunch({ python, repoRoot, runtimePath, cliArgs });
49
-
50
- let nextId = 1;
51
- let stdoutBuffer = "";
52
- let closing = false;
53
- let killTimer = null;
54
- let exitHandled = false;
55
- const pending = new Map();
56
- const child = spawn(launch.command, launch.args, {
57
- cwd,
58
- env: buildRuntimeEnv(repoRoot, process.env, {
59
- sourceRuntime: !runtimePath,
60
- rindHome,
61
- }),
62
- stdio: ["pipe", "pipe", "pipe"],
63
- });
64
-
65
- child.stdout.setEncoding("utf8");
66
- child.stdout.on("data", (chunk) => {
67
- stdoutBuffer += chunk;
68
- const lines = stdoutBuffer.split(/\r?\n/);
69
- stdoutBuffer = lines.pop() || "";
70
- for (const line of lines) {
71
- if (line) {
72
- receive(line);
73
- }
74
- }
75
- });
76
- child.stderr.on("data", (chunk) => onStderr(chunk));
77
- child.once("error", (error) => {
78
- handleExit(null, null, error);
79
- });
80
- child.once("exit", (code, signal) => {
81
- handleExit(code, signal);
82
- });
83
-
84
- function handleExit(code, signal, cause = null) {
85
- if (exitHandled) {
86
- return;
87
- }
88
- exitHandled = true;
89
- clearKillTimer();
90
- const error = cause || new Error(`Runtime exited with ${signal || code}`);
91
- for (const { reject } of pending.values()) {
92
- reject(error);
93
- }
94
- pending.clear();
95
- onExit(code, signal, { closing, error });
96
- }
97
-
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import path from "node:path";
3
+
4
+ import { buildRuntimeEnv } from "./runtime-env.js";
5
+ import {
6
+ createRuntimeRequest,
7
+ isRuntimeEvent,
8
+ isRuntimeResponse,
9
+ runtimeMethods,
10
+ runtimeRequestId,
11
+ } from "./runtime-protocol.js";
12
+
13
+ export function resolveRuntimeLaunch({ python, repoRoot, runtimePath = "", cliArgs = [] }) {
14
+ const executable = runtimePath
15
+ ? { command: runtimePath, args: [] }
16
+ : { command: python, args: [path.join(repoRoot, "main.py")] };
17
+ const traceFlag = isTraceLlmEnvSet() ? ["--trace-llm"] : [];
18
+ return {
19
+ command: executable.command,
20
+ args: [...executable.args, "app-server", "--stdio", ...traceFlag, ...cliArgs],
21
+ };
22
+ }
23
+
24
+ const TRACE_TRUTHY = new Set(["1", "true", "yes", "on"]);
25
+ export function isTraceLlmEnvSet() {
26
+ return TRACE_TRUTHY.has(String(process.env.RIND_TRACE_LLM || "").trim().toLowerCase());
27
+ }
28
+
29
+ // Turns stream for as long as the model runs; every other method must answer
30
+ // within a bounded window so a stalled runtime surfaces as an error, not silence.
31
+ const LONG_RUNNING_METHODS = new Set([runtimeMethods.sessionPrompt, runtimeMethods.sessionFollowUp]);
32
+ const REQUEST_TIMEOUT_MS = 120_000;
33
+
34
+ export function runHelpVersion({ python, repoRoot, runtimePath = "", cliArgs, cwd = process.cwd() }) {
35
+ const executable = runtimePath
36
+ ? { command: runtimePath, args: [] }
37
+ : { command: python, args: [path.join(repoRoot, "main.py")] };
38
+ const result = spawnSync(executable.command, [...executable.args, ...cliArgs], {
39
+ cwd,
40
+ env: buildRuntimeEnv(repoRoot, process.env, { sourceRuntime: !runtimePath }),
41
+ stdio: "inherit",
42
+ });
43
+ return result.status ?? 1;
44
+ }
45
+
46
+ export function createRuntimeClient({
47
+ python,
48
+ repoRoot,
49
+ cliArgs = [],
50
+ cwd = process.cwd(),
51
+ rindHome = process.env.RIND_HOME,
52
+ runtimePath = process.env.RIND_RUNTIME_PATH || "",
53
+ onEvent = () => {},
54
+ onMessage = null,
55
+ onStderr = () => {},
56
+ onExit = () => {},
57
+ }) {
58
+ const handleEvent = onMessage || onEvent;
59
+ const launch = resolveRuntimeLaunch({ python, repoRoot, runtimePath, cliArgs });
60
+
61
+ let nextId = 1;
62
+ let stdoutBuffer = "";
63
+ let closing = false;
64
+ let killTimer = null;
65
+ let exitHandled = false;
66
+ const pending = new Map();
67
+ let child = null;
68
+
69
+ function start() {
70
+ if (child && !child.killed && child.exitCode === null) {
71
+ return child;
72
+ }
73
+ if (closing) {
74
+ throw new Error("Runtime is shutting down.");
75
+ }
76
+ stdoutBuffer = "";
77
+ exitHandled = false;
78
+ child = spawn(launch.command, launch.args, {
79
+ cwd,
80
+ env: buildRuntimeEnv(repoRoot, process.env, {
81
+ sourceRuntime: !runtimePath,
82
+ rindHome,
83
+ }),
84
+ stdio: ["pipe", "pipe", "pipe"],
85
+ });
86
+ child.stdout.setEncoding("utf8");
87
+ child.stdout.on("data", (chunk) => {
88
+ stdoutBuffer += chunk;
89
+ const lines = stdoutBuffer.split(/\r?\n/);
90
+ stdoutBuffer = lines.pop() || "";
91
+ for (const line of lines) {
92
+ if (line) {
93
+ receive(line);
94
+ }
95
+ }
96
+ });
97
+ child.stderr.on("data", (chunk) => onStderr(chunk));
98
+ child.once("error", (error) => {
99
+ handleExit(null, null, error);
100
+ });
101
+ child.once("exit", (code, signal) => {
102
+ handleExit(code, signal);
103
+ });
104
+ return child;
105
+ }
106
+
107
+ function handleExit(code, signal, cause = null) {
108
+ if (exitHandled) {
109
+ return;
110
+ }
111
+ exitHandled = true;
112
+ clearKillTimer();
113
+ const error = cause || new Error(`Runtime exited with ${signal || code}`);
114
+ for (const entry of pending.values()) {
115
+ clearRequestTimer(entry);
116
+ entry.reject(error);
117
+ }
118
+ pending.clear();
119
+ child = null;
120
+ onExit(code, signal, { closing, error });
121
+ }
122
+
98
123
  function request(method, params = {}) {
99
124
  const id = nextId++;
100
125
  return new Promise((resolve, reject) => {
101
- if (!child.stdin.writable || child.destroyed) {
102
- reject(new Error("Runtime stdin is closed. Restart Rind and try again."));
126
+ if (!child || !child.stdin.writable || child.destroyed) {
127
+ reject(new Error("Runtime is not running. Start it before sending a request."));
103
128
  return;
104
129
  }
105
- pending.set(id, { resolve, reject });
106
- child.stdin.write(JSON.stringify(createRuntimeRequest(id, method, params)) + "\n", (error) => {
107
- if (!error) {
108
- return;
109
- }
110
- pending.delete(id);
111
- reject(error);
112
- });
113
- });
114
- }
115
-
116
- function receive(line) {
117
- let message;
118
- try {
119
- message = JSON.parse(line);
120
- } catch {
121
- return;
122
- }
123
- if (message.kind === "response") {
124
- finishRequest(message);
125
- return;
126
- }
127
- if (message.kind === "event") {
128
- handleEvent(message);
129
- }
130
- }
131
-
132
- function finishRequest(message) {
133
- const id = runtimeRequestId(message);
134
- const callbacks = pending.get(id);
135
- if (!callbacks) {
136
- return;
137
- }
138
- pending.delete(id);
139
- if (message.error) {
140
- callbacks.reject(new Error(message.error.message || "Runtime request failed"));
141
- } else {
142
- callbacks.resolve(message.result);
143
- }
144
- }
145
-
146
- function shutdown() {
147
- if (closing) {
148
- return Promise.resolve();
149
- }
150
- closing = true;
151
- scheduleKill();
152
- return request("shutdown").catch(() => {
153
- forceShutdown();
154
- }).finally(() => {
155
- if (child.stdin.writable) {
156
- child.stdin.end();
157
- }
158
- });
159
- }
160
-
161
- function forceShutdown() {
162
- closing = true;
163
- clearKillTimer();
164
- if (!child.killed && child.exitCode === null) {
165
- try {
166
- child.kill("SIGKILL");
167
- } catch {
168
- // Ignore kill races during shutdown.
169
- }
170
- }
171
- }
172
-
173
- function closeInput() {
174
- if (child.stdin.writable) {
175
- child.stdin.end();
176
- }
177
- }
178
-
179
- function scheduleKill() {
180
- clearKillTimer();
181
- killTimer = setTimeout(forceShutdown, 1500);
182
- killTimer.unref?.();
183
- }
184
-
185
- function clearKillTimer() {
186
- if (!killTimer) {
187
- return;
188
- }
189
- clearTimeout(killTimer);
190
- killTimer = null;
130
+ const entry = { resolve, reject };
131
+ if (!LONG_RUNNING_METHODS.has(method)) {
132
+ entry.timer = setTimeout(() => {
133
+ pending.delete(id);
134
+ reject(new Error(`Runtime request timed out after ${REQUEST_TIMEOUT_MS / 1000}s: ${method}`));
135
+ }, REQUEST_TIMEOUT_MS);
136
+ entry.timer.unref?.();
137
+ }
138
+ pending.set(id, entry);
139
+ child.stdin.write(JSON.stringify(createRuntimeRequest(id, method, params)) + "\n", (error) => {
140
+ if (!error) {
141
+ return;
142
+ }
143
+ pending.delete(id);
144
+ clearRequestTimer(entry);
145
+ reject(error);
146
+ });
147
+ });
191
148
  }
192
149
 
193
- return {
194
- child,
150
+ function receive(line) {
151
+ let message;
152
+ try {
153
+ message = JSON.parse(line);
154
+ } catch {
155
+ return;
156
+ }
157
+ if (isRuntimeResponse(message)) {
158
+ finishRequest(message);
159
+ return;
160
+ }
161
+ if (isRuntimeEvent(message)) {
162
+ handleEvent(message);
163
+ }
164
+ }
165
+
166
+ function finishRequest(message) {
167
+ const id = runtimeRequestId(message);
168
+ const callbacks = pending.get(id);
169
+ if (!callbacks) {
170
+ return;
171
+ }
172
+ pending.delete(id);
173
+ clearRequestTimer(callbacks);
174
+ if (message.error) {
175
+ callbacks.reject(new Error(message.error.message || "Runtime request failed"));
176
+ } else {
177
+ callbacks.resolve(message.result);
178
+ }
179
+ }
180
+
181
+ function clearRequestTimer(entry) {
182
+ if (entry.timer) {
183
+ clearTimeout(entry.timer);
184
+ entry.timer = null;
185
+ }
186
+ }
187
+
188
+ function shutdown() {
189
+ if (closing) {
190
+ return Promise.resolve();
191
+ }
192
+ closing = true;
193
+ if (!child) {
194
+ return Promise.resolve();
195
+ }
196
+ scheduleKill();
197
+ return request(runtimeMethods.shutdown).catch(() => {
198
+ forceShutdown();
199
+ }).finally(() => {
200
+ if (child?.stdin.writable) {
201
+ child.stdin.end();
202
+ }
203
+ });
204
+ }
205
+
206
+ function forceShutdown() {
207
+ closing = true;
208
+ clearKillTimer();
209
+ if (child && !child.killed && child.exitCode === null) {
210
+ try {
211
+ child.kill("SIGKILL");
212
+ } catch {
213
+ // Ignore kill races during shutdown.
214
+ }
215
+ }
216
+ }
217
+
218
+ function closeInput() {
219
+ if (child?.stdin.writable) {
220
+ child.stdin.end();
221
+ }
222
+ }
223
+
224
+ function scheduleKill() {
225
+ clearKillTimer();
226
+ killTimer = setTimeout(forceShutdown, 1500);
227
+ killTimer.unref?.();
228
+ }
229
+
230
+ function clearKillTimer() {
231
+ if (!killTimer) {
232
+ return;
233
+ }
234
+ clearTimeout(killTimer);
235
+ killTimer = null;
236
+ }
237
+
238
+ return {
239
+ get child() {
240
+ return child;
241
+ },
242
+ start,
195
243
  request,
196
- shutdown,
197
- forceShutdown,
198
- closeInput,
199
- isClosing: () => closing,
200
- };
201
- }
244
+ shutdown,
245
+ forceShutdown,
246
+ closeInput,
247
+ isClosing: () => closing,
248
+ };
249
+ }
@@ -1,21 +1,21 @@
1
- import path from "node:path";
2
-
3
- export function buildRuntimeEnv(repoRoot, baseEnv = process.env, { sourceRuntime = true, rindHome } = {}) {
4
- const env = { ...baseEnv };
5
- if (rindHome) {
6
- env.RIND_HOME = rindHome;
7
- }
8
- if (!sourceRuntime) {
9
- return env;
10
- }
11
- return {
12
- ...env,
13
- PYTHONIOENCODING: "utf-8",
14
- PYTHONPATH: prependPath(repoRoot, baseEnv.PYTHONPATH),
15
- PYTHONUTF8: "1",
16
- };
17
- }
18
-
19
- function prependPath(entry, value) {
20
- return value ? `${entry}${path.delimiter}${value}` : entry;
21
- }
1
+ import path from "node:path";
2
+
3
+ export function buildRuntimeEnv(repoRoot, baseEnv = process.env, { sourceRuntime = true, rindHome } = {}) {
4
+ const env = { ...baseEnv };
5
+ if (rindHome) {
6
+ env.RIND_HOME = rindHome;
7
+ }
8
+ if (!sourceRuntime) {
9
+ return env;
10
+ }
11
+ return {
12
+ ...env,
13
+ PYTHONIOENCODING: "utf-8",
14
+ PYTHONPATH: prependPath(repoRoot, baseEnv.PYTHONPATH),
15
+ PYTHONUTF8: "1",
16
+ };
17
+ }
18
+
19
+ function prependPath(entry, value) {
20
+ return value ? `${entry}${path.delimiter}${value}` : entry;
21
+ }
@@ -1,15 +1,122 @@
1
- export function createRuntimeRequest(requestId, method, params = {}) {
2
- return { request_id: requestId, method, params };
3
- }
4
-
5
- export function runtimeRequestId(message) {
6
- return message?.request_id;
7
- }
8
-
9
- export function runtimeEventType(message) {
10
- return message?.event_type || message?.event?.type || "";
11
- }
12
-
13
- export function turnInputMethod(activeTurn) {
14
- return activeTurn ? "turn.follow_up" : "turn.start";
15
- }
1
+ export const runtimeProtocolVersion = "2";
2
+
3
+ export const REASONING_EFFORTS = Object.freeze(["low", "medium", "high", "xhigh", "max"]);
4
+
5
+ export const runtimeMethods = Object.freeze({
6
+ initialize: "initialize",
7
+ shutdown: "shutdown",
8
+ sessionNew: "session/new",
9
+ sessionList: "session/list",
10
+ sessionSwitch: "session/switch",
11
+ sessionReplay: "session/replay",
12
+ sessionPrompt: "session/prompt",
13
+ sessionCancel: "session/cancel",
14
+ modelList: "model/list",
15
+ modelSet: "model/set",
16
+ modelEffortSet: "model/effort",
17
+ sessionSteer: "rind/session/steer",
18
+ sessionFollowUp: "rind/session/follow_up",
19
+ sessionPromoteFollowUp: "rind/session/promote_follow_up",
20
+ sessionUnsteer: "rind/session/unsteer",
21
+ sessionDequeueFollowUp: "rind/session/dequeue_follow_up",
22
+ sessionCompact: "rind/session/compact",
23
+ commandExecute: "rind/command/execute",
24
+ userQuestionRespond: "rind/user-question/respond",
25
+ backgroundList: "rind/background/list",
26
+ backgroundOutput: "rind/background/output",
27
+ goalGet: "rind/goal/get",
28
+ goalSet: "rind/goal/set",
29
+ goalStatus: "rind/goal/status",
30
+ goalClear: "rind/goal/clear",
31
+ });
32
+
33
+ export const sessionScopedMethods = new Set([
34
+ runtimeMethods.sessionPrompt,
35
+ runtimeMethods.sessionReplay,
36
+ runtimeMethods.sessionSwitch,
37
+ runtimeMethods.sessionCancel,
38
+ runtimeMethods.modelSet,
39
+ runtimeMethods.modelEffortSet,
40
+ runtimeMethods.sessionSteer,
41
+ runtimeMethods.sessionFollowUp,
42
+ runtimeMethods.sessionPromoteFollowUp,
43
+ runtimeMethods.sessionUnsteer,
44
+ runtimeMethods.sessionDequeueFollowUp,
45
+ runtimeMethods.sessionCompact,
46
+ runtimeMethods.commandExecute,
47
+ runtimeMethods.userQuestionRespond,
48
+ runtimeMethods.backgroundList,
49
+ runtimeMethods.backgroundOutput,
50
+ runtimeMethods.goalGet,
51
+ runtimeMethods.goalSet,
52
+ runtimeMethods.goalStatus,
53
+ runtimeMethods.goalClear,
54
+ ]);
55
+
56
+ export const turnScopedMethods = new Set([
57
+ runtimeMethods.sessionCancel,
58
+ runtimeMethods.sessionSteer,
59
+ ]);
60
+
61
+ export function createRuntimeRequest(requestId, method, params = {}) {
62
+ return { kind: "request", request_id: requestId, method, params };
63
+ }
64
+
65
+ export function isRuntimeResponse(message) {
66
+ return message?.kind === "response"
67
+ && isRequestId(message.request_id)
68
+ && (Object.hasOwn(message, "result") || isRuntimeError(message.error));
69
+ }
70
+
71
+ export function isRuntimeEvent(message) {
72
+ return message?.kind === "event"
73
+ && message.method === "session/update"
74
+ && Number.isInteger(message.sequence)
75
+ && (message.durability === "durable" || message.durability === "incremental")
76
+ && typeof message.session_id === "string"
77
+ && typeof message.turn_id === "string"
78
+ && isRecord(message.event);
79
+ }
80
+
81
+ export function requireRuntimeInitialization(result) {
82
+ if (!isRecord(result) || result.protocol_version !== runtimeProtocolVersion) {
83
+ const received = isRecord(result) ? String(result.protocol_version || "missing") : "invalid";
84
+ throw new Error(`Unsupported Runtime protocol version: ${received}.`);
85
+ }
86
+ if (!Array.isArray(result.capabilities) || !Array.isArray(result.methods)) {
87
+ throw new Error("Runtime initialization response is missing capabilities or methods.");
88
+ }
89
+ return result;
90
+ }
91
+
92
+ export function runtimeRequestId(message) {
93
+ return message?.request_id;
94
+ }
95
+
96
+ export function runtimeEventType(message) {
97
+ return message?.event?.type || "";
98
+ }
99
+
100
+ export function isRuntimeEventForTurn(message, sessionId = "", turnId = "") {
101
+ const eventSessionId = String(message?.session_id || "");
102
+ if (sessionId && eventSessionId && eventSessionId !== sessionId) {
103
+ return false;
104
+ }
105
+ if (runtimeEventType(message) === "turn_started") {
106
+ return Boolean(String(message?.turn_id || ""));
107
+ }
108
+ const eventTurnId = String(message?.turn_id || "");
109
+ return !eventTurnId || Boolean(turnId && eventTurnId === turnId);
110
+ }
111
+
112
+ function isRequestId(value) {
113
+ return (typeof value === "string" && value.trim()) || (typeof value === "number" && Number.isFinite(value));
114
+ }
115
+
116
+ function isRuntimeError(value) {
117
+ return isRecord(value) && typeof value.type === "string" && typeof value.message === "string";
118
+ }
119
+
120
+ function isRecord(value) {
121
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
122
+ }
@@ -1,14 +1,3 @@
1
- export function isReadonlySlashCommand(value) {
2
- const text = String(value || "").trim().toLowerCase();
3
- return text === "/status" || text.startsWith("/status ") || text === "/doctor" || text.startsWith("/doctor ");
4
- }
5
-
6
- export function steeringCommandText(value) {
7
- const text = String(value || "").trim();
8
- const match = text.match(/^\/steer(?:\s+([\s\S]*))?$/i);
9
- return match ? String(match[1] || "").trim() : null;
10
- }
11
-
12
1
  export function parseGoalCommand(value) {
13
2
  const text = String(value || "").trim();
14
3
  const match = text.match(/^\/goal(?:\s+([\s\S]*))?$/i);