@sheepbun/yips 0.1.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.
Files changed (99) hide show
  1. package/dist/agent/commands/command-catalog.js +243 -0
  2. package/dist/agent/commands/commands.js +418 -0
  3. package/dist/agent/conductor.js +118 -0
  4. package/dist/agent/context/code-context.js +68 -0
  5. package/dist/agent/context/memory-store.js +159 -0
  6. package/dist/agent/context/session-store.js +211 -0
  7. package/dist/agent/protocol/tool-protocol.js +160 -0
  8. package/dist/agent/skills/skills.js +327 -0
  9. package/dist/agent/tools/tool-executor.js +415 -0
  10. package/dist/agent/tools/tool-safety.js +52 -0
  11. package/dist/app/index.js +35 -0
  12. package/dist/app/repl.js +105 -0
  13. package/dist/app/update-check.js +132 -0
  14. package/dist/app/version.js +51 -0
  15. package/dist/code-context.js +68 -0
  16. package/dist/colors.js +204 -0
  17. package/dist/command-catalog.js +242 -0
  18. package/dist/commands.js +350 -0
  19. package/dist/conductor.js +94 -0
  20. package/dist/config/config.js +335 -0
  21. package/dist/config/hooks.js +187 -0
  22. package/dist/config.js +335 -0
  23. package/dist/downloader-state.js +302 -0
  24. package/dist/downloader-ui.js +289 -0
  25. package/dist/gateway/adapters/discord.js +108 -0
  26. package/dist/gateway/adapters/formatting.js +96 -0
  27. package/dist/gateway/adapters/telegram.js +106 -0
  28. package/dist/gateway/adapters/types.js +2 -0
  29. package/dist/gateway/adapters/whatsapp.js +124 -0
  30. package/dist/gateway/auth-policy.js +66 -0
  31. package/dist/gateway/core.js +87 -0
  32. package/dist/gateway/headless-conductor.js +328 -0
  33. package/dist/gateway/message-router.js +23 -0
  34. package/dist/gateway/rate-limiter.js +48 -0
  35. package/dist/gateway/runtime/backend-policy.js +18 -0
  36. package/dist/gateway/runtime/discord-bot.js +104 -0
  37. package/dist/gateway/runtime/discord-main.js +69 -0
  38. package/dist/gateway/session-manager.js +77 -0
  39. package/dist/gateway/types.js +2 -0
  40. package/dist/hardware.js +92 -0
  41. package/dist/hooks.js +187 -0
  42. package/dist/index.js +34 -0
  43. package/dist/input-engine.js +250 -0
  44. package/dist/llama-client.js +227 -0
  45. package/dist/llama-server.js +620 -0
  46. package/dist/llm/llama-client.js +227 -0
  47. package/dist/llm/llama-server.js +620 -0
  48. package/dist/llm/token-counter.js +47 -0
  49. package/dist/memory-store.js +159 -0
  50. package/dist/messages.js +59 -0
  51. package/dist/model-downloader.js +382 -0
  52. package/dist/model-manager-state.js +118 -0
  53. package/dist/model-manager-ui.js +194 -0
  54. package/dist/model-manager.js +190 -0
  55. package/dist/models/hardware.js +92 -0
  56. package/dist/models/model-downloader.js +382 -0
  57. package/dist/models/model-manager.js +190 -0
  58. package/dist/prompt-box.js +78 -0
  59. package/dist/prompt-composer.js +498 -0
  60. package/dist/repl.js +105 -0
  61. package/dist/session-store.js +211 -0
  62. package/dist/spinner.js +76 -0
  63. package/dist/title-box.js +388 -0
  64. package/dist/token-counter.js +47 -0
  65. package/dist/tool-executor.js +415 -0
  66. package/dist/tool-protocol.js +121 -0
  67. package/dist/tool-safety.js +52 -0
  68. package/dist/tui/app.js +2553 -0
  69. package/dist/tui/startup.js +56 -0
  70. package/dist/tui-input-routing.js +53 -0
  71. package/dist/tui.js +51 -0
  72. package/dist/types/app-types.js +2 -0
  73. package/dist/types.js +2 -0
  74. package/dist/ui/colors.js +204 -0
  75. package/dist/ui/downloader/downloader-state.js +302 -0
  76. package/dist/ui/downloader/downloader-ui.js +289 -0
  77. package/dist/ui/input/input-engine.js +250 -0
  78. package/dist/ui/input/tui-input-routing.js +53 -0
  79. package/dist/ui/input/vt-session.js +168 -0
  80. package/dist/ui/messages.js +59 -0
  81. package/dist/ui/model-manager/model-manager-state.js +118 -0
  82. package/dist/ui/model-manager/model-manager-ui.js +194 -0
  83. package/dist/ui/prompt/prompt-box.js +78 -0
  84. package/dist/ui/prompt/prompt-composer.js +498 -0
  85. package/dist/ui/spinner.js +76 -0
  86. package/dist/ui/title-box.js +388 -0
  87. package/dist/ui/tui/app.js +6 -0
  88. package/dist/ui/tui/autocomplete.js +85 -0
  89. package/dist/ui/tui/constants.js +18 -0
  90. package/dist/ui/tui/history.js +29 -0
  91. package/dist/ui/tui/layout.js +341 -0
  92. package/dist/ui/tui/runtime-core.js +2584 -0
  93. package/dist/ui/tui/runtime-utils.js +53 -0
  94. package/dist/ui/tui/start-tui.js +54 -0
  95. package/dist/ui/tui/startup.js +56 -0
  96. package/dist/version.js +51 -0
  97. package/dist/vt-session.js +168 -0
  98. package/install.sh +457 -0
  99. package/package.json +128 -0
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeAutoMaxTokens = computeAutoMaxTokens;
4
+ exports.resolveEffectiveMaxTokens = resolveEffectiveMaxTokens;
5
+ exports.formatTitleTokenUsage = formatTitleTokenUsage;
6
+ exports.estimateConversationTokens = estimateConversationTokens;
7
+ function clamp(value, min, max) {
8
+ return Math.max(min, Math.min(max, value));
9
+ }
10
+ function computeAutoMaxTokens(input) {
11
+ const safeRamGb = Number.isFinite(input.ramGb) && input.ramGb > 0 ? input.ramGb : 0;
12
+ const safeModelBytes = Number.isFinite(input.modelSizeBytes) && input.modelSizeBytes > 0 ? input.modelSizeBytes : 0;
13
+ const modelSizeGb = safeModelBytes / 1024 ** 3;
14
+ const availableGb = Math.max(0, safeRamGb - modelSizeGb - 2);
15
+ const rawTokens = Math.floor(availableGb * 1500);
16
+ return clamp(rawTokens, 4096, 128000);
17
+ }
18
+ function resolveEffectiveMaxTokens(tokensMode, manualMax, autoMax) {
19
+ if (tokensMode === "manual") {
20
+ return Number.isFinite(manualMax) && manualMax > 0 ? Math.floor(manualMax) : autoMax;
21
+ }
22
+ return autoMax;
23
+ }
24
+ function formatTokenCount(value) {
25
+ if (value < 1000) {
26
+ return String(Math.floor(value));
27
+ }
28
+ const rounded = Number((value / 1000).toFixed(1));
29
+ const compact = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
30
+ return `${compact}k`;
31
+ }
32
+ function formatTitleTokenUsage(usedTokens, maxTokens) {
33
+ const safeUsed = Number.isFinite(usedTokens) && usedTokens > 0 ? usedTokens : 0;
34
+ const safeMax = Number.isFinite(maxTokens) && maxTokens > 0 ? maxTokens : 0;
35
+ return `${formatTokenCount(safeUsed)}/${formatTokenCount(safeMax)} tks`;
36
+ }
37
+ function estimateConversationTokens(messages) {
38
+ let total = 0;
39
+ for (const message of messages) {
40
+ const chars = Array.from(message.content).length;
41
+ if (chars === 0) {
42
+ continue;
43
+ }
44
+ total += Math.max(1, Math.ceil(chars / 4));
45
+ }
46
+ return total;
47
+ }
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeToolCall = executeToolCall;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_path_1 = require("node:path");
6
+ const node_child_process_1 = require("node:child_process");
7
+ const node_util_1 = require("node:util");
8
+ const hooks_1 = require("./hooks");
9
+ const tool_safety_1 = require("./tool-safety");
10
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
11
+ function normalizePositiveInt(value, fallback, max) {
12
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
13
+ return Math.min(value, max);
14
+ }
15
+ return fallback;
16
+ }
17
+ function normalizeString(value) {
18
+ if (typeof value !== "string") {
19
+ return null;
20
+ }
21
+ const trimmed = value.trim();
22
+ return trimmed.length > 0 ? trimmed : null;
23
+ }
24
+ function toErrorMessage(error) {
25
+ return error instanceof Error ? error.message : String(error);
26
+ }
27
+ function toLines(text) {
28
+ return text.split("\n");
29
+ }
30
+ function buildDiffPreview(before, after, maxBodyLines = 80) {
31
+ if (before === after) {
32
+ return "No content changes.";
33
+ }
34
+ const oldLines = toLines(before);
35
+ const newLines = toLines(after);
36
+ let prefix = 0;
37
+ while (prefix < oldLines.length &&
38
+ prefix < newLines.length &&
39
+ oldLines[prefix] === newLines[prefix]) {
40
+ prefix += 1;
41
+ }
42
+ let oldSuffix = oldLines.length - 1;
43
+ let newSuffix = newLines.length - 1;
44
+ while (oldSuffix >= prefix &&
45
+ newSuffix >= prefix &&
46
+ oldLines[oldSuffix] === newLines[newSuffix]) {
47
+ oldSuffix -= 1;
48
+ newSuffix -= 1;
49
+ }
50
+ const removed = oldLines.slice(prefix, oldSuffix + 1);
51
+ const added = newLines.slice(prefix, newSuffix + 1);
52
+ const hunkLines = [];
53
+ for (const line of removed) {
54
+ hunkLines.push(`-${line}`);
55
+ }
56
+ for (const line of added) {
57
+ hunkLines.push(`+${line}`);
58
+ }
59
+ const truncated = hunkLines.length > maxBodyLines;
60
+ const shown = truncated ? hunkLines.slice(0, maxBodyLines) : hunkLines;
61
+ const header = [
62
+ "--- before",
63
+ "+++ after",
64
+ `@@ -${prefix + 1},${removed.length} +${prefix + 1},${added.length} @@`
65
+ ];
66
+ if (truncated) {
67
+ shown.push(`... truncated ${hunkLines.length - maxBodyLines} additional diff lines ...`);
68
+ }
69
+ return [...header, ...shown].join("\n");
70
+ }
71
+ function summarizeHookResult(hookResult) {
72
+ return {
73
+ status: hookResult.status,
74
+ message: hookResult.message,
75
+ eventId: hookResult.eventId,
76
+ durationMs: hookResult.durationMs,
77
+ exitCode: hookResult.exitCode,
78
+ timedOut: hookResult.timedOut,
79
+ stdout: hookResult.stdout,
80
+ stderr: hookResult.stderr
81
+ };
82
+ }
83
+ async function runFileWriteHook(context, payload) {
84
+ if (!context.runHook) {
85
+ return null;
86
+ }
87
+ try {
88
+ return await context.runHook("on-file-write", payload);
89
+ }
90
+ catch (error) {
91
+ const message = toErrorMessage(error);
92
+ return {
93
+ hook: "on-file-write",
94
+ status: "error",
95
+ command: null,
96
+ cwd: context.workingDirectory,
97
+ message: `Hook execution failed: ${message}`,
98
+ durationMs: 0,
99
+ eventId: "unknown",
100
+ timestamp: new Date().toISOString(),
101
+ stdout: "",
102
+ stderr: message,
103
+ exitCode: null,
104
+ signal: null,
105
+ timedOut: false
106
+ };
107
+ }
108
+ }
109
+ async function executeReadFile(call, context) {
110
+ const pathArg = normalizeString(call.arguments["path"]);
111
+ if (!pathArg) {
112
+ return {
113
+ callId: call.id,
114
+ tool: call.name,
115
+ status: "error",
116
+ output: "read_file requires a non-empty 'path' argument."
117
+ };
118
+ }
119
+ const maxBytes = normalizePositiveInt(call.arguments["maxBytes"], 200_000, 500_000);
120
+ const absolutePath = (0, tool_safety_1.resolveToolPath)(pathArg, context.workingDirectory);
121
+ try {
122
+ const content = await (0, promises_1.readFile)(absolutePath, "utf8");
123
+ const clipped = content.slice(0, maxBytes);
124
+ const truncated = clipped.length < content.length;
125
+ return {
126
+ callId: call.id,
127
+ tool: call.name,
128
+ status: "ok",
129
+ output: truncated ? `${clipped}\n\n[truncated at ${maxBytes} bytes]` : clipped,
130
+ metadata: { path: absolutePath, maxBytes, truncated }
131
+ };
132
+ }
133
+ catch (error) {
134
+ return {
135
+ callId: call.id,
136
+ tool: call.name,
137
+ status: "error",
138
+ output: `read_file failed: ${toErrorMessage(error)}`,
139
+ metadata: { path: absolutePath }
140
+ };
141
+ }
142
+ }
143
+ async function executeWriteFile(call, context) {
144
+ const pathArg = normalizeString(call.arguments["path"]);
145
+ if (!pathArg) {
146
+ return {
147
+ callId: call.id,
148
+ tool: call.name,
149
+ status: "error",
150
+ output: "write_file requires a non-empty 'path' argument."
151
+ };
152
+ }
153
+ const content = typeof call.arguments["content"] === "string" ? call.arguments["content"] : null;
154
+ if (content === null) {
155
+ return {
156
+ callId: call.id,
157
+ tool: call.name,
158
+ status: "error",
159
+ output: "write_file requires a string 'content' argument."
160
+ };
161
+ }
162
+ const absolutePath = (0, tool_safety_1.resolveToolPath)(pathArg, context.workingDirectory);
163
+ const parentDir = (0, node_path_1.resolve)(absolutePath, "..");
164
+ let before = "";
165
+ try {
166
+ before = await (0, promises_1.readFile)(absolutePath, "utf8");
167
+ }
168
+ catch {
169
+ before = "";
170
+ }
171
+ try {
172
+ await (0, promises_1.mkdir)(parentDir, { recursive: true });
173
+ await (0, promises_1.writeFile)(absolutePath, content, "utf8");
174
+ const diffPreview = buildDiffPreview(before, content);
175
+ const hookResult = await runFileWriteHook(context, {
176
+ operation: "write_file",
177
+ path: absolutePath,
178
+ bytesAfter: content.length
179
+ });
180
+ const hookWarning = hookResult && hookResult.status !== "ok" && hookResult.status !== "skipped"
181
+ ? `\n${(0, hooks_1.formatHookFailure)(hookResult)}`
182
+ : "";
183
+ return {
184
+ callId: call.id,
185
+ tool: call.name,
186
+ status: "ok",
187
+ output: `Wrote ${absolutePath}\n${diffPreview}${hookWarning}`,
188
+ metadata: {
189
+ path: absolutePath,
190
+ bytes: content.length,
191
+ diffPreview,
192
+ hook: hookResult ? summarizeHookResult(hookResult) : undefined
193
+ }
194
+ };
195
+ }
196
+ catch (error) {
197
+ return {
198
+ callId: call.id,
199
+ tool: call.name,
200
+ status: "error",
201
+ output: `write_file failed: ${toErrorMessage(error)}`,
202
+ metadata: { path: absolutePath }
203
+ };
204
+ }
205
+ }
206
+ async function executeEditFile(call, context) {
207
+ const pathArg = normalizeString(call.arguments["path"]);
208
+ if (!pathArg) {
209
+ return {
210
+ callId: call.id,
211
+ tool: call.name,
212
+ status: "error",
213
+ output: "edit_file requires a non-empty 'path' argument."
214
+ };
215
+ }
216
+ const oldText = typeof call.arguments["oldText"] === "string" ? call.arguments["oldText"] : null;
217
+ const newText = typeof call.arguments["newText"] === "string" ? call.arguments["newText"] : null;
218
+ if (oldText === null || newText === null) {
219
+ return {
220
+ callId: call.id,
221
+ tool: call.name,
222
+ status: "error",
223
+ output: "edit_file requires string arguments 'oldText' and 'newText'."
224
+ };
225
+ }
226
+ const replaceAll = call.arguments["replaceAll"] === true;
227
+ const absolutePath = (0, tool_safety_1.resolveToolPath)(pathArg, context.workingDirectory);
228
+ let before;
229
+ try {
230
+ before = await (0, promises_1.readFile)(absolutePath, "utf8");
231
+ }
232
+ catch (error) {
233
+ return {
234
+ callId: call.id,
235
+ tool: call.name,
236
+ status: "error",
237
+ output: `edit_file failed: ${toErrorMessage(error)}`,
238
+ metadata: { path: absolutePath }
239
+ };
240
+ }
241
+ if (!before.includes(oldText)) {
242
+ return {
243
+ callId: call.id,
244
+ tool: call.name,
245
+ status: "error",
246
+ output: "edit_file failed: 'oldText' was not found in file.",
247
+ metadata: { path: absolutePath }
248
+ };
249
+ }
250
+ const after = replaceAll ? before.split(oldText).join(newText) : before.replace(oldText, newText);
251
+ try {
252
+ await (0, promises_1.writeFile)(absolutePath, after, "utf8");
253
+ const diffPreview = buildDiffPreview(before, after);
254
+ const hookResult = await runFileWriteHook(context, {
255
+ operation: "edit_file",
256
+ path: absolutePath,
257
+ replaced: replaceAll ? "all" : "first",
258
+ bytesAfter: after.length
259
+ });
260
+ const hookWarning = hookResult && hookResult.status !== "ok" && hookResult.status !== "skipped"
261
+ ? `\n${(0, hooks_1.formatHookFailure)(hookResult)}`
262
+ : "";
263
+ return {
264
+ callId: call.id,
265
+ tool: call.name,
266
+ status: "ok",
267
+ output: `Edited ${absolutePath}\n${diffPreview}${hookWarning}`,
268
+ metadata: {
269
+ path: absolutePath,
270
+ replaceAll,
271
+ diffPreview,
272
+ hook: hookResult ? summarizeHookResult(hookResult) : undefined
273
+ }
274
+ };
275
+ }
276
+ catch (error) {
277
+ return {
278
+ callId: call.id,
279
+ tool: call.name,
280
+ status: "error",
281
+ output: `edit_file failed: ${toErrorMessage(error)}`,
282
+ metadata: { path: absolutePath }
283
+ };
284
+ }
285
+ }
286
+ async function executeListDir(call, context) {
287
+ const pathArg = normalizeString(call.arguments["path"]) ?? ".";
288
+ const absolutePath = (0, tool_safety_1.resolveToolPath)(pathArg, context.workingDirectory);
289
+ try {
290
+ const entries = await (0, promises_1.readdir)(absolutePath, { withFileTypes: true });
291
+ const lines = entries
292
+ .map((entry) => `${entry.isDirectory() ? "dir " : "file"} ${entry.name}`)
293
+ .sort((a, b) => a.localeCompare(b));
294
+ return {
295
+ callId: call.id,
296
+ tool: call.name,
297
+ status: "ok",
298
+ output: lines.join("\n"),
299
+ metadata: { path: absolutePath, count: lines.length }
300
+ };
301
+ }
302
+ catch (error) {
303
+ return {
304
+ callId: call.id,
305
+ tool: call.name,
306
+ status: "error",
307
+ output: `list_dir failed: ${toErrorMessage(error)}`,
308
+ metadata: { path: absolutePath }
309
+ };
310
+ }
311
+ }
312
+ async function executeGrep(call, context) {
313
+ const pattern = normalizeString(call.arguments["pattern"]);
314
+ if (!pattern) {
315
+ return {
316
+ callId: call.id,
317
+ tool: call.name,
318
+ status: "error",
319
+ output: "grep requires a non-empty 'pattern' argument."
320
+ };
321
+ }
322
+ const pathArg = normalizeString(call.arguments["path"]) ?? ".";
323
+ const absolutePath = (0, tool_safety_1.resolveToolPath)(pathArg, context.workingDirectory);
324
+ const maxMatches = normalizePositiveInt(call.arguments["maxMatches"], 200, 2_000);
325
+ try {
326
+ const { stdout } = await execFileAsync("rg", [
327
+ "--line-number",
328
+ "--color",
329
+ "never",
330
+ "--max-count",
331
+ String(maxMatches),
332
+ pattern,
333
+ absolutePath
334
+ ]);
335
+ return {
336
+ callId: call.id,
337
+ tool: call.name,
338
+ status: "ok",
339
+ output: stdout.trim(),
340
+ metadata: { path: absolutePath, maxMatches }
341
+ };
342
+ }
343
+ catch (error) {
344
+ return {
345
+ callId: call.id,
346
+ tool: call.name,
347
+ status: "error",
348
+ output: `grep failed: ${toErrorMessage(error)}`,
349
+ metadata: { path: absolutePath, maxMatches }
350
+ };
351
+ }
352
+ }
353
+ async function executeRunCommand(call, context) {
354
+ const command = normalizeString(call.arguments["command"]);
355
+ if (!command) {
356
+ return {
357
+ callId: call.id,
358
+ tool: call.name,
359
+ status: "error",
360
+ output: "run_command requires a non-empty 'command' argument."
361
+ };
362
+ }
363
+ const cwdArg = normalizeString(call.arguments["cwd"]) ?? ".";
364
+ const cwd = (0, node_path_1.resolve)((0, tool_safety_1.resolveToolPath)(cwdArg, context.workingDirectory));
365
+ const timeoutMs = normalizePositiveInt(call.arguments["timeoutMs"], 60_000, 120_000);
366
+ try {
367
+ const result = await context.vtSession.runCommand(command, { cwd, timeoutMs });
368
+ return {
369
+ callId: call.id,
370
+ tool: call.name,
371
+ status: result.exitCode === 0 ? "ok" : result.timedOut ? "timeout" : "error",
372
+ output: result.output,
373
+ metadata: {
374
+ exitCode: result.exitCode,
375
+ timedOut: result.timedOut,
376
+ cwd
377
+ }
378
+ };
379
+ }
380
+ catch (error) {
381
+ return {
382
+ callId: call.id,
383
+ tool: call.name,
384
+ status: "error",
385
+ output: `run_command failed: ${toErrorMessage(error)}`,
386
+ metadata: { cwd }
387
+ };
388
+ }
389
+ }
390
+ async function executeToolCall(call, context) {
391
+ if (call.name === "read_file") {
392
+ return await executeReadFile(call, context);
393
+ }
394
+ if (call.name === "write_file") {
395
+ return await executeWriteFile(call, context);
396
+ }
397
+ if (call.name === "edit_file") {
398
+ return await executeEditFile(call, context);
399
+ }
400
+ if (call.name === "list_dir") {
401
+ return await executeListDir(call, context);
402
+ }
403
+ if (call.name === "grep") {
404
+ return await executeGrep(call, context);
405
+ }
406
+ if (call.name === "run_command") {
407
+ return await executeRunCommand(call, context);
408
+ }
409
+ return {
410
+ callId: call.id,
411
+ tool: call.name,
412
+ status: "error",
413
+ output: `Unsupported tool: ${call.name}`
414
+ };
415
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseToolProtocol = parseToolProtocol;
4
+ const TOOL_BLOCK_REGEX = /```yips-tools\s*\n([\s\S]*?)```/u;
5
+ const ALLOWED_TOOLS = new Set([
6
+ "read_file",
7
+ "write_file",
8
+ "edit_file",
9
+ "list_dir",
10
+ "grep",
11
+ "run_command"
12
+ ]);
13
+ function isRecord(value) {
14
+ return typeof value === "object" && value !== null;
15
+ }
16
+ function normalizeToolCall(value) {
17
+ if (!isRecord(value)) {
18
+ return null;
19
+ }
20
+ const id = value["id"];
21
+ const name = value["name"];
22
+ const args = value["arguments"];
23
+ if (typeof id !== "string" || id.trim().length === 0) {
24
+ return null;
25
+ }
26
+ if (typeof name !== "string" || !ALLOWED_TOOLS.has(name)) {
27
+ return null;
28
+ }
29
+ if (!isRecord(args)) {
30
+ return null;
31
+ }
32
+ return {
33
+ id: id.trim(),
34
+ name: name,
35
+ arguments: args
36
+ };
37
+ }
38
+ function normalizeSubagentCall(value) {
39
+ if (!isRecord(value)) {
40
+ return null;
41
+ }
42
+ const id = value["id"];
43
+ const task = value["task"];
44
+ if (typeof id !== "string" || id.trim().length === 0) {
45
+ return null;
46
+ }
47
+ if (typeof task !== "string" || task.trim().length === 0) {
48
+ return null;
49
+ }
50
+ const contextRaw = value["context"];
51
+ const context = typeof contextRaw === "string" && contextRaw.trim().length > 0 ? contextRaw : undefined;
52
+ const allowedToolsRaw = value["allowed_tools"];
53
+ const allowedTools = Array.isArray(allowedToolsRaw)
54
+ ? allowedToolsRaw
55
+ .filter((item) => typeof item === "string" && ALLOWED_TOOLS.has(item))
56
+ .map((item) => item)
57
+ : undefined;
58
+ const maxRoundsRaw = value["max_rounds"];
59
+ const maxRounds = typeof maxRoundsRaw === "number" && Number.isInteger(maxRoundsRaw) && maxRoundsRaw > 0
60
+ ? Math.min(maxRoundsRaw, 6)
61
+ : undefined;
62
+ return {
63
+ id: id.trim(),
64
+ task: task.trim(),
65
+ context,
66
+ allowedTools,
67
+ maxRounds
68
+ };
69
+ }
70
+ function parseToolProtocol(input) {
71
+ const match = input.match(TOOL_BLOCK_REGEX);
72
+ if (!match) {
73
+ return {
74
+ assistantText: input,
75
+ toolCalls: [],
76
+ subagentCalls: []
77
+ };
78
+ }
79
+ const jsonBody = (match[1] ?? "").trim();
80
+ if (jsonBody.length === 0) {
81
+ return {
82
+ assistantText: input,
83
+ toolCalls: [],
84
+ subagentCalls: []
85
+ };
86
+ }
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(jsonBody);
90
+ }
91
+ catch {
92
+ return {
93
+ assistantText: input,
94
+ toolCalls: [],
95
+ subagentCalls: []
96
+ };
97
+ }
98
+ if (!isRecord(parsed)) {
99
+ return {
100
+ assistantText: input,
101
+ toolCalls: [],
102
+ subagentCalls: []
103
+ };
104
+ }
105
+ const normalizedTools = Array.isArray(parsed["tool_calls"])
106
+ ? parsed["tool_calls"]
107
+ .map((item) => normalizeToolCall(item))
108
+ .filter((call) => call !== null)
109
+ : [];
110
+ const normalizedSubagents = Array.isArray(parsed["subagent_calls"])
111
+ ? parsed["subagent_calls"]
112
+ .map((item) => normalizeSubagentCall(item))
113
+ .filter((call) => call !== null)
114
+ : [];
115
+ const assistantText = input.replace(match[0], "").trim();
116
+ return {
117
+ assistantText,
118
+ toolCalls: normalizedTools,
119
+ subagentCalls: normalizedSubagents
120
+ };
121
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveToolPath = resolveToolPath;
4
+ exports.isWithinWorkingZone = isWithinWorkingZone;
5
+ exports.assessCommandRisk = assessCommandRisk;
6
+ exports.assessPathRisk = assessPathRisk;
7
+ const node_path_1 = require("node:path");
8
+ const DESTRUCTIVE_COMMAND_PATTERNS = [
9
+ /(^|\s)rm\s+-rf(\s|$)/u,
10
+ /(^|\s)rm\s+-fr(\s|$)/u,
11
+ /(^|\s)mkfs(\.|\s|$)/u,
12
+ /(^|\s)dd\s+if=/u,
13
+ /(^|\s)reboot(\s|$)/u,
14
+ /(^|\s)shutdown(\s|$)/u,
15
+ /(^|\s)poweroff(\s|$)/u,
16
+ /(^|\s)halt(\s|$)/u
17
+ ];
18
+ function normalizeBase(base) {
19
+ return (0, node_path_1.resolve)(base);
20
+ }
21
+ function resolveToolPath(path, workingZone) {
22
+ const trimmed = path.trim();
23
+ if (trimmed.length === 0) {
24
+ return normalizeBase(workingZone);
25
+ }
26
+ return (0, node_path_1.resolve)(normalizeBase(workingZone), trimmed);
27
+ }
28
+ function isWithinWorkingZone(path, workingZone) {
29
+ const absolutePath = (0, node_path_1.resolve)(path);
30
+ const absoluteZone = normalizeBase(workingZone);
31
+ const rel = (0, node_path_1.relative)(absoluteZone, absolutePath);
32
+ return rel === "" || (!rel.startsWith("..") && rel !== ".." && !rel.startsWith("../"));
33
+ }
34
+ function assessCommandRisk(command, cwd, workingZone) {
35
+ const destructive = DESTRUCTIVE_COMMAND_PATTERNS.some((pattern) => pattern.test(command));
36
+ const resolvedCwd = resolveToolPath(cwd, workingZone);
37
+ const outOfZone = !isWithinWorkingZone(resolvedCwd, workingZone);
38
+ return {
39
+ destructive,
40
+ outOfZone,
41
+ requiresConfirmation: destructive || outOfZone
42
+ };
43
+ }
44
+ function assessPathRisk(path, workingZone) {
45
+ const resolved = resolveToolPath(path, workingZone);
46
+ const outOfZone = !isWithinWorkingZone(resolved, workingZone);
47
+ return {
48
+ destructive: false,
49
+ outOfZone,
50
+ requiresConfirmation: outOfZone
51
+ };
52
+ }