arcie 0.1.4 → 0.1.5

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 (64) hide show
  1. package/dist/agent/index.d.ts +1 -1
  2. package/dist/build-TWNCPXBU.js +82 -0
  3. package/dist/build-TWNCPXBU.js.map +1 -0
  4. package/dist/channels/index.d.ts +1 -1
  5. package/dist/{chunk-2ZSKZYWK.js → chunk-4WSILP75.js} +37 -164
  6. package/dist/chunk-4WSILP75.js.map +1 -0
  7. package/dist/chunk-HVBDOHTL.js +148 -0
  8. package/dist/chunk-HVBDOHTL.js.map +1 -0
  9. package/dist/chunk-HWD2Z2CH.js +742 -0
  10. package/dist/chunk-HWD2Z2CH.js.map +1 -0
  11. package/dist/chunk-KVSX4MLK.js +139 -0
  12. package/dist/chunk-KVSX4MLK.js.map +1 -0
  13. package/dist/chunk-N5R3TEEZ.js +15 -0
  14. package/dist/chunk-N5R3TEEZ.js.map +1 -0
  15. package/dist/chunk-SGWQZZ2A.js +54 -0
  16. package/dist/chunk-SGWQZZ2A.js.map +1 -0
  17. package/dist/chunk-XOFWQUOO.js +30 -0
  18. package/dist/chunk-XOFWQUOO.js.map +1 -0
  19. package/dist/cli/index.d.ts +7 -0
  20. package/dist/cli/index.js +146 -232
  21. package/dist/cli/index.js.map +1 -1
  22. package/dist/context/index.d.ts +1 -1
  23. package/dist/dev-ZZDVOWUZ.js +1967 -0
  24. package/dist/dev-ZZDVOWUZ.js.map +1 -0
  25. package/dist/hooks/index.d.ts +1 -1
  26. package/dist/{index-b4NcDl3m.d.ts → index-CElLRVTP.d.ts} +1 -1
  27. package/dist/index.d.ts +4 -4
  28. package/dist/index.js +4 -2
  29. package/dist/index.js.map +1 -1
  30. package/dist/init-5F7CIVLN.js +822 -0
  31. package/dist/init-5F7CIVLN.js.map +1 -0
  32. package/dist/instructions/index.d.ts +1 -1
  33. package/dist/runner/index.d.ts +2 -2
  34. package/dist/runner/index.js +2 -1
  35. package/dist/scaffold-web-chat-OQL5C2GK.js +7 -0
  36. package/dist/scaffold-web-chat-OQL5C2GK.js.map +1 -0
  37. package/dist/schedules/index.d.ts +1 -1
  38. package/dist/skills/index.d.ts +1 -1
  39. package/dist/tools/index.d.ts +1 -1
  40. package/dist/{types-BxOQTzx1.d.ts → types-DwxwdSa2.d.ts} +1 -1
  41. package/package.json +1 -1
  42. package/templates/default/.env.local +12 -0
  43. package/templates/web-chat/.env.local.example +4 -0
  44. package/templates/web-chat/README.md +51 -0
  45. package/templates/web-chat/app/api/chat/route.ts +43 -0
  46. package/templates/web-chat/app/globals.css +55 -0
  47. package/templates/web-chat/app/layout.tsx +15 -0
  48. package/templates/web-chat/app/page.tsx +9 -0
  49. package/templates/web-chat/components/chat.tsx +196 -0
  50. package/templates/web-chat/components/input-bar.tsx +74 -0
  51. package/templates/web-chat/components/message.tsx +63 -0
  52. package/templates/web-chat/components/tool-call.tsx +79 -0
  53. package/templates/web-chat/components/ui/button.tsx +46 -0
  54. package/templates/web-chat/components/ui/input.tsx +19 -0
  55. package/templates/web-chat/components.json +20 -0
  56. package/templates/web-chat/lib/stream.ts +41 -0
  57. package/templates/web-chat/lib/types.ts +61 -0
  58. package/templates/web-chat/lib/utils.ts +6 -0
  59. package/templates/web-chat/next.config.mjs +4 -0
  60. package/templates/web-chat/package.json +32 -0
  61. package/templates/web-chat/postcss.config.mjs +6 -0
  62. package/templates/web-chat/tailwind.config.ts +59 -0
  63. package/templates/web-chat/tsconfig.json +23 -0
  64. package/dist/chunk-2ZSKZYWK.js.map +0 -1
@@ -0,0 +1,1967 @@
1
+ import {
2
+ EMPTY_LINE,
3
+ LiveRegion,
4
+ PromptHistory,
5
+ applyLineEditorKey,
6
+ attachKeyStream,
7
+ createTheme,
8
+ detectUnicode,
9
+ inputTextWidth,
10
+ sliceVisible,
11
+ stripTerminalControls,
12
+ visibleLength,
13
+ visibleLine,
14
+ wrapVisibleLine
15
+ } from "./chunk-HWD2Z2CH.js";
16
+ import {
17
+ dimmed,
18
+ grey
19
+ } from "./chunk-N5R3TEEZ.js";
20
+ import {
21
+ showHeader
22
+ } from "./chunk-XOFWQUOO.js";
23
+ import {
24
+ streamAgent
25
+ } from "./chunk-4WSILP75.js";
26
+ import {
27
+ loadAgent
28
+ } from "./chunk-KVSX4MLK.js";
29
+ import {
30
+ discoverAgent
31
+ } from "./chunk-6XETPLIF.js";
32
+ import "./chunk-QE46QFID.js";
33
+ import "./chunk-PHTJLJOV.js";
34
+
35
+ // src/cli/dev.ts
36
+ import { createServer } from "http";
37
+ import { resolve } from "path";
38
+
39
+ // src/cli/tui/renderer/start-block-chat.ts
40
+ import { watch } from "fs";
41
+
42
+ // src/cli/tui/prompt-commands.ts
43
+ var PROMPT_COMMAND_DEFINITIONS = [
44
+ {
45
+ name: "help",
46
+ aliases: [],
47
+ description: "Show available commands",
48
+ takesArgument: false,
49
+ build: () => ({ type: "help" })
50
+ },
51
+ {
52
+ name: "new",
53
+ aliases: [],
54
+ description: "Start a fresh session",
55
+ takesArgument: false,
56
+ build: () => ({ type: "new" })
57
+ },
58
+ {
59
+ name: "clear",
60
+ aliases: [],
61
+ description: "Clear the screen",
62
+ takesArgument: false,
63
+ build: () => ({ type: "clear" })
64
+ },
65
+ {
66
+ name: "model",
67
+ aliases: [],
68
+ description: "Configure the agent's model and provider",
69
+ argumentHint: "[provider/model]",
70
+ takesArgument: true,
71
+ build: (argument) => ({ type: "extension", name: "model", argument })
72
+ },
73
+ {
74
+ name: "provider",
75
+ aliases: [],
76
+ description: "Configure provider API keys",
77
+ takesArgument: false,
78
+ build: () => ({ type: "extension", name: "provider", argument: "" })
79
+ },
80
+ {
81
+ name: "channels",
82
+ aliases: [],
83
+ description: "Add chat channels to the agent",
84
+ takesArgument: false,
85
+ build: () => ({ type: "extension", name: "channels", argument: "" })
86
+ },
87
+ {
88
+ name: "loglevel",
89
+ aliases: [],
90
+ description: "Show or hide captured stdout/stderr logs",
91
+ argumentHint: "[all|stderr|none]",
92
+ takesArgument: true,
93
+ build: (argument) => ({ type: "loglevel", argument })
94
+ },
95
+ {
96
+ name: "exit",
97
+ aliases: ["quit"],
98
+ description: "Quit the TUI",
99
+ takesArgument: false,
100
+ build: () => ({ type: "exit" })
101
+ }
102
+ ];
103
+ var PROMPT_COMMANDS = PROMPT_COMMAND_DEFINITIONS;
104
+ function parsePromptCommand(prompt) {
105
+ const trimmed = prompt.trim();
106
+ if (!trimmed.startsWith("/")) return null;
107
+ for (const spec of PROMPT_COMMANDS) {
108
+ for (const alias of [spec.name, ...spec.aliases]) {
109
+ const token = `/${alias}`;
110
+ if (trimmed === token) return spec.build("");
111
+ if (spec.takesArgument && trimmed.startsWith(`${token} `)) {
112
+ return spec.build(trimmed.slice(token.length).trim());
113
+ }
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ function isPromptControlCommand(prompt) {
119
+ return parsePromptCommand(prompt) !== null;
120
+ }
121
+ function formatPromptCommandHelp(commands = PROMPT_COMMANDS) {
122
+ const entries = commands.map((spec) => {
123
+ const hint = spec.argumentHint === void 0 ? "" : ` ${spec.argumentHint}`;
124
+ const aliases = spec.aliases.map((alias) => ` (/${alias})`).join("");
125
+ return { invocation: `/${spec.name}${hint}${aliases}`, description: spec.description };
126
+ });
127
+ const column = Math.max(...entries.map((entry) => entry.invocation.length)) + 2;
128
+ return entries.map((entry) => entry.invocation.padEnd(column) + entry.description).join("\n");
129
+ }
130
+ function matchingCommands(commands, text) {
131
+ if (!text.startsWith("/") || /\s/.test(text)) return [];
132
+ const rest = text.slice(1);
133
+ return commands.filter(
134
+ (spec) => [spec.name, ...spec.aliases].some((token) => token.startsWith(rest))
135
+ );
136
+ }
137
+
138
+ // src/cli/tui/tool-format.ts
139
+ var ELLIPSIS = "\u2026";
140
+ function summarizeToolArgs(input, maxLength = 80) {
141
+ if (input === void 0 || input === null) return "";
142
+ if (typeof input !== "object") return truncate(formatScalar(input), maxLength);
143
+ if (Array.isArray(input)) return truncate(`[${input.length}]`, maxLength);
144
+ const entries = Object.entries(input);
145
+ if (entries.length === 0) return "";
146
+ const parts = [];
147
+ for (const [key, value] of entries) {
148
+ parts.push(`${stripTerminalControls(key)}=${formatInlineValue(value)}`);
149
+ }
150
+ return truncate(parts.join(" "), maxLength);
151
+ }
152
+ function summarizeToolResult(output, maxLength = 80) {
153
+ if (output === void 0 || output === null) return "";
154
+ if (typeof output === "string") {
155
+ const firstLine = output.split("\n").find((line) => line.trim().length > 0) ?? "";
156
+ return truncate(firstLine.trim(), maxLength);
157
+ }
158
+ if (typeof output !== "object") return truncate(formatScalar(output), maxLength);
159
+ if (Array.isArray(output)) {
160
+ return truncate(`${output.length} ${output.length === 1 ? "item" : "items"}`, maxLength);
161
+ }
162
+ const record = output;
163
+ for (const key of ["result", "text", "message", "summary", "value", "output"]) {
164
+ const value = record[key];
165
+ if (value !== void 0 && typeof value !== "object") {
166
+ return truncate(`${formatScalar(value)}`, maxLength);
167
+ }
168
+ }
169
+ const keys = Object.keys(record);
170
+ if (keys.length === 0) return "{}";
171
+ const inline = keys.slice(0, 3).map((key) => `${key}=${formatInlineValue(record[key])}`).join(" ");
172
+ return truncate(keys.length > 3 ? `${inline} ${ELLIPSIS}` : inline, maxLength);
173
+ }
174
+ function formatValuePretty(value) {
175
+ if (typeof value === "string") return stripTerminalControls(value);
176
+ try {
177
+ return stripTerminalControls(JSON.stringify(value, null, 2) ?? String(value));
178
+ } catch {
179
+ return stripTerminalControls(String(value));
180
+ }
181
+ }
182
+ function formatInlineValue(value) {
183
+ if (value === void 0) return "undefined";
184
+ if (value === null) return "null";
185
+ if (typeof value === "string") return `"${truncate(value, 32)}"`;
186
+ if (typeof value === "object") {
187
+ return Array.isArray(value) ? `[${value.length}]` : "{\u2026}";
188
+ }
189
+ return formatScalar(value);
190
+ }
191
+ function formatScalar(value) {
192
+ if (typeof value === "string") return stripTerminalControls(value);
193
+ return stripTerminalControls(String(value));
194
+ }
195
+ function truncate(text, maxLength) {
196
+ const collapsed = stripTerminalControls(text).replace(/\s+/g, " ").trim();
197
+ if (collapsed.length <= maxLength) return collapsed;
198
+ return `${collapsed.slice(0, Math.max(0, maxLength - 1)).trimEnd()}${ELLIPSIS}`;
199
+ }
200
+
201
+ // src/cli/tui/renderer/event-to-blocks.ts
202
+ var EventTranslator = class {
203
+ #autoCollapseReasoning;
204
+ #nextId = 0;
205
+ #currentAssistantId;
206
+ #currentReasoningId;
207
+ #toolIds = /* @__PURE__ */ new Map();
208
+ #subagentStack = [];
209
+ constructor(options = {}) {
210
+ this.#autoCollapseReasoning = options.autoCollapseReasoning ?? true;
211
+ }
212
+ get depth() {
213
+ return this.#subagentStack.length;
214
+ }
215
+ reset() {
216
+ const ops = [];
217
+ if (this.#currentAssistantId !== void 0) {
218
+ ops.push({ type: "end-live", id: this.#currentAssistantId });
219
+ }
220
+ if (this.#currentReasoningId !== void 0) {
221
+ ops.push({ type: "end-live", id: this.#currentReasoningId });
222
+ }
223
+ for (const [, id] of this.#toolIds) ops.push({ type: "end-live", id });
224
+ for (const id of this.#subagentStack) ops.push({ type: "end-live", id });
225
+ this.#currentAssistantId = void 0;
226
+ this.#currentReasoningId = void 0;
227
+ this.#toolIds.clear();
228
+ this.#subagentStack = [];
229
+ return ops;
230
+ }
231
+ feed(event) {
232
+ switch (event.type) {
233
+ case "session.started":
234
+ case "session.waiting":
235
+ case "session.completed":
236
+ case "turn.started":
237
+ case "turn.completed":
238
+ case "step.started":
239
+ case "step.completed":
240
+ return [];
241
+ case "message.received":
242
+ return this.#onUserMessage(event.data.message);
243
+ case "message.appended":
244
+ return this.#onAssistantDelta(event.data.delta);
245
+ case "message.completed":
246
+ return this.#endAssistant(event.data.text ?? void 0);
247
+ case "reasoning.appended":
248
+ return this.#onReasoningDelta(event.data.delta);
249
+ case "reasoning.completed":
250
+ return this.#endReasoning(event.data.text);
251
+ case "tool.started":
252
+ return this.#onToolStarted(event.data.name, event.data.callId, event.data.input);
253
+ case "tool.completed":
254
+ return this.#onToolCompleted(
255
+ event.data.callId,
256
+ event.data.status,
257
+ event.data.output,
258
+ event.data.error?.message,
259
+ event.data.error?.code
260
+ );
261
+ case "subagent.called":
262
+ return this.#onSubagentCalled(event.data.name, event.data.callId);
263
+ case "subagent.completed":
264
+ return this.#onSubagentCompleted(event.data.callId, event.data.output);
265
+ case "step.failed":
266
+ case "turn.failed":
267
+ case "session.failed":
268
+ return this.#onFailure(event.data.code, event.data.message);
269
+ }
270
+ }
271
+ #id(prefix) {
272
+ this.#nextId += 1;
273
+ return `${prefix}-${this.#nextId}`;
274
+ }
275
+ #onUserMessage(message) {
276
+ const ops = this.#closeReasoningIfLive();
277
+ ops.push({
278
+ type: "commit",
279
+ block: { kind: "user", body: message, depth: this.depth }
280
+ });
281
+ return ops;
282
+ }
283
+ #onAssistantDelta(delta) {
284
+ const ops = this.#closeReasoningIfLive();
285
+ if (this.#currentAssistantId === void 0) {
286
+ const id = this.#id("assistant");
287
+ this.#currentAssistantId = id;
288
+ ops.push({
289
+ type: "start-live",
290
+ block: this.#assistantKind({ kind: "assistant", id, body: delta, live: true, depth: this.depth })
291
+ });
292
+ } else {
293
+ ops.push({
294
+ type: "update-live",
295
+ id: this.#currentAssistantId,
296
+ patch: withDelta(delta)
297
+ });
298
+ }
299
+ return ops;
300
+ }
301
+ #endAssistant(text) {
302
+ const ops = [];
303
+ if (this.#currentAssistantId === void 0) return ops;
304
+ if (text !== void 0 && text.length > 0) {
305
+ ops.push({
306
+ type: "update-live",
307
+ id: this.#currentAssistantId,
308
+ patch: { body: text, live: false }
309
+ });
310
+ } else {
311
+ ops.push({
312
+ type: "update-live",
313
+ id: this.#currentAssistantId,
314
+ patch: { live: false }
315
+ });
316
+ }
317
+ ops.push({ type: "end-live", id: this.#currentAssistantId });
318
+ this.#currentAssistantId = void 0;
319
+ return ops;
320
+ }
321
+ #onReasoningDelta(delta) {
322
+ const ops = [];
323
+ if (this.#currentReasoningId === void 0) {
324
+ const id = this.#id("reasoning");
325
+ this.#currentReasoningId = id;
326
+ ops.push({
327
+ type: "start-live",
328
+ block: this.#assistantKind({
329
+ kind: "reasoning",
330
+ id,
331
+ body: delta,
332
+ live: true,
333
+ collapsed: false,
334
+ depth: this.depth
335
+ })
336
+ });
337
+ } else {
338
+ ops.push({
339
+ type: "update-live",
340
+ id: this.#currentReasoningId,
341
+ patch: withDelta(delta)
342
+ });
343
+ }
344
+ return ops;
345
+ }
346
+ #endReasoning(text) {
347
+ const ops = [];
348
+ if (this.#currentReasoningId === void 0) return ops;
349
+ ops.push({
350
+ type: "update-live",
351
+ id: this.#currentReasoningId,
352
+ patch: { body: text, live: false, collapsed: this.#autoCollapseReasoning }
353
+ });
354
+ ops.push({ type: "end-live", id: this.#currentReasoningId });
355
+ this.#currentReasoningId = void 0;
356
+ return ops;
357
+ }
358
+ #closeReasoningIfLive() {
359
+ if (this.#currentReasoningId === void 0 || !this.#autoCollapseReasoning) return [];
360
+ const id = this.#currentReasoningId;
361
+ this.#currentReasoningId = void 0;
362
+ return [
363
+ { type: "update-live", id, patch: { collapsed: true, live: false } },
364
+ { type: "end-live", id }
365
+ ];
366
+ }
367
+ #onToolStarted(name, callId, input) {
368
+ const id = this.#id("tool");
369
+ this.#toolIds.set(callId, id);
370
+ const inSubagent = this.depth > 0;
371
+ const args = summarizeToolArgs(input, 80);
372
+ return [
373
+ {
374
+ type: "start-live",
375
+ block: {
376
+ kind: inSubagent ? "subagent-tool" : "tool",
377
+ id,
378
+ title: name,
379
+ subtitle: args,
380
+ toolInput: input,
381
+ status: "running",
382
+ live: true,
383
+ depth: this.depth
384
+ }
385
+ }
386
+ ];
387
+ }
388
+ #onToolCompleted(callId, status, output, errorMessage, errorCode) {
389
+ const id = this.#toolIds.get(callId);
390
+ if (id === void 0) return [];
391
+ const isApproval = status === "pending" && errorCode === "needs_approval";
392
+ if (isApproval) {
393
+ return [
394
+ {
395
+ type: "update-live",
396
+ id,
397
+ patch: {
398
+ status: "approval",
399
+ result: errorMessage ?? "awaiting approval"
400
+ }
401
+ }
402
+ ];
403
+ }
404
+ this.#toolIds.delete(callId);
405
+ const ok = status === "completed";
406
+ const patch = {
407
+ status: ok ? "done" : "error",
408
+ toolOutput: ok ? output : void 0,
409
+ result: ok ? summarizeToolResult(output, 80) : errorMessage ?? "failed",
410
+ live: false
411
+ };
412
+ return [
413
+ { type: "update-live", id, patch },
414
+ { type: "end-live", id }
415
+ ];
416
+ }
417
+ #onSubagentCalled(name, callId) {
418
+ const id = this.#id("subagent");
419
+ this.#subagentStack.push(id);
420
+ return [
421
+ {
422
+ type: "commit",
423
+ block: {
424
+ kind: "subagent",
425
+ id,
426
+ title: name,
427
+ depth: this.depth - 1
428
+ }
429
+ }
430
+ ];
431
+ }
432
+ #onSubagentCompleted(_callId, output) {
433
+ if (this.#subagentStack.length === 0) return [];
434
+ this.#subagentStack.pop();
435
+ if (output && output.length > 0) {
436
+ return [
437
+ {
438
+ type: "commit",
439
+ block: {
440
+ kind: "subagent-step",
441
+ body: output,
442
+ depth: this.depth
443
+ }
444
+ }
445
+ ];
446
+ }
447
+ return [];
448
+ }
449
+ #onFailure(code, message) {
450
+ return [
451
+ {
452
+ type: "commit",
453
+ block: {
454
+ kind: "error",
455
+ title: code,
456
+ body: message,
457
+ depth: this.depth
458
+ }
459
+ }
460
+ ];
461
+ }
462
+ #assistantKind(block) {
463
+ return this.depth > 0 && block.kind === "assistant" ? { ...block, kind: "subagent-step" } : block;
464
+ }
465
+ };
466
+ var DELTA_KEY = "__delta";
467
+ function withDelta(delta) {
468
+ return { [DELTA_KEY]: delta };
469
+ }
470
+ function createBlockStore() {
471
+ const live = /* @__PURE__ */ new Map();
472
+ let justCommitted = [];
473
+ return {
474
+ get live() {
475
+ return live;
476
+ },
477
+ get justCommitted() {
478
+ return justCommitted;
479
+ },
480
+ apply(op) {
481
+ switch (op.type) {
482
+ case "commit":
483
+ justCommitted.push(op.block);
484
+ return;
485
+ case "start-live":
486
+ if (op.block.id === void 0) return;
487
+ live.set(op.block.id, op.block);
488
+ return;
489
+ case "update-live": {
490
+ const existing = live.get(op.id);
491
+ if (existing === void 0) return;
492
+ const patch = op.patch;
493
+ const delta = patch[DELTA_KEY];
494
+ const rest = { ...patch };
495
+ delete rest[DELTA_KEY];
496
+ const merged = { ...existing, ...rest };
497
+ if (delta !== void 0) merged.body = `${existing.body ?? ""}${delta}`;
498
+ live.set(op.id, merged);
499
+ return;
500
+ }
501
+ case "end-live": {
502
+ const existing = live.get(op.id);
503
+ if (existing === void 0) return;
504
+ live.delete(op.id);
505
+ justCommitted.push({ ...existing, live: false });
506
+ return;
507
+ }
508
+ case "clear-live":
509
+ live.delete(op.id);
510
+ return;
511
+ }
512
+ },
513
+ drainCommitted() {
514
+ const out = justCommitted;
515
+ justCommitted = [];
516
+ return out;
517
+ }
518
+ };
519
+ }
520
+
521
+ // src/cli/tui/markdown.ts
522
+ var ansi = {
523
+ bold: "\x1B[1m",
524
+ boldOff: "\x1B[22m",
525
+ italic: "\x1B[3m",
526
+ italicOff: "\x1B[23m"
527
+ };
528
+ var tableSeparator = "\u2500";
529
+ function renderMarkdown(input) {
530
+ const lines = input.split("\n");
531
+ const output = [];
532
+ for (let index = 0; index < lines.length; index += 1) {
533
+ const table = parseTable(lines, index);
534
+ if (table != null) {
535
+ output.push(...renderTable(table));
536
+ index = table.endIndex - 1;
537
+ continue;
538
+ }
539
+ output.push(renderMarkdownLine(lines[index] ?? ""));
540
+ }
541
+ return output.join("\n");
542
+ }
543
+ function renderMarkdownLine(line) {
544
+ if (line.startsWith("### ")) return renderInlineMarkdown(`\u25B6 ${line.slice(4)}`);
545
+ if (line.startsWith("## ")) return renderInlineMarkdown(`\u25A0 ${line.slice(3)}`);
546
+ if (line.startsWith("# ")) return renderInlineMarkdown(`\u2588 ${line.slice(2)}`);
547
+ const unorderedListItem = line.match(/^(\s*)[-+*]\s+(.*)$/);
548
+ if (unorderedListItem) {
549
+ const [, indentation, text = ""] = unorderedListItem;
550
+ return renderInlineMarkdown(`${indentation}\u2022${text.length > 0 ? ` ${text}` : ""}`);
551
+ }
552
+ if (/^\d+\. /.test(line)) return renderInlineMarkdown(line.replace(/^(\d+)\. /, "$1. "));
553
+ if (line.startsWith("> ")) return renderInlineMarkdown(`\u2502 ${line.slice(2)}`);
554
+ return renderInlineMarkdown(line);
555
+ }
556
+ function renderInlineMarkdown(input) {
557
+ const urls = [];
558
+ const shielded = input.replaceAll(/https?:\/\/\S+/g, (match) => {
559
+ const token = `${urls.length}`;
560
+ urls.push(match);
561
+ return token;
562
+ });
563
+ const styled = shielded.replaceAll(/`([^`]+)`/g, "$1").replaceAll(/\*\*([^*\n]+)\*\*/g, `${ansi.bold}$1${ansi.boldOff}`).replaceAll(/__([^_\n]+)__/g, `${ansi.bold}$1${ansi.boldOff}`).replaceAll(/\*([^*\n]+)\*/g, `${ansi.italic}$1${ansi.italicOff}`).replaceAll(/_([^_\n]+)_/g, `${ansi.italic}$1${ansi.italicOff}`);
564
+ return styled.replaceAll(/(\d+)/g, (_match, index) => urls[Number(index)] ?? "");
565
+ }
566
+ function parseTable(lines, startIndex) {
567
+ const header = parseTableCells(lines[startIndex] ?? "");
568
+ if (header == null || header.length < 2) return void 0;
569
+ const separatorCells = parseTableCells(lines[startIndex + 1] ?? "");
570
+ if (separatorCells == null || separatorCells.length !== header.length) return void 0;
571
+ const alignments = parseTableAlignments(separatorCells);
572
+ if (alignments == null) return void 0;
573
+ const rows = [];
574
+ let endIndex = startIndex + 2;
575
+ while (endIndex < lines.length) {
576
+ const row = parseTableCells(lines[endIndex] ?? "");
577
+ if (row == null) break;
578
+ rows.push(normalizeTableRow(row, header.length));
579
+ endIndex += 1;
580
+ }
581
+ return { alignments, endIndex, header, rows };
582
+ }
583
+ function parseTableCells(line) {
584
+ if (!line.includes("|")) return void 0;
585
+ let tableLine = line.trim();
586
+ if (tableLine.startsWith("|")) tableLine = tableLine.slice(1);
587
+ if (tableLine.endsWith("|") && !tableLine.endsWith("\\|")) {
588
+ tableLine = tableLine.slice(0, -1);
589
+ }
590
+ const cells = [];
591
+ let cell = "";
592
+ for (let index = 0; index < tableLine.length; index += 1) {
593
+ const character = tableLine[index];
594
+ const nextCharacter = tableLine[index + 1];
595
+ if (character === "\\" && nextCharacter === "|") {
596
+ cell += "|";
597
+ index += 1;
598
+ continue;
599
+ }
600
+ if (character === "|") {
601
+ cells.push(cell.trim());
602
+ cell = "";
603
+ continue;
604
+ }
605
+ cell += character;
606
+ }
607
+ cells.push(cell.trim());
608
+ return cells;
609
+ }
610
+ function parseTableAlignments(cells) {
611
+ const alignments = [];
612
+ for (const cell of cells) {
613
+ const match = cell.match(/^(:)?-{3,}(:)?$/);
614
+ if (match == null) return void 0;
615
+ const [, left, right] = match;
616
+ alignments.push(left != null && right != null ? "center" : right != null ? "right" : "left");
617
+ }
618
+ return alignments;
619
+ }
620
+ function normalizeTableRow(row, length) {
621
+ return Array.from({ length }, (_, index) => row[index] ?? "");
622
+ }
623
+ function renderTable(table) {
624
+ const header = table.header.map(
625
+ (cell) => `${ansi.bold}${renderInlineMarkdown(cell)}${ansi.boldOff}`
626
+ );
627
+ const rows = table.rows.map((row) => row.map(renderInlineMarkdown));
628
+ const tableRows = [header, ...rows];
629
+ const widths = table.alignments.map(
630
+ (_, columnIndex) => Math.max(3, ...tableRows.map((row) => visibleLength(row[columnIndex] ?? "")))
631
+ );
632
+ return [
633
+ formatTableRow(header, widths, table.alignments),
634
+ widths.map((width) => tableSeparator.repeat(width)).join(" "),
635
+ ...rows.map((row) => formatTableRow(row, widths, table.alignments))
636
+ ];
637
+ }
638
+ function formatTableRow(row, widths, alignments) {
639
+ return row.map((cell, index) => alignTableCell(cell, widths[index] ?? 0, alignments[index] ?? "left")).join(" ");
640
+ }
641
+ function alignTableCell(cell, width, alignment) {
642
+ const paddingWidth = Math.max(0, width - visibleLength(cell));
643
+ if (alignment === "right") return `${" ".repeat(paddingWidth)}${cell}`;
644
+ if (alignment === "center") {
645
+ const leftPadding = Math.floor(paddingWidth / 2);
646
+ const rightPadding = paddingWidth - leftPadding;
647
+ return `${" ".repeat(leftPadding)}${cell}${" ".repeat(rightPadding)}`;
648
+ }
649
+ return `${cell}${" ".repeat(paddingWidth)}`;
650
+ }
651
+
652
+ // src/cli/tui/renderer/blocks.ts
653
+ function renderBlockLines(block, width, theme, context) {
654
+ const depth = block.depth ?? 0;
655
+ const prefix = nestingPrefix(depth, theme);
656
+ const avail = Math.max(8, width - visibleLength(prefix));
657
+ const rows = renderBody(block, avail, theme, context);
658
+ return rows.map((row) => `${prefix}${row}`);
659
+ }
660
+ function nestingPrefix(depth, theme) {
661
+ if (depth <= 0) return "";
662
+ const rule = `${theme.colors.orange(theme.glyph.rule)} `;
663
+ return rule.repeat(depth);
664
+ }
665
+ function renderBody(block, width, theme, context) {
666
+ switch (block.kind) {
667
+ case "user":
668
+ return renderUser(block, width, theme);
669
+ case "assistant":
670
+ case "subagent-step":
671
+ return renderProse(block, width, theme);
672
+ case "reasoning":
673
+ return renderReasoning(block, width, theme);
674
+ case "tool":
675
+ case "subagent-tool":
676
+ return renderTool(block, width, theme, context);
677
+ case "error":
678
+ return renderError(block, width, theme);
679
+ case "notice":
680
+ return renderNotice(block, width, theme);
681
+ case "warning":
682
+ return renderWarning(block, width, theme);
683
+ case "result":
684
+ return renderResult(block, width, theme);
685
+ case "subagent":
686
+ return renderSubagentHeader(block, width, theme);
687
+ case "agent-header":
688
+ return (block.body ?? "").split("\n");
689
+ }
690
+ }
691
+ function renderUser(block, width, theme) {
692
+ const bar = theme.colors.cyan(theme.glyph.user);
693
+ const lines = wrap(block.body ?? "", width - 2);
694
+ return lines.map((line) => `${bar} ${line}`);
695
+ }
696
+ function renderProse(block, width, theme) {
697
+ const rows = [];
698
+ const isSubagent = block.kind === "subagent-step";
699
+ const glyph = isSubagent ? "" : `${theme.colors.bold(theme.colors.white(theme.glyph.brand))} `;
700
+ const indent = isSubagent ? "" : " ";
701
+ if (block.reasoning && block.reasoning.trim().length > 0) {
702
+ rows.push(...renderReasoningLines(block.reasoning, width, theme));
703
+ }
704
+ const body = (block.body ?? "").trim();
705
+ if (body.length === 0 && rows.length === 0) {
706
+ return [`${glyph}${theme.colors.dim(`thinking${theme.glyph.ellipsis}`)}`];
707
+ }
708
+ if (body.length > 0) {
709
+ const rendered = renderMarkdown(body).split("\n").flatMap((line) => wrapVisibleLine(line, width - indent.length));
710
+ rendered.forEach((line, index) => {
711
+ if (index === 0 && !isSubagent && rows.length === 0) {
712
+ rows.push(`${glyph}${line}`);
713
+ } else {
714
+ rows.push(`${indent}${line}`);
715
+ }
716
+ });
717
+ }
718
+ return rows.length > 0 ? rows : [`${glyph}`];
719
+ }
720
+ function renderReasoning(block, width, theme) {
721
+ if (block.collapsed) {
722
+ return [`${theme.colors.gray(theme.glyph.reasoning)} ${theme.colors.dim("thinking")}`];
723
+ }
724
+ return renderReasoningLines(block.body ?? "", width, theme, theme.glyph.reasoning);
725
+ }
726
+ function renderReasoningLines(text, width, theme, glyph) {
727
+ const pad = glyph ? 2 : 0;
728
+ const lines = wrap(text.trim(), width - pad);
729
+ if (lines.length === 0) return [];
730
+ return lines.map((line, index) => {
731
+ const prefix = glyph ? index === 0 ? `${theme.colors.gray(glyph)} ` : " " : "";
732
+ return `${prefix}${theme.colors.dim(theme.colors.italic(line))}`;
733
+ });
734
+ }
735
+ function renderTool(block, width, theme, context) {
736
+ const { icon, accent } = toolGlyph(block.status ?? "running", theme, context);
737
+ const name = block.title ?? "tool";
738
+ const headerWidth = width - 2;
739
+ const namePlain = truncatePlain(name, headerWidth);
740
+ let header = `${icon} ${theme.colors.bold(namePlain)}`;
741
+ const argsBudget = headerWidth - namePlain.length - 2;
742
+ const args = block.subtitle ?? "";
743
+ if (args.length > 0 && argsBudget >= 6) {
744
+ header += ` ${theme.colors.gray(truncate(args, argsBudget))}`;
745
+ }
746
+ const rows = [header];
747
+ if (block.expanded) {
748
+ rows.push(...renderToolExpanded(block, width, theme));
749
+ } else if (block.status === "done" && block.result && block.result.length > 0) {
750
+ rows.push(resultLine(theme.glyph.arrow, block.result, width, theme, accent));
751
+ } else if (block.status === "error" && block.result) {
752
+ rows.push(resultLine(theme.glyph.arrow, block.result, width, theme, theme.colors.red));
753
+ } else if (block.status === "denied") {
754
+ rows.push(resultLine(theme.glyph.arrow, "denied", width, theme, theme.colors.yellow));
755
+ }
756
+ return rows;
757
+ }
758
+ function renderToolExpanded(block, width, theme) {
759
+ const rows = [];
760
+ const push = (label, value, color) => {
761
+ if (value === void 0) return;
762
+ rows.push(` ${theme.colors.dim(label)}`);
763
+ for (const line of wrap(formatValuePretty(value), width - 4)) {
764
+ rows.push(` ${color(line)}`);
765
+ }
766
+ };
767
+ push("input", block.toolInput, theme.colors.gray);
768
+ if (block.status === "error" && block.result) {
769
+ push("error", block.result, theme.colors.red);
770
+ } else {
771
+ push("output", block.toolOutput, theme.colors.gray);
772
+ }
773
+ return rows;
774
+ }
775
+ function resultLine(marker, text, width, theme, color) {
776
+ const budget = width - 4;
777
+ return ` ${theme.colors.dim(marker)} ${color(truncate(text, budget))}`;
778
+ }
779
+ function toolGlyph(status, theme, context) {
780
+ switch (status) {
781
+ case "done":
782
+ return { icon: theme.colors.green(theme.glyph.success), accent: theme.colors.gray };
783
+ case "error":
784
+ return { icon: theme.colors.red(theme.glyph.error), accent: theme.colors.red };
785
+ case "denied":
786
+ return { icon: theme.colors.yellow(theme.glyph.warning), accent: theme.colors.yellow };
787
+ case "approval":
788
+ return { icon: theme.colors.yellow(theme.glyph.question), accent: theme.colors.yellow };
789
+ case "running":
790
+ default:
791
+ return { icon: theme.colors.yellow(context.spinner), accent: theme.colors.gray };
792
+ }
793
+ }
794
+ var ERROR_DETAIL_MAX_LINES = 12;
795
+ function renderError(block, width, theme) {
796
+ const icon = theme.colors.red(theme.colors.bold(theme.glyph.error));
797
+ const title = block.title ?? "Error";
798
+ const rows = [`${icon} ${theme.colors.red(theme.colors.bold(title))}`];
799
+ for (const line of wrap(block.body ?? "", width - 2)) {
800
+ rows.push(` ${colorizeError(line, theme)}`);
801
+ }
802
+ rows.push(...renderErrorDetail(block.detail, width, theme));
803
+ return rows;
804
+ }
805
+ function renderErrorDetail(detail, width, theme) {
806
+ if (detail === void 0 || detail.trim().length === 0) return [];
807
+ const lines = detail.split("\n");
808
+ const visible = lines.slice(0, ERROR_DETAIL_MAX_LINES);
809
+ const rows = visible.map(
810
+ (line) => ` ${theme.colors.dim(truncatePlain(line, Math.max(1, width - 2)))}`
811
+ );
812
+ const hidden = lines.length - visible.length;
813
+ if (hidden > 0) {
814
+ rows.push(
815
+ ` ${theme.colors.dim(`${theme.glyph.ellipsis} +${hidden} more line${hidden === 1 ? "" : "s"}`)}`
816
+ );
817
+ }
818
+ return rows;
819
+ }
820
+ var URL_PATTERN = /(https?:\/\/\S+)/u;
821
+ function colorizeError(line, theme) {
822
+ if (!URL_PATTERN.test(line)) return theme.colors.red(line);
823
+ return line.split(URL_PATTERN).map(
824
+ (segment, index) => index % 2 === 1 ? theme.colors.cyan(segment) : theme.colors.red(segment)
825
+ ).join("");
826
+ }
827
+ function renderNotice(block, width, theme) {
828
+ const marker = theme.colors.dim(theme.glyph.dot);
829
+ const lines = wrap(block.body ?? "", width - 2);
830
+ if (lines.length === 0) return [marker];
831
+ return lines.map((line) => `${marker} ${theme.colors.dim(line)}`);
832
+ }
833
+ function renderAttentionRows(body, width, theme) {
834
+ const marker = theme.colors.yellow(theme.glyph.warning);
835
+ const lines = wrap(body, width - 2);
836
+ return lines.map((line, index) => `${index === 0 ? marker : " "} ${paintCommands(line, theme)}`);
837
+ }
838
+ function renderWarning(block, width, theme) {
839
+ return renderAttentionRows(block.body ?? "", width, theme);
840
+ }
841
+ function paintCommands(line, theme) {
842
+ return line.replace(
843
+ /\/[a-z:-]+/g,
844
+ (token) => isPromptControlCommand(token) ? theme.colors.blue(token) : token
845
+ );
846
+ }
847
+ function renderResult(block, width, theme) {
848
+ const marker = theme.colors.dim(theme.glyph.elbow);
849
+ const lines = wrap(block.body ?? "", width - 7);
850
+ if (lines.length === 0) return [` ${marker}`];
851
+ const dim = (line) => theme.colors.dim(line.replaceAll("\x1B[22m", "\x1B[22m\x1B[2m"));
852
+ return lines.map(
853
+ (line, index) => index === 0 ? ` ${marker} ${dim(line)}` : ` ${dim(line)}`
854
+ );
855
+ }
856
+ function renderSubagentHeader(block, width, theme) {
857
+ const name = truncatePlain(block.title ?? "subagent", Math.max(8, width - 14));
858
+ return [
859
+ `${theme.colors.orange(theme.glyph.subagent)} ${theme.colors.bold(name)} ${theme.colors.dim("subagent")}`
860
+ ];
861
+ }
862
+ function wrap(text, width) {
863
+ if (text.trim().length === 0) return [];
864
+ return text.split("\n").flatMap((line) => wrapVisibleLine(line, Math.max(1, width)));
865
+ }
866
+ function truncatePlain(text, maxWidth) {
867
+ if (visibleLength(text) <= maxWidth) return text;
868
+ return sliceVisible(text, maxWidth);
869
+ }
870
+
871
+ // src/cli/tui/renderer/terminal-renderer.ts
872
+ var EMPTY_TYPEAHEAD = {
873
+ matches: [],
874
+ selectedIndex: 0,
875
+ dismissed: false
876
+ };
877
+ var TerminalRenderer = class {
878
+ #output;
879
+ #theme;
880
+ #commands;
881
+ #store;
882
+ #region;
883
+ #history = new PromptHistory();
884
+ #spinnerIntervalMs;
885
+ #line = EMPTY_LINE;
886
+ #typeahead = EMPTY_TYPEAHEAD;
887
+ #pendingResolve;
888
+ #spinnerFrame = 0;
889
+ #spinnerTimer;
890
+ #stopped = false;
891
+ #firstPaintDone = false;
892
+ constructor(options = {}) {
893
+ this.#output = options.output ?? process.stdout;
894
+ this.#theme = options.theme ?? createTheme({ unicode: detectUnicode() });
895
+ this.#commands = options.commands ?? PROMPT_COMMANDS;
896
+ this.#spinnerIntervalMs = options.spinnerIntervalMs ?? 90;
897
+ this.#store = createBlockStore();
898
+ this.#region = new LiveRegion({
899
+ output: this.#output,
900
+ onResize: () => this.#paint()
901
+ });
902
+ }
903
+ apply(ops) {
904
+ if (ops.length === 0) return;
905
+ for (const op of ops) this.#store.apply(op);
906
+ this.#flushCommitted();
907
+ this.#syncSpinner();
908
+ this.#paint();
909
+ }
910
+ writeNotice(text) {
911
+ this.apply([{ type: "commit", block: { kind: "notice", body: text } }]);
912
+ }
913
+ writeError(title, message) {
914
+ this.apply([
915
+ { type: "commit", block: { kind: "error", title, body: message } }
916
+ ]);
917
+ }
918
+ writeAgentHeader(body) {
919
+ this.apply([{ type: "commit", block: { kind: "agent-header", body } }]);
920
+ }
921
+ readPrompt() {
922
+ return new Promise((resolve2) => {
923
+ this.#pendingResolve = resolve2;
924
+ this.#line = EMPTY_LINE;
925
+ this.#typeahead = EMPTY_TYPEAHEAD;
926
+ this.#history.begin(this.#line.text);
927
+ this.#paint();
928
+ });
929
+ }
930
+ handleKey(key) {
931
+ if (this.#pendingResolve === void 0) return;
932
+ if (key.type === "ctrl-c") {
933
+ const resolve2 = this.#pendingResolve;
934
+ this.#pendingResolve = void 0;
935
+ this.stop();
936
+ resolve2(void 0);
937
+ return;
938
+ }
939
+ if (this.#isTypeaheadOpen() && (key.type === "up" || key.type === "down")) {
940
+ const delta = key.type === "up" ? -1 : 1;
941
+ this.#moveTypeahead(delta);
942
+ this.#paint();
943
+ return;
944
+ }
945
+ if (this.#isTypeaheadOpen() && (key.type === "tab" || key.type === "enter")) {
946
+ this.#acceptTypeahead(key.type === "enter");
947
+ return;
948
+ }
949
+ if (key.type === "escape" && this.#isTypeaheadOpen()) {
950
+ this.#typeahead = { ...this.#typeahead, dismissed: true };
951
+ this.#paint();
952
+ return;
953
+ }
954
+ if (key.type === "enter") {
955
+ this.#submit();
956
+ return;
957
+ }
958
+ if (key.type === "up" || key.type === "ctrl-p") {
959
+ const previous = this.#history.previous(this.#line.text);
960
+ if (previous !== void 0) {
961
+ this.#line = { text: previous, cursor: previous.length };
962
+ this.#refreshTypeahead();
963
+ this.#paint();
964
+ }
965
+ return;
966
+ }
967
+ if (key.type === "down" || key.type === "ctrl-n") {
968
+ const next = this.#history.next();
969
+ if (next !== void 0) {
970
+ this.#line = { text: next, cursor: next.length };
971
+ this.#refreshTypeahead();
972
+ this.#paint();
973
+ }
974
+ return;
975
+ }
976
+ const nextLine = applyLineEditorKey(this.#line, key, { multiline: false });
977
+ if (nextLine !== void 0) {
978
+ this.#line = nextLine;
979
+ this.#refreshTypeahead();
980
+ this.#paint();
981
+ }
982
+ }
983
+ stop() {
984
+ if (this.#stopped) return;
985
+ this.#stopped = true;
986
+ if (this.#spinnerTimer !== void 0) clearInterval(this.#spinnerTimer);
987
+ this.#region.stop();
988
+ }
989
+ #submit() {
990
+ if (this.#pendingResolve === void 0) return;
991
+ const text = this.#line.text;
992
+ this.#history.add(text);
993
+ const resolve2 = this.#pendingResolve;
994
+ this.#pendingResolve = void 0;
995
+ this.#line = EMPTY_LINE;
996
+ this.#typeahead = EMPTY_TYPEAHEAD;
997
+ this.#region.paint([], { row: 1, col: 1 });
998
+ resolve2(text);
999
+ }
1000
+ #refreshTypeahead() {
1001
+ const matches = matchingCommands(this.#commands, this.#line.text);
1002
+ this.#typeahead = { matches, selectedIndex: 0, dismissed: false };
1003
+ }
1004
+ #isTypeaheadOpen() {
1005
+ return this.#typeahead.matches.length > 0 && !this.#typeahead.dismissed;
1006
+ }
1007
+ #moveTypeahead(delta) {
1008
+ const count = this.#typeahead.matches.length;
1009
+ if (count === 0) return;
1010
+ const selectedIndex = (this.#typeahead.selectedIndex + delta + count) % count;
1011
+ this.#typeahead = { ...this.#typeahead, selectedIndex };
1012
+ }
1013
+ #acceptTypeahead(submitAfter) {
1014
+ const spec = this.#typeahead.matches[this.#typeahead.selectedIndex];
1015
+ if (spec === void 0) return;
1016
+ const text = `/${spec.name}${spec.takesArgument ? " " : ""}`;
1017
+ this.#line = { text, cursor: text.length };
1018
+ this.#typeahead = EMPTY_TYPEAHEAD;
1019
+ if (submitAfter && !spec.takesArgument) {
1020
+ this.#submit();
1021
+ } else {
1022
+ this.#paint();
1023
+ }
1024
+ }
1025
+ #flushCommitted() {
1026
+ const drained = this.#store.drainCommitted();
1027
+ if (drained.length === 0) return;
1028
+ const rows = [];
1029
+ let previous;
1030
+ const ctx = { spinner: this.#currentSpinner(), previous };
1031
+ for (const block of drained) {
1032
+ const blockCtx = { spinner: this.#currentSpinner(), previous };
1033
+ rows.push(...renderBlockLines(block, this.columns, this.#theme, blockCtx));
1034
+ previous = { kind: block.kind, title: block.title };
1035
+ }
1036
+ if (rows.length > 0 && this.#firstPaintDone) rows.unshift("");
1037
+ if (!this.#firstPaintDone && rows.length > 0) this.#firstPaintDone = true;
1038
+ this.#region.commit(rows);
1039
+ }
1040
+ #paint() {
1041
+ if (this.#stopped) return;
1042
+ if (this.#pendingResolve === void 0 && this.#store.live.size === 0) {
1043
+ this.#region.paint([], { row: 1, col: 1 });
1044
+ return;
1045
+ }
1046
+ const rows = [];
1047
+ const ctx = { spinner: this.#currentSpinner() };
1048
+ for (const block of this.#store.live.values()) {
1049
+ rows.push(...renderBlockLines(block, this.columns, this.#theme, ctx));
1050
+ }
1051
+ if (this.#pendingResolve !== void 0) {
1052
+ if (rows.length > 0) rows.push("");
1053
+ const inputRow = this.#renderInputRow();
1054
+ const caretCol = this.#caretColumn();
1055
+ rows.push(inputRow);
1056
+ this.#region.paint(rows, { row: rows.length, col: caretCol });
1057
+ return;
1058
+ }
1059
+ this.#region.paint(rows, { row: rows.length, col: 1 });
1060
+ }
1061
+ #renderInputRow() {
1062
+ const c = this.#theme.colors;
1063
+ const promptGlyph = c.cyan(this.#theme.glyph.prompt);
1064
+ const promptWidth = visibleLength(`${this.#theme.glyph.prompt} `);
1065
+ const budget = Math.max(1, this.columns - promptWidth);
1066
+ const view = visibleLine(this.#line, budget);
1067
+ const before = view.before;
1068
+ const under = view.under.length === 0 ? " " : view.under;
1069
+ const after = view.after;
1070
+ return `${promptGlyph} ${before}${c.inverse(under)}${after}`;
1071
+ }
1072
+ #caretColumn() {
1073
+ const promptWidth = visibleLength(`${this.#theme.glyph.prompt} `);
1074
+ const view = visibleLine(this.#line, Math.max(1, this.columns - promptWidth));
1075
+ return 1 + promptWidth + inputTextWidth(view.before);
1076
+ }
1077
+ #currentSpinner() {
1078
+ return this.#theme.spinner[this.#spinnerFrame % this.#theme.spinner.length] ?? "-";
1079
+ }
1080
+ #syncSpinner() {
1081
+ const hasLive = [...this.#store.live.values()].some((b) => b.live === true);
1082
+ if (hasLive && this.#spinnerTimer === void 0) {
1083
+ this.#spinnerTimer = setInterval(() => {
1084
+ this.#spinnerFrame += 1;
1085
+ this.#paint();
1086
+ }, this.#spinnerIntervalMs);
1087
+ this.#spinnerTimer.unref?.();
1088
+ }
1089
+ if (!hasLive && this.#spinnerTimer !== void 0) {
1090
+ clearInterval(this.#spinnerTimer);
1091
+ this.#spinnerTimer = void 0;
1092
+ }
1093
+ }
1094
+ get columns() {
1095
+ return this.#region.columns;
1096
+ }
1097
+ };
1098
+
1099
+ // src/cli/tui/renderer/start-block-chat.ts
1100
+ async function startBlockChat(options) {
1101
+ const { agentDir } = options;
1102
+ const renderer = new TerminalRenderer();
1103
+ const translator = new EventTranslator();
1104
+ const commitHeader = async () => {
1105
+ try {
1106
+ const agent = await loadAgent(agentDir);
1107
+ const model = agent.manifest.config.model;
1108
+ renderer.writeAgentHeader(`arcie \xB7 ${agentDir.split("/").pop() ?? "agent"} \xB7 ${model}`);
1109
+ } catch (err) {
1110
+ const message = err instanceof Error ? err.message : String(err);
1111
+ renderer.writeError("Agent load failed", message);
1112
+ }
1113
+ };
1114
+ const { diagnostics } = discoverAgent(agentDir);
1115
+ const errorDiags = diagnostics.filter((d) => d.severity === "error");
1116
+ if (errorDiags.length > 0) {
1117
+ for (const d of errorDiags) renderer.writeError(d.code, d.message);
1118
+ renderer.stop();
1119
+ return;
1120
+ }
1121
+ await commitHeader();
1122
+ let watcher;
1123
+ try {
1124
+ let reprobeTimer;
1125
+ watcher = watch(agentDir, { recursive: true }, () => {
1126
+ if (reprobeTimer !== void 0) clearTimeout(reprobeTimer);
1127
+ reprobeTimer = setTimeout(() => {
1128
+ void commitHeader();
1129
+ }, 250);
1130
+ });
1131
+ } catch {
1132
+ }
1133
+ const detachKeys = attachKeyStream((key) => renderer.handleKey(key));
1134
+ try {
1135
+ if (options.initialInput !== void 0 && options.initialInput.length > 0) {
1136
+ await streamOneTurn(renderer, translator, agentDir, options.initialInput);
1137
+ }
1138
+ while (true) {
1139
+ const text = await renderer.readPrompt();
1140
+ if (text === void 0) return;
1141
+ const trimmed = text.trim();
1142
+ if (trimmed.length === 0) continue;
1143
+ if (trimmed.startsWith("/")) {
1144
+ const command = parsePromptCommand(trimmed);
1145
+ if (command === null) {
1146
+ renderer.writeError("Unknown command", `${trimmed} \u2014 try /help`);
1147
+ continue;
1148
+ }
1149
+ switch (command.type) {
1150
+ case "exit":
1151
+ return;
1152
+ case "help":
1153
+ for (const line of formatPromptCommandHelp().split("\n")) {
1154
+ renderer.writeNotice(line);
1155
+ }
1156
+ continue;
1157
+ case "clear":
1158
+ case "new":
1159
+ for (const op of translator.reset()) renderer.apply([op]);
1160
+ await commitHeader();
1161
+ continue;
1162
+ case "extension":
1163
+ renderer.writeNotice(`/${command.name} not yet implemented`);
1164
+ continue;
1165
+ case "loglevel":
1166
+ renderer.writeNotice("logs not yet implemented");
1167
+ continue;
1168
+ }
1169
+ continue;
1170
+ }
1171
+ await streamOneTurn(renderer, translator, agentDir, trimmed);
1172
+ }
1173
+ } finally {
1174
+ detachKeys();
1175
+ watcher?.close();
1176
+ renderer.stop();
1177
+ }
1178
+ }
1179
+ async function streamOneTurn(renderer, translator, agentDir, message) {
1180
+ let sawApproval = false;
1181
+ try {
1182
+ for await (const event of streamAgent(agentDir, message)) {
1183
+ const ops = translator.feed(event);
1184
+ renderer.apply(ops);
1185
+ if (event.type === "tool.completed" && event.data.status === "pending" && event.data.error?.code === "needs_approval") {
1186
+ sawApproval = true;
1187
+ }
1188
+ }
1189
+ if (sawApproval) {
1190
+ renderer.writeNotice(
1191
+ "Tool call awaiting approval \u2014 approve via the Cencori sessions API to continue."
1192
+ );
1193
+ }
1194
+ } catch (err) {
1195
+ const detail = err instanceof Error ? err.stack : void 0;
1196
+ renderer.apply([
1197
+ {
1198
+ type: "commit",
1199
+ block: {
1200
+ kind: "error",
1201
+ title: "Stream error",
1202
+ body: err instanceof Error ? err.message : String(err),
1203
+ detail
1204
+ }
1205
+ }
1206
+ ]);
1207
+ }
1208
+ }
1209
+
1210
+ // src/server/llm.ts
1211
+ var PROVIDERS = {
1212
+ openai: {
1213
+ baseUrl: "https://api.openai.com/v1",
1214
+ apiKey: () => process.env.OPENAI_API_KEY
1215
+ },
1216
+ anthropic: {
1217
+ baseUrl: "https://api.anthropic.com/v1",
1218
+ apiKey: () => process.env.ANTHROPIC_API_KEY
1219
+ },
1220
+ groq: {
1221
+ baseUrl: "https://api.groq.com/openai/v1",
1222
+ apiKey: () => process.env.GROQ_API_KEY
1223
+ },
1224
+ deepseek: {
1225
+ baseUrl: "https://api.deepseek.com/v1",
1226
+ apiKey: () => process.env.DEEPSEEK_API_KEY
1227
+ },
1228
+ mistral: {
1229
+ baseUrl: "https://api.mistral.ai/v1",
1230
+ apiKey: () => process.env.MISTRAL_API_KEY
1231
+ },
1232
+ google: {
1233
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta",
1234
+ apiKey: () => process.env.GOOGLE_API_KEY
1235
+ },
1236
+ meta: {
1237
+ baseUrl: "https://api.together.xyz/v1",
1238
+ apiKey: () => process.env.TOGETHER_API_KEY || process.env.OPENAI_API_KEY
1239
+ }
1240
+ };
1241
+ function parseModelId(modelId) {
1242
+ const slash = modelId.indexOf("/");
1243
+ if (slash === -1) return { provider: "", model: modelId };
1244
+ return { provider: modelId.slice(0, slash), model: modelId.slice(slash + 1) };
1245
+ }
1246
+ function getProviderApiKey(provider) {
1247
+ const cfg = PROVIDERS[provider];
1248
+ return cfg?.apiKey?.();
1249
+ }
1250
+ async function* streamLlm(modelId, messages, tools, toolResults) {
1251
+ const { provider, model } = parseModelId(modelId);
1252
+ const cfg = PROVIDERS[provider];
1253
+ if (!cfg) {
1254
+ yield { type: "error", error: `Unknown provider: ${provider}` };
1255
+ return;
1256
+ }
1257
+ const apiKey = cfg.apiKey();
1258
+ if (!apiKey) {
1259
+ yield { type: "error", error: `Missing API key for provider: ${provider}. Set ${provider.toUpperCase()}_API_KEY` };
1260
+ return;
1261
+ }
1262
+ if (provider === "anthropic") {
1263
+ yield* streamAnthropic(cfg.baseUrl, apiKey, model, messages, tools, toolResults);
1264
+ } else if (provider === "google") {
1265
+ yield* streamGoogle(cfg.baseUrl, apiKey, model, messages, tools, toolResults);
1266
+ } else {
1267
+ yield* streamOpenAICompatible(cfg.baseUrl, apiKey, model, messages, tools, toolResults);
1268
+ }
1269
+ }
1270
+ async function* streamOpenAICompatible(baseUrl, apiKey, model, messages, tools, toolResults) {
1271
+ const body = {
1272
+ model,
1273
+ messages,
1274
+ stream: true,
1275
+ stream_options: { include_usage: false }
1276
+ };
1277
+ if (tools && tools.length > 0) {
1278
+ body.tools = tools;
1279
+ }
1280
+ const res = await fetch(`${baseUrl}/chat/completions`, {
1281
+ method: "POST",
1282
+ headers: {
1283
+ "Content-Type": "application/json",
1284
+ Authorization: `Bearer ${apiKey}`
1285
+ },
1286
+ body: JSON.stringify(body)
1287
+ });
1288
+ if (!res.ok) {
1289
+ const err = await res.text().catch(() => "");
1290
+ yield { type: "error", error: `LLM API error (${res.status}): ${err.slice(0, 300)}` };
1291
+ return;
1292
+ }
1293
+ const reader = res.body?.getReader();
1294
+ if (!reader) {
1295
+ yield { type: "error", error: "No response body" };
1296
+ return;
1297
+ }
1298
+ const decoder = new TextDecoder();
1299
+ let buffer = "";
1300
+ let accumulatedToolCalls = /* @__PURE__ */ new Map();
1301
+ while (true) {
1302
+ const { done, value } = await reader.read();
1303
+ if (done) break;
1304
+ buffer += decoder.decode(value, { stream: true });
1305
+ const lines = buffer.split("\n");
1306
+ buffer = lines.pop() ?? "";
1307
+ for (const line of lines) {
1308
+ if (!line.startsWith("data: ")) continue;
1309
+ const payload = line.slice(6).trim();
1310
+ if (payload === "[DONE]") {
1311
+ if (accumulatedToolCalls.size > 0) {
1312
+ const calls = Array.from(accumulatedToolCalls.values()).map((c) => ({
1313
+ id: c.id,
1314
+ name: c.name,
1315
+ arguments: c.args
1316
+ }));
1317
+ yield { type: "tool_call", toolCalls: calls };
1318
+ }
1319
+ yield { type: "done", finishReason: "stop" };
1320
+ return;
1321
+ }
1322
+ try {
1323
+ const parsed = JSON.parse(payload);
1324
+ const choice = parsed.choices?.[0];
1325
+ if (choice?.finish_reason) {
1326
+ if (choice.finish_reason === "tool_calls" && accumulatedToolCalls.size > 0) {
1327
+ const calls = Array.from(accumulatedToolCalls.values()).map((c) => ({
1328
+ id: c.id,
1329
+ name: c.name,
1330
+ arguments: c.args
1331
+ }));
1332
+ yield { type: "tool_call", toolCalls: calls };
1333
+ }
1334
+ yield { type: "done", finishReason: choice.finish_reason };
1335
+ return;
1336
+ }
1337
+ const delta = choice?.delta;
1338
+ if (!delta) continue;
1339
+ if (delta.content) {
1340
+ yield { type: "delta", delta: delta.content };
1341
+ }
1342
+ if (delta.tool_calls) {
1343
+ for (const tc of delta.tool_calls) {
1344
+ const idx = tc.index ?? 0;
1345
+ let existing = accumulatedToolCalls.get(idx);
1346
+ if (!existing) {
1347
+ if (!tc.function?.name) continue;
1348
+ existing = { id: tc.id ?? "", name: tc.function.name, args: "" };
1349
+ accumulatedToolCalls.set(idx, existing);
1350
+ }
1351
+ if (tc.id) existing.id = tc.id;
1352
+ if (tc.function?.name) existing.name = tc.function.name;
1353
+ if (tc.function?.arguments) existing.args += tc.function.arguments;
1354
+ }
1355
+ }
1356
+ } catch {
1357
+ }
1358
+ }
1359
+ }
1360
+ if (accumulatedToolCalls.size > 0) {
1361
+ const calls = Array.from(accumulatedToolCalls.values()).map((c) => ({
1362
+ id: c.id,
1363
+ name: c.name,
1364
+ arguments: c.args
1365
+ }));
1366
+ yield { type: "tool_call", toolCalls: calls };
1367
+ }
1368
+ yield { type: "done", finishReason: "stop" };
1369
+ }
1370
+ async function* streamAnthropic(baseUrl, apiKey, model, messages, tools, toolResults) {
1371
+ const systemMsgs = messages.filter((m) => m.role === "system");
1372
+ const chatMsgs = messages.filter((m) => m.role !== "system").map((m) => {
1373
+ if (m.role === "tool") {
1374
+ return {
1375
+ role: "user",
1376
+ content: [{ type: "tool_result", tool_use_id: m.tool_call_id, content: m.content }]
1377
+ };
1378
+ }
1379
+ if (m.tool_calls) {
1380
+ return {
1381
+ role: "assistant",
1382
+ content: m.tool_calls.map((tc) => ({
1383
+ type: "tool_use",
1384
+ id: tc.id,
1385
+ name: tc.function.name,
1386
+ input: JSON.parse(tc.function.arguments || "{}")
1387
+ }))
1388
+ };
1389
+ }
1390
+ return { role: m.role, content: m.content };
1391
+ });
1392
+ const body = {
1393
+ model,
1394
+ max_tokens: 4096,
1395
+ messages: chatMsgs,
1396
+ stream: true
1397
+ };
1398
+ if (systemMsgs.length > 0) {
1399
+ body.system = systemMsgs.map((m) => ({ type: "text", text: m.content }));
1400
+ }
1401
+ if (tools && tools.length > 0) {
1402
+ body.tools = tools.map((t) => ({
1403
+ name: t.name,
1404
+ description: t.description,
1405
+ input_schema: t.input_schema || { type: "object", properties: {} }
1406
+ }));
1407
+ }
1408
+ const res = await fetch(`${baseUrl}/messages`, {
1409
+ method: "POST",
1410
+ headers: {
1411
+ "Content-Type": "application/json",
1412
+ "x-api-key": apiKey,
1413
+ "anthropic-version": "2023-06-01"
1414
+ },
1415
+ body: JSON.stringify(body)
1416
+ });
1417
+ if (!res.ok) {
1418
+ const err = await res.text().catch(() => "");
1419
+ yield { type: "error", error: `Anthropic API error (${res.status}): ${err.slice(0, 300)}` };
1420
+ return;
1421
+ }
1422
+ const reader = res.body?.getReader();
1423
+ if (!reader) {
1424
+ yield { type: "error", error: "No response body" };
1425
+ return;
1426
+ }
1427
+ const decoder = new TextDecoder();
1428
+ let buffer = "";
1429
+ let currentEvent = "";
1430
+ let accumulatedToolCalls = /* @__PURE__ */ new Map();
1431
+ let inputJsonBuffer = "";
1432
+ while (true) {
1433
+ const { done, value } = await reader.read();
1434
+ if (done) break;
1435
+ buffer += decoder.decode(value, { stream: true });
1436
+ const lines = buffer.split("\n");
1437
+ buffer = lines.pop() ?? "";
1438
+ for (const line of lines) {
1439
+ if (line.startsWith("event: ")) {
1440
+ currentEvent = line.slice(7);
1441
+ if (currentEvent === "content_block_start" || currentEvent === "content_block_delta") {
1442
+ inputJsonBuffer = "";
1443
+ }
1444
+ } else if (line.startsWith("data: ")) {
1445
+ const payload = line.slice(6).trim();
1446
+ if (!payload) continue;
1447
+ try {
1448
+ const parsed = JSON.parse(payload);
1449
+ if (currentEvent === "content_block_start" && parsed.content_block) {
1450
+ if (parsed.content_block.type === "tool_use") {
1451
+ accumulatedToolCalls.set(parsed.index, {
1452
+ id: parsed.content_block.id,
1453
+ name: parsed.content_block.name,
1454
+ args: ""
1455
+ });
1456
+ }
1457
+ } else if (currentEvent === "content_block_delta" && parsed.delta) {
1458
+ if (parsed.delta.type === "text_delta" && parsed.delta.text) {
1459
+ yield { type: "delta", delta: parsed.delta.text };
1460
+ } else if (parsed.delta.type === "input_json_delta" && parsed.delta.partial_json) {
1461
+ for (const [idx, tc] of accumulatedToolCalls) {
1462
+ if (String(parsed.index) === String(idx)) {
1463
+ tc.args += parsed.delta.partial_json;
1464
+ }
1465
+ }
1466
+ }
1467
+ } else if (currentEvent === "message_start") {
1468
+ } else if (currentEvent === "message_delta") {
1469
+ if (parsed.delta?.stop_reason === "tool_use" || parsed.delta?.stop_reason === "end_turn") {
1470
+ }
1471
+ if (parsed.delta?.stop_reason === "end_turn" && accumulatedToolCalls.size > 0) {
1472
+ const calls = Array.from(accumulatedToolCalls.values()).map((c) => ({
1473
+ id: c.id,
1474
+ name: c.name,
1475
+ arguments: c.args
1476
+ }));
1477
+ yield { type: "tool_call", toolCalls: calls };
1478
+ accumulatedToolCalls.clear();
1479
+ }
1480
+ } else if (currentEvent === "message_stop") {
1481
+ if (accumulatedToolCalls.size > 0) {
1482
+ const calls = Array.from(accumulatedToolCalls.values()).map((c) => ({
1483
+ id: c.id,
1484
+ name: c.name,
1485
+ arguments: c.args
1486
+ }));
1487
+ yield { type: "tool_call", toolCalls: calls };
1488
+ accumulatedToolCalls.clear();
1489
+ }
1490
+ yield { type: "done", finishReason: "stop" };
1491
+ return;
1492
+ }
1493
+ } catch {
1494
+ }
1495
+ }
1496
+ }
1497
+ }
1498
+ yield { type: "done", finishReason: "stop" };
1499
+ }
1500
+ async function* streamGoogle(baseUrl, apiKey, model, messages, tools, toolResults) {
1501
+ const contents = messages.filter((m) => m.role !== "system").map((m) => {
1502
+ const role = m.role === "assistant" ? "model" : m.role === "tool" ? "user" : m.role;
1503
+ return {
1504
+ role,
1505
+ parts: [{ text: m.content }]
1506
+ };
1507
+ });
1508
+ const body = {
1509
+ contents,
1510
+ generationConfig: { temperature: 0.7 }
1511
+ };
1512
+ if (tools && tools.length > 0) {
1513
+ body.tools = tools.map((t) => ({
1514
+ functionDeclarations: [{
1515
+ name: t.name,
1516
+ description: t.description,
1517
+ parameters: t.input_schema || {}
1518
+ }]
1519
+ }));
1520
+ }
1521
+ const res = await fetch(
1522
+ `${baseUrl}/models/${model}:streamGenerateContent?key=${apiKey}&alt=sse`,
1523
+ {
1524
+ method: "POST",
1525
+ headers: { "Content-Type": "application/json" },
1526
+ body: JSON.stringify(body)
1527
+ }
1528
+ );
1529
+ if (!res.ok) {
1530
+ const err = await res.text().catch(() => "");
1531
+ yield { type: "error", error: `Google API error (${res.status}): ${err.slice(0, 300)}` };
1532
+ return;
1533
+ }
1534
+ const reader = res.body?.getReader();
1535
+ if (!reader) {
1536
+ yield { type: "error", error: "No response body" };
1537
+ return;
1538
+ }
1539
+ const decoder = new TextDecoder();
1540
+ let buffer = "";
1541
+ while (true) {
1542
+ const { done, value } = await reader.read();
1543
+ if (done) break;
1544
+ buffer += decoder.decode(value, { stream: true });
1545
+ const lines = buffer.split("\n");
1546
+ buffer = lines.pop() ?? "";
1547
+ for (const line of lines) {
1548
+ if (!line.startsWith("data: ")) continue;
1549
+ const payload = line.slice(6).trim();
1550
+ if (!payload) continue;
1551
+ try {
1552
+ const parsed = JSON.parse(payload);
1553
+ const candidate = parsed.candidates?.[0];
1554
+ if (!candidate) continue;
1555
+ const part = candidate.content?.parts?.[0];
1556
+ if (part?.text) {
1557
+ yield { type: "delta", delta: part.text };
1558
+ }
1559
+ if (candidate.finishReason) {
1560
+ yield { type: "done", finishReason: candidate.finishReason.toLowerCase() };
1561
+ return;
1562
+ }
1563
+ } catch {
1564
+ }
1565
+ }
1566
+ }
1567
+ yield { type: "done", finishReason: "stop" };
1568
+ }
1569
+
1570
+ // src/server/index.ts
1571
+ var sessions = /* @__PURE__ */ new Map();
1572
+ var sessionCounter = 0;
1573
+ function nextId() {
1574
+ return `local-${++sessionCounter}-${Date.now().toString(36)}`;
1575
+ }
1576
+ function readBody(req) {
1577
+ return new Promise((resolve2, reject) => {
1578
+ const chunks = [];
1579
+ req.on("data", (c) => chunks.push(c));
1580
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString()));
1581
+ req.on("error", reject);
1582
+ });
1583
+ }
1584
+ function writeSSE(res, event, data) {
1585
+ res.write(`event: ${event}
1586
+ data: ${JSON.stringify(data)}
1587
+
1588
+ `);
1589
+ }
1590
+ function parseUrl(url) {
1591
+ const idx = url.indexOf("?");
1592
+ return { pathname: idx === -1 ? url : url.slice(0, idx) };
1593
+ }
1594
+ async function handleSessionsRequest(req, res) {
1595
+ const { pathname } = parseUrl(req.url ?? "/");
1596
+ const sessionMatch = pathname.match(/^\/v1\/sessions\/([^/]+)\/turns$/);
1597
+ const approveMatch = pathname.match(/^\/v1\/sessions\/([^/]+)\/approve$/);
1598
+ const createMatch = pathname === "/v1/sessions" && req.method === "POST";
1599
+ if (createMatch) {
1600
+ return handleCreateSession(req, res);
1601
+ }
1602
+ if (sessionMatch && req.method === "POST") {
1603
+ return handleTurn(req, res, sessionMatch[1]);
1604
+ }
1605
+ if (approveMatch && req.method === "POST") {
1606
+ return handleApprove(req, res, approveMatch[1]);
1607
+ }
1608
+ return false;
1609
+ }
1610
+ async function handleCreateSession(req, res) {
1611
+ const body = JSON.parse(await readBody(req));
1612
+ const id = nextId();
1613
+ const session = {
1614
+ id,
1615
+ agentId: body.agent_id ?? "unnamed",
1616
+ model: body.metadata?.model ?? "",
1617
+ instructions: body.metadata?.instructions ?? "",
1618
+ messages: []
1619
+ };
1620
+ sessions.set(id, session);
1621
+ res.writeHead(200, { "Content-Type": "application/json" });
1622
+ res.end(JSON.stringify({ id }));
1623
+ return true;
1624
+ }
1625
+ async function handleTurn(req, res, sessionId) {
1626
+ const session = sessions.get(sessionId);
1627
+ if (!session) {
1628
+ res.writeHead(404, { "Content-Type": "application/json" });
1629
+ res.end(JSON.stringify({ error: "Session not found" }));
1630
+ return true;
1631
+ }
1632
+ const body = JSON.parse(await readBody(req));
1633
+ const model = body.model || session.model;
1634
+ const input = body.input ?? "";
1635
+ const instructions = body.instructions || session.instructions;
1636
+ const stream = body.stream !== false;
1637
+ const pauseOnTools = body.pause_on_tool_calls !== false;
1638
+ const tools = body.tools ?? [];
1639
+ session.model = model;
1640
+ session.instructions = instructions;
1641
+ if (tools.length > 0) session.tools = tools;
1642
+ const messages = [];
1643
+ if (instructions) {
1644
+ messages.push({ role: "system", content: instructions });
1645
+ }
1646
+ for (const msg of session.messages) {
1647
+ messages.push(msg);
1648
+ }
1649
+ const turnNumber = session.messages.filter((m) => m.role === "user").length + 1;
1650
+ const userMsg = { role: "user", content: input };
1651
+ messages.push(userMsg);
1652
+ if (stream) {
1653
+ res.writeHead(200, {
1654
+ "Content-Type": "text/event-stream",
1655
+ "Cache-Control": "no-cache",
1656
+ Connection: "keep-alive",
1657
+ "X-Accel-Buffering": "no"
1658
+ });
1659
+ writeSSE(res, "turn.started", { turn_number: turnNumber, input_text: input });
1660
+ writeSSE(res, "message.received", { input_text: input, turn_number: turnNumber });
1661
+ writeSSE(res, "step.started", { step_number: 1, turn_number: turnNumber });
1662
+ let textSoFar = "";
1663
+ let hadToolCalls = false;
1664
+ let messagePushed = false;
1665
+ try {
1666
+ for await (const event of streamLlm(model, messages, tools.length > 0 ? tools : void 0)) {
1667
+ if (event.type === "delta" && event.delta) {
1668
+ textSoFar += event.delta;
1669
+ writeSSE(res, "output_text.delta", { delta: event.delta, text: textSoFar });
1670
+ }
1671
+ if (event.type === "tool_call" && event.toolCalls) {
1672
+ hadToolCalls = true;
1673
+ session.pendingToolCalls = event.toolCalls;
1674
+ for (const tc of event.toolCalls) {
1675
+ writeSSE(res, "tool_call.started", {
1676
+ tool: tc.name,
1677
+ arguments: JSON.parse(tc.arguments || "{}"),
1678
+ action_id: tc.id
1679
+ });
1680
+ }
1681
+ const toolCallMsg = {
1682
+ role: "assistant",
1683
+ content: textSoFar,
1684
+ tool_calls: event.toolCalls.map((tc) => ({
1685
+ id: tc.id,
1686
+ type: "function",
1687
+ function: { name: tc.name, arguments: tc.arguments }
1688
+ }))
1689
+ };
1690
+ if (pauseOnTools) {
1691
+ session.messages.push(toolCallMsg);
1692
+ messagePushed = true;
1693
+ writeSSE(res, "turn.paused", { text: textSoFar });
1694
+ res.end();
1695
+ return true;
1696
+ }
1697
+ session.messages.push(toolCallMsg);
1698
+ messagePushed = true;
1699
+ }
1700
+ if (event.type === "done") {
1701
+ if (!messagePushed) {
1702
+ session.messages.push({ role: "assistant", content: textSoFar });
1703
+ messagePushed = true;
1704
+ }
1705
+ writeSSE(res, "message.completed", {
1706
+ text: textSoFar,
1707
+ finish_reason: event.finishReason ?? "stop",
1708
+ step_number: 1,
1709
+ turn_number: turnNumber
1710
+ });
1711
+ writeSSE(res, "step.completed", {
1712
+ finish_reason: event.finishReason ?? "stop",
1713
+ step_number: 1,
1714
+ turn_number: turnNumber
1715
+ });
1716
+ writeSSE(res, "turn.completed", { turn_number: turnNumber, turn_id: sessionId });
1717
+ }
1718
+ if (event.type === "error") {
1719
+ writeSSE(res, "turn.completed", { turn_number: turnNumber, turn_id: sessionId });
1720
+ writeSSE(res, "error", { message: event.error });
1721
+ }
1722
+ }
1723
+ } catch (err) {
1724
+ const msg = err instanceof Error ? err.message : String(err);
1725
+ writeSSE(res, "error", { message: msg });
1726
+ }
1727
+ res.end();
1728
+ } else {
1729
+ let fullText = "";
1730
+ let messagePushed = false;
1731
+ try {
1732
+ for await (const event of streamLlm(model, messages, tools.length > 0 ? tools : void 0)) {
1733
+ if (event.type === "delta" && event.delta) {
1734
+ fullText += event.delta;
1735
+ }
1736
+ if (event.type === "tool_call" && event.toolCalls) {
1737
+ session.pendingToolCalls = event.toolCalls;
1738
+ }
1739
+ }
1740
+ } catch {
1741
+ }
1742
+ if (!messagePushed) {
1743
+ session.messages.push({ role: "assistant", content: fullText });
1744
+ }
1745
+ res.writeHead(200, { "Content-Type": "application/json" });
1746
+ res.end(JSON.stringify({ output: fullText, turn_id: sessionId }));
1747
+ }
1748
+ return true;
1749
+ }
1750
+ async function handleApprove(req, res, sessionId) {
1751
+ const session = sessions.get(sessionId);
1752
+ if (!session) {
1753
+ res.writeHead(404, { "Content-Type": "application/json" });
1754
+ res.end(JSON.stringify({ error: "Session not found" }));
1755
+ return true;
1756
+ }
1757
+ const body = JSON.parse(await readBody(req));
1758
+ const toolResults = body.tool_results ?? [];
1759
+ const actionId = body.action_id ?? toolResults[0]?.action_id ?? "";
1760
+ if (!session.pendingToolCalls || session.pendingToolCalls.length === 0) {
1761
+ res.writeHead(400, { "Content-Type": "application/json" });
1762
+ res.end(JSON.stringify({ error: "No pending tool calls" }));
1763
+ return true;
1764
+ }
1765
+ const toolResultMessages = toolResults.map((r) => ({
1766
+ role: "tool",
1767
+ tool_call_id: r.action_id,
1768
+ content: r.output
1769
+ }));
1770
+ for (const msg of toolResultMessages) {
1771
+ session.messages.push(msg);
1772
+ }
1773
+ session.pendingToolCalls = void 0;
1774
+ const turnNumber = session.messages.filter((m) => m.role === "user").length;
1775
+ res.writeHead(200, {
1776
+ "Content-Type": "text/event-stream",
1777
+ "Cache-Control": "no-cache",
1778
+ Connection: "keep-alive",
1779
+ "X-Accel-Buffering": "no"
1780
+ });
1781
+ writeSSE(res, "turn.resumed", {});
1782
+ let textSoFar = "";
1783
+ let hadToolCalls = false;
1784
+ let messagePushed = false;
1785
+ try {
1786
+ for await (const event of streamLlm(
1787
+ session.model,
1788
+ session.messages,
1789
+ session.tools,
1790
+ toolResults
1791
+ )) {
1792
+ if (event.type === "delta" && event.delta) {
1793
+ textSoFar += event.delta;
1794
+ writeSSE(res, "output_text.delta", { delta: event.delta, text: textSoFar });
1795
+ }
1796
+ if (event.type === "tool_call" && event.toolCalls) {
1797
+ hadToolCalls = true;
1798
+ session.pendingToolCalls = event.toolCalls;
1799
+ for (const tc of event.toolCalls) {
1800
+ writeSSE(res, "tool_call.started", {
1801
+ tool: tc.name,
1802
+ arguments: JSON.parse(tc.arguments || "{}"),
1803
+ action_id: tc.id
1804
+ });
1805
+ }
1806
+ session.messages.push({
1807
+ role: "assistant",
1808
+ content: textSoFar,
1809
+ tool_calls: event.toolCalls.map((tc) => ({
1810
+ id: tc.id,
1811
+ type: "function",
1812
+ function: { name: tc.name, arguments: tc.arguments }
1813
+ }))
1814
+ });
1815
+ messagePushed = true;
1816
+ writeSSE(res, "turn.paused", { text: textSoFar });
1817
+ res.end();
1818
+ return true;
1819
+ }
1820
+ if (event.type === "done") {
1821
+ if (!messagePushed) {
1822
+ session.messages.push({ role: "assistant", content: textSoFar });
1823
+ messagePushed = true;
1824
+ }
1825
+ writeSSE(res, "message.completed", {
1826
+ text: textSoFar,
1827
+ finish_reason: event.finishReason ?? "stop"
1828
+ });
1829
+ writeSSE(res, "turn.completed", { turn_number: turnNumber, turn_id: sessionId });
1830
+ }
1831
+ if (event.type === "error") {
1832
+ writeSSE(res, "error", { message: event.error });
1833
+ }
1834
+ }
1835
+ } catch (err) {
1836
+ const msg = err instanceof Error ? err.message : String(err);
1837
+ writeSSE(res, "error", { message: msg });
1838
+ }
1839
+ res.end();
1840
+ return true;
1841
+ }
1842
+
1843
+ // src/cli/dev.ts
1844
+ function checkProviderKeys(modelId) {
1845
+ const provider = modelId.split("/")[0];
1846
+ const missing = [];
1847
+ const keyMap = {
1848
+ openai: "OPENAI_API_KEY",
1849
+ anthropic: "ANTHROPIC_API_KEY",
1850
+ groq: "GROQ_API_KEY",
1851
+ deepseek: "DEEPSEEK_API_KEY",
1852
+ mistral: "MISTRAL_API_KEY",
1853
+ google: "GOOGLE_API_KEY",
1854
+ meta: "TOGETHER_API_KEY"
1855
+ };
1856
+ const envVar = keyMap[provider];
1857
+ if (envVar && !process.env[envVar] && !getProviderApiKey(provider)) {
1858
+ missing.push(envVar);
1859
+ }
1860
+ return missing;
1861
+ }
1862
+ async function devCommand(options) {
1863
+ const agentDirPath = resolve(process.cwd(), options.agentDir);
1864
+ const port = parseInt(options.port, 10);
1865
+ const localApiUrl = `http://127.0.0.1:${port}/v1`;
1866
+ if (!process.env.CENCORI_API_KEY) {
1867
+ process.env.CENCORI_API_KEY = "local-dev-key";
1868
+ }
1869
+ if (!process.env.CENCORI_API_URL) {
1870
+ process.env.CENCORI_API_URL = localApiUrl;
1871
+ }
1872
+ if (!options.input) {
1873
+ showHeader();
1874
+ const { diagnostics } = discoverAgent(agentDirPath);
1875
+ if (diagnostics.some((d) => d.severity === "error")) {
1876
+ for (const d of diagnostics) {
1877
+ console.error(` ${grey("\u2716")} ${d.code}: ${d.message}`);
1878
+ }
1879
+ process.exit(1);
1880
+ }
1881
+ for (const d of diagnostics) {
1882
+ console.warn(` ${grey("\u26A0")} ${d.code}: ${d.message}`);
1883
+ }
1884
+ try {
1885
+ const agent = await loadAgent(agentDirPath);
1886
+ console.log(` ${agentDirPath} ${grey("\xB7")} ${grey(agent.manifest.config.model)}`);
1887
+ console.log();
1888
+ console.log(` ${dimmed(`http://localhost:${port}`)}`);
1889
+ console.log();
1890
+ console.log(` ${dimmed(`$ curl -X POST http://localhost:${port} \\`)}`);
1891
+ console.log(` ${dimmed(` -H "Content-Type: application/json" \\`)}`);
1892
+ console.log(` ${dimmed(` -d '{"message": "hello"}'`)}`);
1893
+ console.log();
1894
+ const missing = checkProviderKeys(agent.manifest.config.model);
1895
+ if (missing.length > 0) {
1896
+ console.log(` ${grey("\u26A0")} Missing API keys: ${missing.join(", ")}`);
1897
+ console.log(` ${dimmed(" Set them in .env.local or your environment")}`);
1898
+ console.log();
1899
+ }
1900
+ } catch {
1901
+ console.log(` ${agentDirPath}`);
1902
+ console.log();
1903
+ console.log(` ${dimmed(`http://localhost:${port}`)}`);
1904
+ console.log();
1905
+ }
1906
+ }
1907
+ const server = createServer(async (req, res) => {
1908
+ if (await handleSessionsRequest(req, res)) {
1909
+ return;
1910
+ }
1911
+ if (req.method === "POST" && req.url === "/") {
1912
+ let body = "";
1913
+ for await (const chunk of req) body += chunk;
1914
+ try {
1915
+ const { message, stream } = JSON.parse(body);
1916
+ if (stream) {
1917
+ res.writeHead(200, {
1918
+ "Content-Type": "application/x-ndjson",
1919
+ "Cache-Control": "no-cache",
1920
+ Connection: "keep-alive"
1921
+ });
1922
+ for await (const event of streamAgent(agentDirPath, message)) {
1923
+ res.write(JSON.stringify(event) + "\n");
1924
+ }
1925
+ res.end();
1926
+ } else {
1927
+ res.writeHead(200, { "Content-Type": "application/json" });
1928
+ for await (const _event of streamAgent(agentDirPath, message)) {
1929
+ }
1930
+ res.end(JSON.stringify({ status: "ok" }));
1931
+ }
1932
+ } catch (err) {
1933
+ res.writeHead(500, { "Content-Type": "application/json" });
1934
+ res.end(JSON.stringify({ error: String(err) }));
1935
+ }
1936
+ } else {
1937
+ res.writeHead(404);
1938
+ res.end("Not found");
1939
+ }
1940
+ });
1941
+ server.on("error", (err) => {
1942
+ if (err.code === "EADDRINUSE") {
1943
+ console.error();
1944
+ console.error(` ${grey("\u2717")} port ${port} is already in use`);
1945
+ console.error(` ${dimmed(`try: arcie dev --port ${port + 1}`)}`);
1946
+ console.error(` ${dimmed(`or: lsof -iTCP:${port} -sTCP:LISTEN -n -P # find and kill the holder`)}`);
1947
+ console.error();
1948
+ } else {
1949
+ console.error();
1950
+ console.error(` ${grey("\u2717")} ${err.message}`);
1951
+ console.error();
1952
+ }
1953
+ process.exit(1);
1954
+ });
1955
+ if (options.input) {
1956
+ server.listen(port, () => {
1957
+ void startBlockChat({ agentDir: agentDirPath });
1958
+ });
1959
+ } else {
1960
+ server.listen(port, () => {
1961
+ });
1962
+ }
1963
+ }
1964
+ export {
1965
+ devCommand
1966
+ };
1967
+ //# sourceMappingURL=dev-ZZDVOWUZ.js.map