@garuda-agent/garuda 0.0.1 → 0.15.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 (66) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +4 -0
  4. package/README.md +1097 -0
  5. package/SECURITY.md +32 -0
  6. package/dist/acpCommand-FZ2P5JFO.js +153 -0
  7. package/dist/anthropic-NQDBCHJU.js +327 -0
  8. package/dist/chunk-23FXBNBB.js +46 -0
  9. package/dist/chunk-2S62ZNTC.js +320 -0
  10. package/dist/chunk-2TAXSFWN.js +49 -0
  11. package/dist/chunk-36GQIVGT.js +526 -0
  12. package/dist/chunk-3CEPHS4C.js +231 -0
  13. package/dist/chunk-3NJNVZTK.js +123 -0
  14. package/dist/chunk-42DSA4EA.js +116 -0
  15. package/dist/chunk-4JSUUDFQ.js +118 -0
  16. package/dist/chunk-4P26OKTP.js +100 -0
  17. package/dist/chunk-5DEWP3YX.js +139 -0
  18. package/dist/chunk-A2QAIRQL.js +45 -0
  19. package/dist/chunk-ATW7BNIG.js +31 -0
  20. package/dist/chunk-B25VL4RU.js +242 -0
  21. package/dist/chunk-EPKWDCV4.js +9 -0
  22. package/dist/chunk-EU7PA45O.js +324 -0
  23. package/dist/chunk-FARS4R67.js +206 -0
  24. package/dist/chunk-G2AX7MGS.js +36 -0
  25. package/dist/chunk-HRGX53VC.js +173 -0
  26. package/dist/chunk-HUIK3WXO.js +234 -0
  27. package/dist/chunk-INRZR772.js +73 -0
  28. package/dist/chunk-K3SIUOAG.js +138 -0
  29. package/dist/chunk-K7KK7U44.js +9358 -0
  30. package/dist/chunk-KKB5W47S.js +231 -0
  31. package/dist/chunk-N4I6YS3E.js +73 -0
  32. package/dist/chunk-NXJ4XLB5.js +19 -0
  33. package/dist/chunk-OUCV3FCT.js +205 -0
  34. package/dist/chunk-PQNT34LY.js +1247 -0
  35. package/dist/chunk-PZS3V6P3.js +46 -0
  36. package/dist/chunk-S77XDSD4.js +157 -0
  37. package/dist/chunk-SI2QJYCR.js +1341 -0
  38. package/dist/chunk-WMOLXTBU.js +61 -0
  39. package/dist/chunk-YFQJXRIJ.js +193 -0
  40. package/dist/chunk-YQTGMCU6.js +111 -0
  41. package/dist/chunk-ZAE25JM2.js +158 -0
  42. package/dist/cli/index.js +1203 -0
  43. package/dist/create-N4I7TIZE.js +223 -0
  44. package/dist/deadline-F5RJJ2QW.js +86 -0
  45. package/dist/detect-LARDSLLZ.js +10 -0
  46. package/dist/evalCommand-3SSFMZQD.js +2798 -0
  47. package/dist/export-HN3CT7AV.js +158 -0
  48. package/dist/inkChat-34BTFG2K.js +1193 -0
  49. package/dist/install-VRPBZY5H.js +17 -0
  50. package/dist/job-3NYDMP2M.js +31 -0
  51. package/dist/jobCommand-XJ5GULS5.js +30 -0
  52. package/dist/launchd-QVHQ3VNN.js +40 -0
  53. package/dist/lspCommand-XNTBHXLA.js +99 -0
  54. package/dist/manager-4PMSXT47.js +14 -0
  55. package/dist/manager-CSI76AWF.js +847 -0
  56. package/dist/nightCommand-CR6ZHYGP.js +247 -0
  57. package/dist/oauth-W6OK6XXO.js +23 -0
  58. package/dist/openaiCompatible-CJWVJP5M.js +501 -0
  59. package/dist/policy-EJPLEEXP.js +31 -0
  60. package/dist/proxy-XJAHCZB2.js +192 -0
  61. package/dist/remove-LE7Q7K46.js +59 -0
  62. package/dist/run-ML6GSPN4.js +888 -0
  63. package/dist/server-R3GNIC33.js +676 -0
  64. package/dist/servers-6YDGWOZ6.js +24 -0
  65. package/dist/text-J2GD647J.js +11 -0
  66. package/package.json +84 -3
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ HIDDEN_WARNING,
4
+ hasHidden,
5
+ headerText,
6
+ visible
7
+ } from "./chunk-4JSUUDFQ.js";
8
+ import {
9
+ BUILTIN_COMMANDS
10
+ } from "./chunk-23FXBNBB.js";
11
+
12
+ // src/acp/server.ts
13
+ import { randomUUID } from "crypto";
14
+ import { statSync } from "fs";
15
+ import { isAbsolute as isAbsolute2 } from "path";
16
+ import {
17
+ agent,
18
+ methods,
19
+ ndJsonStream,
20
+ PROTOCOL_VERSION,
21
+ RequestError
22
+ } from "@agentclientprotocol/sdk";
23
+
24
+ // src/acp/toolCalls.ts
25
+ import { resolve } from "path";
26
+ var TITLE_MAX = 200;
27
+ var KINDS = {
28
+ read_file: "read",
29
+ skill: "read",
30
+ glob: "search",
31
+ grep: "search",
32
+ find_symbol: "search",
33
+ find_references: "search",
34
+ find_callers: "search",
35
+ repo_map: "search",
36
+ ast_query: "search",
37
+ impact_analysis: "search",
38
+ write_file: "edit",
39
+ edit_file: "edit",
40
+ remember: "edit",
41
+ bash: "execute",
42
+ process_manager: "execute",
43
+ web_fetch: "fetch",
44
+ web_search: "fetch",
45
+ explore: "think",
46
+ agent: "think",
47
+ delegate_expert: "think",
48
+ todo_write: "think"
49
+ };
50
+ function oneLine(text, max = TITLE_MAX) {
51
+ const line = visible(text.replace(/\r?\n/g, " \u21B5 "));
52
+ return line.length <= max ? line : `${line.slice(0, max - 1)}\u2026`;
53
+ }
54
+ function viewOf(name, input, root) {
55
+ const args = typeof input === "object" && input !== null ? input : {};
56
+ const text = (key) => typeof args[key] === "string" ? args[key] : void 0;
57
+ const path = text("path");
58
+ const kind = KINDS[name] ?? "other";
59
+ const locations = path === void 0 || !["read", "edit", "search"].includes(kind) ? [] : [
60
+ {
61
+ path: resolve(root, path),
62
+ ...typeof args.offset === "number" ? { line: args.offset } : {}
63
+ }
64
+ ];
65
+ return { title: oneLine(titleOf(name, text, path)), kind, locations };
66
+ }
67
+ function titleOf(name, text, path) {
68
+ switch (name) {
69
+ case "read_file":
70
+ return `Read ${path ?? ""}`;
71
+ case "write_file":
72
+ return `Write ${path ?? ""}`;
73
+ case "edit_file":
74
+ return `Edit ${path ?? ""}`;
75
+ case "glob":
76
+ return `Find files ${text("pattern") ?? ""}`;
77
+ case "grep":
78
+ return `Search ${text("pattern") ?? ""}${path === void 0 ? "" : ` in ${path}`}`;
79
+ case "bash":
80
+ return `$ ${text("command") ?? ""}`;
81
+ case "web_fetch":
82
+ return `Fetch ${text("url") ?? ""}`;
83
+ case "web_search":
84
+ return `Search the web: ${text("query") ?? ""}`;
85
+ case "explore":
86
+ return "Subagent: explore";
87
+ case "agent":
88
+ return `Subagent: ${text("agent") ?? "agent"}`;
89
+ case "delegate_expert":
90
+ return `Subagent: ${text("language") ?? "expert"} expert`;
91
+ case "todo_write":
92
+ return "Update the plan";
93
+ case "remember":
94
+ return "Remember a note";
95
+ case "skill":
96
+ return `Skill: ${text("name") ?? ""}`;
97
+ default: {
98
+ const mcp = /^mcp__(.+?)__(.+)$/.exec(name);
99
+ if (mcp !== null) return `${mcp[1]}/${mcp[2]}`;
100
+ const symbol = text("symbol") ?? text("name") ?? text("query");
101
+ return symbol === void 0 ? name : `${name} ${symbol}`;
102
+ }
103
+ }
104
+ }
105
+
106
+ // src/acp/updates.ts
107
+ var RESULT_MAX_CHARS = 5e4;
108
+ function textContent(text) {
109
+ return { type: "content", content: { type: "text", text } };
110
+ }
111
+ var SessionChannel = class {
112
+ constructor(sessionId) {
113
+ this.sessionId = sessionId;
114
+ }
115
+ sessionId;
116
+ client;
117
+ chain = Promise.resolve();
118
+ waiting = [];
119
+ stopped = false;
120
+ stoppedCalls = [];
121
+ /** The prompt starts: send to this client, and send the notices that waited. */
122
+ open(client) {
123
+ this.client = client;
124
+ this.stopped = false;
125
+ this.stoppedCalls.length = 0;
126
+ for (const text of this.waiting.splice(0)) this.message(text);
127
+ }
128
+ /** The editor stopped the running prompt: tool calls that end from now on were cancelled. */
129
+ cancel() {
130
+ this.stopped = true;
131
+ }
132
+ get cancelled() {
133
+ return this.stopped;
134
+ }
135
+ /** A tool call that the stop ended (its title), for the message at the end of the turn. */
136
+ noteCancelled(title) {
137
+ this.stoppedCalls.push(title);
138
+ }
139
+ /** The titles of the tool calls that the stop ended, once. */
140
+ takeCancelled() {
141
+ return this.stoppedCalls.splice(0);
142
+ }
143
+ /** The prompt ends: wait for every update, then stop sending. */
144
+ async close() {
145
+ await this.chain;
146
+ this.client = void 0;
147
+ }
148
+ get current() {
149
+ return this.client;
150
+ }
151
+ update(update) {
152
+ const client = this.client;
153
+ if (client === void 0) return;
154
+ this.chain = this.chain.then(() => client.notify("session/update", { sessionId: this.sessionId, update })).catch((error) => {
155
+ process.stderr.write(`garuda acp: session/update failed: ${String(error)}
156
+ `);
157
+ });
158
+ }
159
+ /** Every update so far has gone out. */
160
+ flush() {
161
+ return this.chain;
162
+ }
163
+ /** A line from Garuda (not the model), as message text. Kept for the next prompt if none runs. */
164
+ notice(text) {
165
+ if (this.client === void 0) this.waiting.push(text);
166
+ else this.message(text);
167
+ }
168
+ message(text) {
169
+ this.update({
170
+ sessionUpdate: "agent_message_chunk",
171
+ content: { type: "text", text: `
172
+
173
+ Garuda: ${text}
174
+
175
+ ` }
176
+ });
177
+ }
178
+ };
179
+ var KnownCalls = class {
180
+ titles = /* @__PURE__ */ new Map();
181
+ add(id, title) {
182
+ this.titles.set(id, title);
183
+ }
184
+ title(id) {
185
+ return this.titles.get(id);
186
+ }
187
+ };
188
+ var SETTLE_MS = 3e3;
189
+ var EventMapper = class {
190
+ constructor(channel, calls, root) {
191
+ this.channel = channel;
192
+ this.calls = calls;
193
+ this.root = root;
194
+ }
195
+ channel;
196
+ calls;
197
+ root;
198
+ /** Tool calls that started and have no result yet. */
199
+ open = /* @__PURE__ */ new Set();
200
+ changed = () => {
201
+ };
202
+ event(event) {
203
+ const send = (update) => this.channel.update(update);
204
+ switch (event.type) {
205
+ case "text_delta":
206
+ send({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: event.text } });
207
+ break;
208
+ case "thinking_delta":
209
+ send({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text: event.text } });
210
+ break;
211
+ case "tool_call": {
212
+ const view = viewOf(event.call.name, event.call.input, this.root);
213
+ this.calls.add(event.call.id, view.title);
214
+ this.open.add(event.call.id);
215
+ send({
216
+ sessionUpdate: "tool_call",
217
+ toolCallId: event.call.id,
218
+ title: view.title,
219
+ kind: view.kind,
220
+ status: "pending",
221
+ locations: view.locations,
222
+ rawInput: event.call.input
223
+ });
224
+ break;
225
+ }
226
+ case "tool_progress":
227
+ send({
228
+ sessionUpdate: "tool_call_update",
229
+ toolCallId: event.call.id,
230
+ status: "in_progress",
231
+ content: [textContent(oneLine(event.text))]
232
+ });
233
+ break;
234
+ case "tool_result": {
235
+ if (!this.open.delete(event.call.id)) break;
236
+ this.changed();
237
+ const stopped = this.channel.cancelled;
238
+ if (stopped) {
239
+ this.channel.noteCancelled(this.calls.title(event.call.id) ?? event.call.name);
240
+ }
241
+ send({
242
+ sessionUpdate: "tool_call_update",
243
+ toolCallId: event.call.id,
244
+ status: stopped || event.outcome.isError ? "failed" : "completed",
245
+ content: [textContent(cut(event.outcome.content))]
246
+ });
247
+ break;
248
+ }
249
+ case "server_tool": {
250
+ const input = event.call.input;
251
+ const query = typeof input?.query === "string" ? input.query : "";
252
+ const title = oneLine(`Search the web: ${query}`);
253
+ this.calls.add(event.call.id, title);
254
+ send({
255
+ sessionUpdate: "tool_call",
256
+ toolCallId: event.call.id,
257
+ title,
258
+ kind: "fetch",
259
+ status: event.result === void 0 ? "in_progress" : "completed"
260
+ });
261
+ if (event.result !== void 0) {
262
+ const lines = event.result.results.map((r) => `${r.title} ${r.url}`);
263
+ send({
264
+ sessionUpdate: "tool_call_update",
265
+ toolCallId: event.call.id,
266
+ status: event.result.error === void 0 ? "completed" : "failed",
267
+ content: [textContent(cut(event.result.error ?? lines.join("\n")))]
268
+ });
269
+ }
270
+ break;
271
+ }
272
+ case "notice":
273
+ this.channel.notice(event.text);
274
+ break;
275
+ case "compaction":
276
+ this.channel.notice("the conversation was summarised to fit the context window.");
277
+ break;
278
+ case "model_retry":
279
+ this.channel.notice(
280
+ `the model connection broke (${oneLine(event.reason)}). Retry ${event.attempt} of ${event.maxRetries}: the answer starts again below.`
281
+ );
282
+ break;
283
+ case "step_end":
284
+ break;
285
+ }
286
+ }
287
+ /**
288
+ * After a stop: wait (at most `ms`) until every running tool call reported its result. A call
289
+ * that has not reported by then ends here as failed and counts as cancelled, so no call stays
290
+ * "running" in the editor.
291
+ */
292
+ async settle(ms = SETTLE_MS) {
293
+ if (this.open.size > 0) {
294
+ await new Promise((resolve2) => {
295
+ const timer = setTimeout(resolve2, ms);
296
+ this.changed = () => {
297
+ if (this.open.size > 0) return;
298
+ clearTimeout(timer);
299
+ resolve2();
300
+ };
301
+ });
302
+ this.changed = () => {
303
+ };
304
+ }
305
+ for (const id of this.open) {
306
+ this.channel.noteCancelled(this.calls.title(id) ?? id);
307
+ this.channel.update({
308
+ sessionUpdate: "tool_call_update",
309
+ toolCallId: id,
310
+ status: "failed",
311
+ content: [textContent("Cancelled.")]
312
+ });
313
+ }
314
+ this.open.clear();
315
+ }
316
+ };
317
+ function cut(text) {
318
+ return text.length <= RESULT_MAX_CHARS ? text : `${text.slice(0, RESULT_MAX_CHARS)}
319
+ [Garuda: cut at ${RESULT_MAX_CHARS} characters for the editor; the model has all of it]`;
320
+ }
321
+
322
+ // src/acp/approver.ts
323
+ var DIFF_MAX_CHARS = 1e6;
324
+ var DEFAULT_CHOICES = ["once", "session", "deny"];
325
+ var KINDS2 = {
326
+ once: "allow_once",
327
+ session: "allow_always",
328
+ deny: "reject_once"
329
+ };
330
+ var NAMES = {
331
+ once: "Allow once",
332
+ // "Always" in Garuda is this session only (session rules), as in the terminal.
333
+ session: "Allow for this session",
334
+ deny: "Deny"
335
+ };
336
+ var AcpApprover = class {
337
+ constructor(channel, calls) {
338
+ this.channel = channel;
339
+ this.calls = calls;
340
+ }
341
+ channel;
342
+ calls;
343
+ questions = 0;
344
+ async ask(request, signal) {
345
+ const client = this.channel.current;
346
+ if (client === void 0 || signal.aborted) return "deny";
347
+ const known = request.callId === void 0 ? void 0 : this.calls.title(request.callId);
348
+ const toolCallId = known !== void 0 && request.callId !== void 0 ? request.callId : `garuda-question-${++this.questions}`;
349
+ const title = oneLine(request.title ?? known ?? headerText(request).replace(/:$/, ""));
350
+ if (known === void 0) {
351
+ this.channel.update({
352
+ sessionUpdate: "tool_call",
353
+ toolCallId,
354
+ title,
355
+ kind: "other",
356
+ status: "pending"
357
+ });
358
+ }
359
+ await this.channel.flush();
360
+ const choices = request.choices ?? DEFAULT_CHOICES;
361
+ const options = choices.map((choice) => ({
362
+ optionId: choice,
363
+ name: request.labels?.[choice] ?? NAMES[choice],
364
+ kind: KINDS2[choice]
365
+ }));
366
+ const answer = client.request("session/request_permission", {
367
+ sessionId: this.channel.sessionId,
368
+ toolCall: { toolCallId, title, status: "pending", content: questionContent(request) },
369
+ options
370
+ }).catch(() => void 0);
371
+ let onAbort;
372
+ const aborted = new Promise((resolve2) => {
373
+ onAbort = () => resolve2(void 0);
374
+ signal.addEventListener("abort", onAbort, { once: true });
375
+ });
376
+ try {
377
+ const response = await Promise.race([answer, aborted]);
378
+ if (response === void 0 || response.outcome.outcome !== "selected") return "deny";
379
+ const picked = response.outcome.optionId;
380
+ return choices.find((c) => c === picked) ?? "deny";
381
+ } finally {
382
+ if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
383
+ }
384
+ }
385
+ };
386
+ function questionContent(request) {
387
+ const content = [];
388
+ const change = request.change;
389
+ if (change !== void 0 && (change.oldText?.length ?? 0) + change.newText.length <= DIFF_MAX_CHARS) {
390
+ content.push({
391
+ type: "diff",
392
+ path: change.path,
393
+ oldText: change.oldText,
394
+ newText: change.newText
395
+ });
396
+ }
397
+ const lines = [headerText(request)];
398
+ if (request.question !== void 0) lines.push(visible(request.question));
399
+ const preview = request.target.kind === "command" ? `$ ${request.preview}` : request.preview;
400
+ lines.push(visible(preview));
401
+ if (hasHidden(request.preview)) lines.push(HIDDEN_WARNING);
402
+ content.push(textContent(lines.join("\n")));
403
+ return content;
404
+ }
405
+
406
+ // src/acp/prompt.ts
407
+ import { isAbsolute, relative, sep } from "path";
408
+ import { fileURLToPath } from "url";
409
+ var EMBEDDED_MAX_CHARS = 1e5;
410
+ function promptText(blocks, root) {
411
+ const parts = [];
412
+ for (const block of blocks) {
413
+ switch (block.type) {
414
+ case "text":
415
+ parts.push(block.text);
416
+ break;
417
+ case "resource_link":
418
+ parts.push(linkText(block.uri, block.name, root));
419
+ break;
420
+ case "resource": {
421
+ const resource = block.resource;
422
+ if ("text" in resource) {
423
+ const text = resource.text.length <= EMBEDDED_MAX_CHARS ? resource.text : `${resource.text.slice(0, EMBEDDED_MAX_CHARS)}
424
+ [Garuda: cut at ${EMBEDDED_MAX_CHARS} characters]`;
425
+ parts.push(`${resource.uri}:
426
+ ${fence(text)}`);
427
+ } else {
428
+ parts.push(`[Garuda: ${resource.uri} is binary; it was not attached]`);
429
+ }
430
+ break;
431
+ }
432
+ default:
433
+ parts.push(`[Garuda: a ${block.type} block was not attached; Garuda takes text only]`);
434
+ }
435
+ }
436
+ return parts.join("\n\n");
437
+ }
438
+ function linkText(uri, name, root) {
439
+ let path;
440
+ try {
441
+ path = uri.startsWith("file:") ? fileURLToPath(uri) : void 0;
442
+ } catch {
443
+ path = void 0;
444
+ }
445
+ if (path !== void 0) {
446
+ const inner = relative(root, path);
447
+ const inside = inner !== "" && !inner.startsWith("..") && !isAbsolute(inner);
448
+ if (inside && !/\s/.test(inner)) return `@${inner.split(sep).join("/")}`;
449
+ }
450
+ return `${name} (${uri})`;
451
+ }
452
+ function fence(text) {
453
+ const longest = Math.max(2, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
454
+ const mark = "`".repeat(longest + 1);
455
+ return `${mark}
456
+ ${text}
457
+ ${mark}`;
458
+ }
459
+
460
+ // src/acp/server.ts
461
+ var INTERNAL_ERROR = -32603;
462
+ var MODES = {
463
+ build: { name: "Build", description: "Edits and commands, each one asks first." },
464
+ plan: {
465
+ name: "Plan",
466
+ description: "Reads and plans. No edits; commands cannot write the project."
467
+ }
468
+ };
469
+ var STOP_REASONS = {
470
+ done: "end_turn",
471
+ max_tokens: "max_tokens",
472
+ refusal: "refusal",
473
+ max_steps: "max_turn_requests",
474
+ token_budget: "max_turn_requests",
475
+ repeated_calls: "max_turn_requests"
476
+ };
477
+ function acpServer(options) {
478
+ const sessions = /* @__PURE__ */ new Map();
479
+ const sessionOf = (id) => {
480
+ const session = sessions.get(id);
481
+ if (session === void 0) throw RequestError.resourceNotFound(`session:${id}`);
482
+ return session;
483
+ };
484
+ let connection;
485
+ const app = agent({ name: "garuda" }).onConnect((c) => {
486
+ connection = c.client;
487
+ }).onRequest(
488
+ methods.agent.initialize,
489
+ () => ({
490
+ protocolVersion: PROTOCOL_VERSION,
491
+ agentInfo: { name: "garuda", title: "Garuda", version: options.version },
492
+ agentCapabilities: {
493
+ loadSession: false,
494
+ promptCapabilities: { image: false, audio: false, embeddedContext: true },
495
+ mcpCapabilities: { http: false, sse: false }
496
+ },
497
+ // The keys come from the environment, as in the terminal.
498
+ authMethods: []
499
+ })
500
+ ).onRequest(methods.agent.authenticate, () => ({})).onRequest(methods.agent.session.new, async (c) => {
501
+ const root = c.params.cwd;
502
+ if (!isAbsolute2(root) || !isFolder(root)) {
503
+ throw RequestError.invalidParams(
504
+ void 0,
505
+ `cwd must be an absolute path to a folder: ${root}`
506
+ );
507
+ }
508
+ const id = randomUUID();
509
+ const channel = new SessionChannel(id);
510
+ const calls = new KnownCalls();
511
+ const events = new EventMapper(channel, calls, root);
512
+ let runtime;
513
+ try {
514
+ runtime = await options.createRuntime({
515
+ root,
516
+ approver: new AcpApprover(channel, calls),
517
+ onEvent: (event) => events.event(event),
518
+ onNotice: (text) => channel.notice(text)
519
+ });
520
+ } catch (error) {
521
+ throw new RequestError(INTERNAL_ERROR, error.message);
522
+ }
523
+ if (runtime.executorNotice !== void 0) channel.notice(runtime.executorNotice);
524
+ const skipped = editorServers(c.params.mcpServers);
525
+ if (skipped !== void 0) channel.notice(skipped);
526
+ sessions.set(id, { id, root, runtime, channel, events });
527
+ setImmediate(() => {
528
+ void connection?.notify("session/update", {
529
+ sessionId: id,
530
+ update: {
531
+ sessionUpdate: "available_commands_update",
532
+ availableCommands: commandsOf(runtime)
533
+ }
534
+ }).catch(() => {
535
+ });
536
+ });
537
+ return { sessionId: id, modes: modesOf(runtime.mode) };
538
+ }).onRequest(methods.agent.session.setMode, (c) => {
539
+ const session = sessionOf(c.params.sessionId);
540
+ const mode = c.params.modeId;
541
+ if (mode !== "build" && mode !== "plan") {
542
+ throw RequestError.invalidParams(void 0, `Unknown mode "${mode}": use build or plan.`);
543
+ }
544
+ session.runtime.setMode(mode);
545
+ return {};
546
+ }).onNotification(methods.agent.session.cancel, (c) => {
547
+ const session = sessions.get(c.params.sessionId);
548
+ if (session?.running === void 0) return;
549
+ session.channel.cancel();
550
+ session.running.abort();
551
+ }).onRequest(methods.agent.session.prompt, async (c) => {
552
+ const session = sessionOf(c.params.sessionId);
553
+ if (session.running !== void 0) {
554
+ throw RequestError.invalidRequest(
555
+ void 0,
556
+ "A prompt is already running in this session."
557
+ );
558
+ }
559
+ const controller = new AbortController();
560
+ session.running = controller;
561
+ const onCancel = () => {
562
+ session.channel.cancel();
563
+ controller.abort();
564
+ };
565
+ c.signal.addEventListener("abort", onCancel, { once: true });
566
+ session.channel.open(c.client);
567
+ try {
568
+ return {
569
+ stopReason: await turn(
570
+ session,
571
+ promptText(c.params.prompt, session.root),
572
+ controller.signal
573
+ )
574
+ };
575
+ } finally {
576
+ c.signal.removeEventListener("abort", onCancel);
577
+ session.running = void 0;
578
+ await session.channel.close();
579
+ }
580
+ });
581
+ return {
582
+ app,
583
+ async serve(output, input) {
584
+ await app.connect(ndJsonStream(output, input)).closed;
585
+ },
586
+ shutdown() {
587
+ for (const session of sessions.values()) session.runtime.executor.shutdown();
588
+ },
589
+ async close() {
590
+ for (const session of sessions.values()) {
591
+ session.running?.abort();
592
+ await session.runtime.close().catch(() => {
593
+ });
594
+ session.runtime.executor.shutdown();
595
+ }
596
+ sessions.clear();
597
+ }
598
+ };
599
+ }
600
+ async function turn(session, text, signal) {
601
+ const { runtime, channel, events } = session;
602
+ let prompt = text;
603
+ const command = /^\/([A-Za-z0-9][\w:.-]*)(?:\s|$)/.exec(text.trim());
604
+ if (command !== null) {
605
+ const name = command[1];
606
+ if (BUILTIN_COMMANDS.includes(name)) {
607
+ channel.notice(
608
+ name === "plan" ? "use the editor's mode menu (Plan) for plan mode." : `/${name} is a terminal command; it is not available in editors yet.`
609
+ );
610
+ return "end_turn";
611
+ }
612
+ const resolved = await runtime.resolveCommand(text.trim(), signal);
613
+ if (resolved.kind === "denied") {
614
+ channel.notice(resolved.message);
615
+ return "end_turn";
616
+ }
617
+ if (resolved.kind === "prompt") prompt = resolved.prompt;
618
+ }
619
+ try {
620
+ const result = await runtime.runTurn(prompt, signal);
621
+ return STOP_REASONS[result.stopReason];
622
+ } catch (error) {
623
+ if (signal.aborted) {
624
+ runtime.recordStop("interrupted");
625
+ await events.settle();
626
+ const calls = channel.takeCancelled();
627
+ channel.notice(
628
+ calls.length === 0 ? "the turn was cancelled." : calls.map((title) => `${title} cancelled.`).join("\n")
629
+ );
630
+ return "cancelled";
631
+ }
632
+ runtime.recordStop("error");
633
+ throw new RequestError(INTERNAL_ERROR, error.message);
634
+ }
635
+ }
636
+ function modesOf(current) {
637
+ return {
638
+ currentModeId: current,
639
+ availableModes: Object.keys(MODES).map((id) => ({ id, ...MODES[id] }))
640
+ };
641
+ }
642
+ function commandsOf(runtime) {
643
+ const list = [];
644
+ for (const skill of runtime.skills.filter((s) => s.userInvocable)) {
645
+ list.push({
646
+ name: skill.name,
647
+ description: skill.description,
648
+ ...skill.argumentHint === void 0 ? {} : { input: { hint: skill.argumentHint } }
649
+ });
650
+ }
651
+ for (const command of runtime.commands) {
652
+ if (list.some((c) => c.name === command.name)) continue;
653
+ list.push({
654
+ name: command.name,
655
+ description: command.description ?? `/${command.name} (${command.source} command)`,
656
+ ...command.argumentHint === void 0 ? {} : { input: { hint: command.argumentHint } }
657
+ });
658
+ }
659
+ return list;
660
+ }
661
+ function editorServers(servers) {
662
+ if (servers.length === 0) return void 0;
663
+ const names = servers.map((s) => s.name).join(", ");
664
+ return `the editor's MCP servers are not started in this version (${names}). Garuda's own MCP servers (~/.garuda/mcp.json and the project's) work as in the terminal.`;
665
+ }
666
+ function isFolder(path) {
667
+ try {
668
+ return statSync(path).isDirectory();
669
+ } catch {
670
+ return false;
671
+ }
672
+ }
673
+ export {
674
+ acpServer
675
+ };
676
+ //# sourceMappingURL=server-R3GNIC33.js.map
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ LSP_LANGUAGES,
4
+ MANAGED,
5
+ SERVERS,
6
+ discoverServer,
7
+ findServer,
8
+ isTypeScript7,
9
+ languageOf,
10
+ managedDir,
11
+ managedLabel
12
+ } from "./chunk-EU7PA45O.js";
13
+ export {
14
+ LSP_LANGUAGES,
15
+ MANAGED,
16
+ SERVERS,
17
+ discoverServer,
18
+ findServer,
19
+ isTypeScript7,
20
+ languageOf,
21
+ managedDir,
22
+ managedLabel
23
+ };
24
+ //# sourceMappingURL=servers-6YDGWOZ6.js.map
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ jobPrompt,
4
+ jobReport
5
+ } from "./chunk-YQTGMCU6.js";
6
+ import "./chunk-OUCV3FCT.js";
7
+ export {
8
+ jobPrompt,
9
+ jobReport
10
+ };
11
+ //# sourceMappingURL=text-J2GD647J.js.map