@robota-sdk/agent-cli 3.0.0-beta.54 → 3.0.0-beta.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4372 @@
1
+ import {
2
+ DEFAULT_PROVIDER_DEFINITIONS,
3
+ createProviderFromSettings,
4
+ findProviderDefinition,
5
+ formatSupportedProviderTypes,
6
+ isSubagentWorkerChildMessage,
7
+ readMergedProviderSettings,
8
+ readProviderSettings
9
+ } from "./chunk-2JAZDNYT.js";
10
+
11
+ // src/background/managed-shell-process-runner.ts
12
+ import { spawn } from "child_process";
13
+ import {
14
+ BackgroundTaskError
15
+ } from "@robota-sdk/agent-sdk";
16
+ var DEFAULT_OUTPUT_LIMIT_BYTES = 3e4;
17
+ var DEFAULT_KILL_GRACE_MS = 2e3;
18
+ var LOG_PAGE_SIZE = 200;
19
+ function createCapture(limitBytes) {
20
+ const chunks = [];
21
+ let capturedBytes = 0;
22
+ let truncated = false;
23
+ return {
24
+ appendOutput(text) {
25
+ if (truncated) return;
26
+ const remaining = limitBytes - capturedBytes;
27
+ const buffer = Buffer.from(text, "utf8");
28
+ if (buffer.byteLength <= remaining) {
29
+ chunks.push(text);
30
+ capturedBytes += buffer.byteLength;
31
+ return;
32
+ }
33
+ chunks.push(buffer.subarray(0, Math.max(remaining, 0)).toString("utf8"));
34
+ chunks.push("\n[output truncated]\n");
35
+ truncated = true;
36
+ },
37
+ getOutput() {
38
+ return chunks.join("");
39
+ }
40
+ };
41
+ }
42
+ function appendLog(lines, source, text) {
43
+ for (const line of text.split(/\r?\n/)) {
44
+ if (line.length > 0) lines.push(`[${source}] ${line}`);
45
+ }
46
+ }
47
+ function resolveShell(request) {
48
+ return { command: request.shell ?? "sh", args: ["-c", request.command] };
49
+ }
50
+ function sendInput(child, input) {
51
+ return new Promise((resolve, reject) => {
52
+ const onError = (error) => {
53
+ child.stdin.off("error", onError);
54
+ reject(error);
55
+ };
56
+ child.stdin.once("error", onError);
57
+ child.stdin.end(input, () => {
58
+ child.stdin.off("error", onError);
59
+ resolve();
60
+ });
61
+ });
62
+ }
63
+ function createManagedShellProcessRunner(options = {}) {
64
+ const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
65
+ return {
66
+ kind: "process",
67
+ start(task) {
68
+ if (task.request.kind !== "process") {
69
+ throw new BackgroundTaskError("runner", `Invalid process task kind: ${task.request.kind}`);
70
+ }
71
+ return startProcessTask(task.taskId, task.request, killGraceMs);
72
+ }
73
+ };
74
+ }
75
+ function startProcessTask(taskId, request, killGraceMs) {
76
+ const shell = resolveShell(request);
77
+ const runtime = {
78
+ taskId,
79
+ request,
80
+ child: spawn(shell.command, shell.args, {
81
+ cwd: request.cwd,
82
+ env: { ...process.env, ...request.env ?? {} },
83
+ stdio: ["pipe", "pipe", "pipe"]
84
+ }),
85
+ logs: [],
86
+ capture: createCapture(request.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES),
87
+ killGraceMs
88
+ };
89
+ const result = createProcessResult(runtime);
90
+ return createProcessHandle(runtime, result);
91
+ }
92
+ function createProcessResult(runtime) {
93
+ let settled = false;
94
+ return new Promise((resolve, reject) => {
95
+ const timeoutTimer = runtime.request.timeoutMs ? setTimeout(() => {
96
+ appendLog(runtime.logs, "system", `timed out after ${runtime.request.timeoutMs}ms`);
97
+ runtime.child.kill("SIGTERM");
98
+ rejectOnceLocal(new BackgroundTaskError("timeout", "Background process timed out"));
99
+ }, runtime.request.timeoutMs) : void 0;
100
+ function clearTimers() {
101
+ if (timeoutTimer) clearTimeout(timeoutTimer);
102
+ if (runtime.killTimer) clearTimeout(runtime.killTimer);
103
+ }
104
+ function resolveOnce(exitCode, signalCode) {
105
+ if (settled) return;
106
+ settled = true;
107
+ clearTimers();
108
+ resolve({
109
+ taskId: runtime.taskId,
110
+ kind: "process",
111
+ output: runtime.capture.getOutput(),
112
+ exitCode,
113
+ signalCode
114
+ });
115
+ }
116
+ function rejectOnceLocal(error) {
117
+ if (settled) return;
118
+ settled = true;
119
+ clearTimers();
120
+ reject(error);
121
+ }
122
+ attachOutputListeners(runtime);
123
+ runtime.child.on("error", (error) => {
124
+ appendLog(runtime.logs, "system", error.message);
125
+ rejectOnceLocal(new BackgroundTaskError("process", error.message));
126
+ });
127
+ runtime.child.on("close", (code, signal) => {
128
+ resolveOnce(code ?? void 0, signal ?? void 0);
129
+ });
130
+ if (runtime.request.stdin) {
131
+ runtime.child.stdin.write(runtime.request.stdin);
132
+ runtime.child.stdin.end();
133
+ }
134
+ });
135
+ }
136
+ function createProcessHandle(runtime, result) {
137
+ return {
138
+ taskId: runtime.taskId,
139
+ ...runtime.child.pid !== void 0 ? { pid: runtime.child.pid } : {},
140
+ result,
141
+ cancel: async (reason) => {
142
+ cancelProcess(runtime, reason);
143
+ },
144
+ send: async (input) => {
145
+ if (!input.stdin) return;
146
+ await sendInput(runtime.child, input.stdin);
147
+ },
148
+ readLog: (cursor) => Promise.resolve(readProcessLog(runtime, cursor))
149
+ };
150
+ }
151
+ function attachOutputListeners(runtime) {
152
+ runtime.child.stdout.on("data", (chunk) => {
153
+ const text = chunk.toString("utf8");
154
+ runtime.capture.appendOutput(text);
155
+ appendLog(runtime.logs, "stdout", text);
156
+ });
157
+ runtime.child.stderr.on("data", (chunk) => {
158
+ const text = chunk.toString("utf8");
159
+ runtime.capture.appendOutput(text);
160
+ appendLog(runtime.logs, "stderr", text);
161
+ });
162
+ }
163
+ function cancelProcess(runtime, reason) {
164
+ appendLog(runtime.logs, "system", reason ? `cancel requested: ${reason}` : "cancel requested");
165
+ if (!runtime.child.killed) runtime.child.kill("SIGTERM");
166
+ runtime.killTimer = setTimeout(() => {
167
+ if (!runtime.child.killed) runtime.child.kill("SIGKILL");
168
+ }, runtime.killGraceMs);
169
+ }
170
+ function readProcessLog(runtime, cursor) {
171
+ const offset = cursor?.offset ?? 0;
172
+ const nextOffset = Math.min(offset + LOG_PAGE_SIZE, runtime.logs.length);
173
+ return {
174
+ taskId: runtime.taskId,
175
+ cursor,
176
+ nextCursor: nextOffset < runtime.logs.length ? { offset: nextOffset } : void 0,
177
+ lines: runtime.logs.slice(offset, nextOffset)
178
+ };
179
+ }
180
+
181
+ // src/subagents/git-worktree-isolation-adapter.ts
182
+ import { execFileSync } from "child_process";
183
+ import { randomUUID } from "crypto";
184
+ import { mkdirSync } from "fs";
185
+ import { join } from "path";
186
+ import {
187
+ BackgroundTaskError as BackgroundTaskError2
188
+ } from "@robota-sdk/agent-sdk";
189
+ var WORKTREE_DIR = ".robota/worktrees";
190
+ var BRANCH_PREFIX = "robota";
191
+ var GIT_ENCODING = "utf8";
192
+ var SHORT_ID_LENGTH = 8;
193
+ function createGitWorktreeIsolationAdapter(options) {
194
+ return new GitWorktreeIsolationAdapter(options);
195
+ }
196
+ var GitWorktreeIsolationAdapter = class {
197
+ worktreeDir;
198
+ branchPrefix;
199
+ constructor(options = {}) {
200
+ this.worktreeDir = options.worktreeDir ?? WORKTREE_DIR;
201
+ this.branchPrefix = options.branchPrefix ?? BRANCH_PREFIX;
202
+ }
203
+ prepare(request) {
204
+ const repoRoot = runGit(request.cwd, ["rev-parse", "--show-toplevel"]).trim();
205
+ const shortId = randomUUID().slice(0, SHORT_ID_LENGTH);
206
+ const branchName = `${this.branchPrefix}/${request.jobId}-${shortId}`;
207
+ const worktreeRoot = join(repoRoot, this.worktreeDir);
208
+ const worktreePath = join(worktreeRoot, `${request.jobId}-${shortId}`);
209
+ mkdirSync(worktreeRoot, { recursive: true });
210
+ runGit(repoRoot, ["worktree", "add", "-b", branchName, worktreePath, "HEAD"]);
211
+ return { repoRoot, worktreePath, branchName };
212
+ }
213
+ isClean(worktree) {
214
+ return runGit(worktree.worktreePath, ["status", "--porcelain"]).trim().length === 0;
215
+ }
216
+ remove(worktree) {
217
+ runGit(worktree.repoRoot, ["worktree", "remove", "--force", worktree.worktreePath]);
218
+ runGit(worktree.repoRoot, ["branch", "-D", worktree.branchName]);
219
+ }
220
+ };
221
+ function runGit(cwd, args) {
222
+ try {
223
+ return execFileSync("git", args, {
224
+ cwd,
225
+ encoding: GIT_ENCODING,
226
+ stdio: ["ignore", "pipe", "pipe"]
227
+ });
228
+ } catch (error) {
229
+ const message = error instanceof Error ? error.message : String(error);
230
+ throw new BackgroundTaskError2("runner", `git ${args.join(" ")} failed: ${message}`);
231
+ }
232
+ }
233
+
234
+ // src/subagents/child-process-subagent-runner.ts
235
+ import { fork } from "child_process";
236
+ import { existsSync, readFileSync } from "fs";
237
+ import { dirname, join as join2 } from "path";
238
+ import {
239
+ BackgroundTaskError as BackgroundTaskError5,
240
+ getBuiltInAgent,
241
+ createWorktreeSubagentRunner
242
+ } from "@robota-sdk/agent-sdk";
243
+
244
+ // src/subagents/child-process-subagent-runner-result.ts
245
+ import {
246
+ BackgroundTaskError as BackgroundTaskError4
247
+ } from "@robota-sdk/agent-sdk";
248
+
249
+ // src/subagents/child-process-subagent-transport.ts
250
+ import {
251
+ BackgroundTaskError as BackgroundTaskError3
252
+ } from "@robota-sdk/agent-sdk";
253
+ function handleWorkerMessage(message, startWorker, resolveOnce, rejectOnce, emit) {
254
+ switch (message.type) {
255
+ case "ready":
256
+ startWorker();
257
+ break;
258
+ case "result":
259
+ resolveOnce(message.output);
260
+ break;
261
+ case "error":
262
+ rejectOnce(new BackgroundTaskError3("runner", message.message));
263
+ break;
264
+ case "cancelled":
265
+ rejectOnce(new BackgroundTaskError3("runner", message.reason ?? "Subagent worker cancelled"));
266
+ break;
267
+ case "text_delta":
268
+ emit?.({ type: "background_task_text_delta", delta: message.delta });
269
+ break;
270
+ case "tool_start":
271
+ emit?.({
272
+ type: "background_task_tool_start",
273
+ toolName: message.toolName,
274
+ firstArg: extractFirstArg(message.toolArgs)
275
+ });
276
+ break;
277
+ case "tool_end":
278
+ emit?.({
279
+ type: "background_task_tool_end",
280
+ toolName: message.toolName,
281
+ success: message.success
282
+ });
283
+ break;
284
+ default:
285
+ rejectOnce(new BackgroundTaskError3("runner", "Unhandled subagent worker message"));
286
+ }
287
+ }
288
+ function extractFirstArg(toolArgs) {
289
+ if (!toolArgs) return void 0;
290
+ const firstValue = Object.values(toolArgs)[0];
291
+ if (firstValue === void 0) return void 0;
292
+ return typeof firstValue === "object" ? JSON.stringify(firstValue) : String(firstValue);
293
+ }
294
+ function sendWorkerMessage(child, message) {
295
+ return new Promise((resolve, reject) => {
296
+ if (!child.connected) {
297
+ reject(new BackgroundTaskError3("crash", "Subagent worker IPC channel is closed"));
298
+ return;
299
+ }
300
+ child.send(message, (error) => {
301
+ if (error) {
302
+ reject(error);
303
+ return;
304
+ }
305
+ resolve();
306
+ });
307
+ });
308
+ }
309
+ async function cancelChildProcess(runtime, reason) {
310
+ if (runtime.child.connected) {
311
+ await sendWorkerMessage(runtime.child, { type: "cancel", reason }).catch(() => void 0);
312
+ }
313
+ runtime.killTimer = setTimeout(() => {
314
+ if (!runtime.child.killed) {
315
+ runtime.child.kill("SIGTERM");
316
+ }
317
+ }, runtime.killGraceMs);
318
+ }
319
+
320
+ // src/subagents/child-process-subagent-runner-result.ts
321
+ function createChildProcessSubagentResult(options) {
322
+ return new Promise((resolve, reject) => {
323
+ new ChildProcessSubagentResultController(options, resolve, reject).start();
324
+ });
325
+ }
326
+ var ChildProcessSubagentResultController = class {
327
+ constructor(options, resolve, reject) {
328
+ this.options = options;
329
+ this.resolve = resolve;
330
+ this.reject = reject;
331
+ this.timeoutTimer = createTimeoutTimer(this.options.runtime, (error) => this.rejectOnce(error));
332
+ }
333
+ settled = false;
334
+ started = false;
335
+ timeoutTimer;
336
+ start() {
337
+ const { child } = this.options.runtime;
338
+ child.on("message", this.onMessage);
339
+ child.on("error", this.onError);
340
+ child.on("exit", this.onExit);
341
+ child.once("spawn", () => {
342
+ setImmediate(this.startWorker);
343
+ });
344
+ }
345
+ startWorker = () => {
346
+ if (this.started) return;
347
+ this.started = true;
348
+ const { child } = this.options.runtime;
349
+ void sendWorkerMessage(child, { type: "start", payload: this.options.payload }).catch(
350
+ (error) => {
351
+ this.rejectOnce(error instanceof Error ? error : new Error(String(error)));
352
+ }
353
+ );
354
+ };
355
+ onMessage = (message) => {
356
+ if (!isSubagentWorkerChildMessage(message)) {
357
+ this.rejectOnce(
358
+ new BackgroundTaskError4("runner", "Received malformed subagent worker message")
359
+ );
360
+ return;
361
+ }
362
+ const { job } = this.options.runtime;
363
+ handleWorkerMessage(message, this.startWorker, this.resolveOnce, this.rejectOnce, job.emit);
364
+ };
365
+ onError = (error) => {
366
+ this.rejectOnce(new BackgroundTaskError4("crash", error.message));
367
+ };
368
+ onExit = (code, signal) => {
369
+ if (this.settled) return;
370
+ this.rejectOnce(new BackgroundTaskError4("crash", formatEarlyExitMessage(code, signal)));
371
+ };
372
+ resolveOnce = (output) => {
373
+ if (this.settled) return;
374
+ this.settled = true;
375
+ this.clearTimers();
376
+ this.cleanup();
377
+ const { runtime, resolveTranscriptPath } = this.options;
378
+ this.resolve(toSubagentResult(runtime.job, output, resolveTranscriptPath));
379
+ };
380
+ rejectOnce = (error) => {
381
+ if (this.settled) return;
382
+ this.settled = true;
383
+ this.clearTimers();
384
+ this.cleanup();
385
+ this.reject(error);
386
+ };
387
+ clearTimers() {
388
+ if (this.timeoutTimer) clearTimeout(this.timeoutTimer);
389
+ if (this.options.runtime.killTimer) clearTimeout(this.options.runtime.killTimer);
390
+ }
391
+ cleanup() {
392
+ const { child } = this.options.runtime;
393
+ child.off("message", this.onMessage);
394
+ child.off("error", this.onError);
395
+ child.off("exit", this.onExit);
396
+ }
397
+ };
398
+ function createCancellationResult(jobId) {
399
+ let settled = false;
400
+ let rejectFn = () => {
401
+ };
402
+ const promise = new Promise((_resolve, reject) => {
403
+ rejectFn = reject;
404
+ });
405
+ return {
406
+ promise,
407
+ reject(reason) {
408
+ if (settled) return;
409
+ settled = true;
410
+ rejectFn(new BackgroundTaskError4("runner", reason ?? `Subagent job cancelled: ${jobId}`));
411
+ }
412
+ };
413
+ }
414
+ function createTimeoutTimer(runtime, rejectOnce) {
415
+ if (!runtime.job.request.timeoutMs) return void 0;
416
+ return setTimeout(() => {
417
+ void cancelChildProcess(runtime, "Subagent worker timed out");
418
+ rejectOnce(new BackgroundTaskError4("timeout", "Subagent worker timed out"));
419
+ }, runtime.job.request.timeoutMs);
420
+ }
421
+ function toSubagentResult(job, output, resolveTranscriptPath) {
422
+ const transcriptPath = resolveTranscriptPath(job);
423
+ return {
424
+ jobId: job.jobId,
425
+ output,
426
+ ...transcriptPath ? { metadata: { transcriptPath, logPath: transcriptPath } } : {}
427
+ };
428
+ }
429
+ function formatEarlyExitMessage(code, signal) {
430
+ const detail = signal !== null ? `signal ${signal}` : `exit code ${code === null ? "unknown" : code}`;
431
+ return `Subagent worker exited before result: ${detail}`;
432
+ }
433
+
434
+ // src/subagents/child-process-subagent-runner.ts
435
+ var DEFAULT_KILL_GRACE_MS2 = 2e3;
436
+ var LOG_PAGE_SIZE2 = 200;
437
+ function createChildProcessSubagentRunnerFactory(options = {}) {
438
+ return (deps) => {
439
+ const runner = new ChildProcessSubagentRunner(deps, options);
440
+ if (options.worktreeIsolation === false) return runner;
441
+ return createWorktreeSubagentRunner({
442
+ runner,
443
+ worktreeAdapter: options.worktreeAdapter ?? createGitWorktreeIsolationAdapter(),
444
+ hooks: deps.config.hooks,
445
+ hookTypeExecutors: deps.hookTypeExecutors
446
+ });
447
+ };
448
+ }
449
+ var ChildProcessSubagentRunner = class {
450
+ constructor(deps, options = {}) {
451
+ this.deps = deps;
452
+ this.workerPath = options.workerPath ?? resolveDefaultWorkerPath();
453
+ this.execArgv = options.execArgv;
454
+ this.killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS2;
455
+ this.providerConfig = options.providerConfig;
456
+ this.env = options.env;
457
+ this.logsDir = options.logsDir;
458
+ }
459
+ workerPath;
460
+ execArgv;
461
+ killGraceMs;
462
+ providerConfig;
463
+ env;
464
+ logsDir;
465
+ start(job) {
466
+ const child = fork(this.workerPath, [], {
467
+ cwd: job.request.cwd,
468
+ env: { ...process.env, ...this.env ?? {} },
469
+ execArgv: this.execArgv ?? resolveDefaultExecArgv(this.workerPath),
470
+ stdio: ["ignore", "ignore", "ignore", "ipc"]
471
+ });
472
+ const runtime = {
473
+ job,
474
+ child,
475
+ killGraceMs: this.killGraceMs
476
+ };
477
+ const payload = this.createStartPayload(job);
478
+ const workerResult = createChildProcessSubagentResult({
479
+ runtime,
480
+ payload,
481
+ resolveTranscriptPath: (request) => this.resolveTranscriptPath(request)
482
+ });
483
+ const cancellation = createCancellationResult(job.jobId);
484
+ void workerResult.catch(() => void 0);
485
+ const result = Promise.race([workerResult, cancellation.promise]);
486
+ const transcriptPath = this.resolveTranscriptPath(job);
487
+ return {
488
+ jobId: job.jobId,
489
+ ...child.pid !== void 0 && { pid: child.pid },
490
+ ...transcriptPath !== void 0 && { transcriptPath, logPath: transcriptPath },
491
+ result,
492
+ cancel: async (reason) => {
493
+ cancellation.reject(reason);
494
+ await cancelChildProcess(runtime, reason);
495
+ },
496
+ send: async (prompt) => {
497
+ await sendWorkerMessage(child, { type: "send", prompt });
498
+ },
499
+ ...transcriptPath !== void 0 && {
500
+ readLog: async (cursor) => readTranscriptLog(job.jobId, transcriptPath, cursor)
501
+ }
502
+ };
503
+ }
504
+ createStartPayload(job) {
505
+ const definition = resolveAgentDefinition(job.request.type, this.deps.customAgentRegistry);
506
+ return {
507
+ jobId: job.jobId,
508
+ request: job.request,
509
+ agentDefinition: applyRequestOverrides(definition, job),
510
+ parentConfig: this.deps.config,
511
+ parentContext: this.deps.context,
512
+ providerProfile: createProviderProfile(this.providerConfig, this.deps, job),
513
+ permissionMode: this.deps.permissionMode,
514
+ ...this.logsDir ? { logsDir: this.logsDir } : {}
515
+ };
516
+ }
517
+ resolveTranscriptPath(job) {
518
+ if (!this.logsDir) return void 0;
519
+ return join2(this.logsDir, job.request.parentSessionId, "subagents", `${job.jobId}.jsonl`);
520
+ }
521
+ };
522
+ function resolveAgentDefinition(agentType, customRegistry) {
523
+ const definition = customRegistry?.(agentType) ?? getBuiltInAgent(agentType);
524
+ if (!definition) {
525
+ throw new BackgroundTaskError5("validation", `Unknown agent type: ${agentType}`);
526
+ }
527
+ return definition;
528
+ }
529
+ function applyRequestOverrides(definition, job) {
530
+ return {
531
+ ...definition,
532
+ ...job.request.model ? { model: job.request.model } : {},
533
+ ...job.request.allowedTools ? { tools: job.request.allowedTools } : {},
534
+ ...job.request.disallowedTools ? { disallowedTools: job.request.disallowedTools } : {}
535
+ };
536
+ }
537
+ function createProviderProfile(providerConfig, deps, job) {
538
+ const provider = providerConfig ?? deps.config.provider;
539
+ return {
540
+ profileName: deps.config.currentProvider,
541
+ type: provider.name,
542
+ model: job.request.model ?? provider.model,
543
+ apiKey: provider.apiKey,
544
+ baseURL: provider.baseURL,
545
+ timeout: provider.timeout
546
+ };
547
+ }
548
+ function resolveDefaultWorkerPath() {
549
+ const entryPoint = process.argv[1] ?? "";
550
+ const entryDir = entryPoint ? dirname(entryPoint) : process.cwd();
551
+ const extension = entryPoint.endsWith(".ts") || entryPoint.endsWith(".tsx") ? ".ts" : ".js";
552
+ const candidates = [
553
+ join2(entryDir, "subagents", `child-process-subagent-worker${extension}`),
554
+ join2(entryDir, `child-process-subagent-worker${extension}`)
555
+ ];
556
+ for (const candidate of candidates) {
557
+ if (existsSync(candidate)) {
558
+ return candidate;
559
+ }
560
+ }
561
+ return candidates[0];
562
+ }
563
+ function resolveDefaultExecArgv(workerPath) {
564
+ if (!workerPath.endsWith(".ts")) {
565
+ return process.execArgv;
566
+ }
567
+ if (process.execArgv.some((arg) => arg.includes("tsx"))) {
568
+ return process.execArgv;
569
+ }
570
+ return [...process.execArgv, "--import", "tsx"];
571
+ }
572
+ function readTranscriptLog(jobId, transcriptPath, cursor) {
573
+ const offset = cursor?.offset ?? 0;
574
+ if (!existsSync(transcriptPath)) {
575
+ return {
576
+ taskId: jobId,
577
+ cursor,
578
+ lines: []
579
+ };
580
+ }
581
+ const lines = readFileSync(transcriptPath, "utf8").split(/\r?\n/).filter(Boolean);
582
+ const nextOffset = Math.min(offset + LOG_PAGE_SIZE2, lines.length);
583
+ return {
584
+ taskId: jobId,
585
+ cursor,
586
+ nextCursor: nextOffset < lines.length ? { offset: nextOffset } : void 0,
587
+ lines: lines.slice(offset, nextOffset)
588
+ };
589
+ }
590
+
591
+ // src/cli.ts
592
+ import { readFileSync as readFileSync4 } from "fs";
593
+ import { join as join7, dirname as dirname3 } from "path";
594
+ import { fileURLToPath } from "url";
595
+ import { InteractiveSession as InteractiveSession2, projectPaths } from "@robota-sdk/agent-sdk";
596
+ import { SessionStore } from "@robota-sdk/agent-sessions";
597
+
598
+ // src/utils/cli-args.ts
599
+ import { parseArgs } from "util";
600
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
601
+ function parsePermissionMode(raw) {
602
+ if (raw === void 0) return void 0;
603
+ if (!VALID_MODES.includes(raw)) {
604
+ process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
605
+ `);
606
+ process.exit(1);
607
+ }
608
+ return raw;
609
+ }
610
+ function parseMaxTurns(raw) {
611
+ if (raw === void 0) return void 0;
612
+ const n = parseInt(raw, 10);
613
+ if (isNaN(n) || n <= 0) {
614
+ process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
615
+ `);
616
+ process.exit(1);
617
+ }
618
+ return n;
619
+ }
620
+ function parseCliArgs() {
621
+ const { values, positionals } = parseArgs({
622
+ allowPositionals: true,
623
+ options: {
624
+ p: { type: "boolean", short: "p", default: false },
625
+ continue: { type: "boolean", short: "c", default: false },
626
+ resume: { type: "string", short: "r" },
627
+ model: { type: "string" },
628
+ language: { type: "string" },
629
+ "permission-mode": { type: "string" },
630
+ "max-turns": { type: "string" },
631
+ "fork-session": { type: "boolean", default: false },
632
+ name: { type: "string", short: "n" },
633
+ "output-format": { type: "string" },
634
+ "system-prompt": { type: "string" },
635
+ "append-system-prompt": { type: "string" },
636
+ version: { type: "boolean", default: false },
637
+ reset: { type: "boolean", default: false },
638
+ bare: { type: "boolean", default: false },
639
+ "allowed-tools": { type: "string" },
640
+ "no-session-persistence": { type: "boolean", default: false },
641
+ "json-schema": { type: "string" },
642
+ configure: { type: "boolean", default: false },
643
+ "configure-provider": { type: "string" },
644
+ provider: { type: "string" },
645
+ type: { type: "string" },
646
+ "base-url": { type: "string" },
647
+ "api-key": { type: "string" },
648
+ "api-key-env": { type: "string" },
649
+ "set-current": { type: "boolean", default: false },
650
+ "settings-scope": { type: "string" }
651
+ }
652
+ });
653
+ return {
654
+ positional: positionals,
655
+ printMode: values["p"] ?? false,
656
+ continueMode: values["continue"] ?? false,
657
+ resumeId: values["resume"],
658
+ model: values["model"],
659
+ language: values["language"],
660
+ permissionMode: parsePermissionMode(values["permission-mode"]),
661
+ maxTurns: parseMaxTurns(values["max-turns"]),
662
+ forkSession: values["fork-session"] ?? false,
663
+ sessionName: values["name"],
664
+ outputFormat: values["output-format"],
665
+ systemPrompt: values["system-prompt"],
666
+ appendSystemPrompt: values["append-system-prompt"],
667
+ version: values["version"] ?? false,
668
+ reset: values["reset"] ?? false,
669
+ bare: values["bare"] ?? false,
670
+ allowedTools: values["allowed-tools"],
671
+ noSessionPersistence: values["no-session-persistence"] ?? false,
672
+ jsonSchema: values["json-schema"],
673
+ configure: values["configure"] ?? false,
674
+ configureProvider: values["configure-provider"],
675
+ provider: values["provider"],
676
+ providerType: values["type"],
677
+ baseURL: values["base-url"],
678
+ apiKey: values["api-key"],
679
+ apiKeyEnv: values["api-key-env"],
680
+ setCurrent: values["set-current"] ?? false,
681
+ settingsScope: values["settings-scope"]
682
+ };
683
+ }
684
+
685
+ // src/utils/settings-io.ts
686
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, unlinkSync } from "fs";
687
+ import { join as join3, dirname as dirname2 } from "path";
688
+ function getUserSettingsPath() {
689
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
690
+ return join3(home, ".robota", "settings.json");
691
+ }
692
+ function readSettings(path) {
693
+ if (!existsSync2(path)) return {};
694
+ const raw = readFileSync2(path, "utf8");
695
+ try {
696
+ return JSON.parse(raw);
697
+ } catch {
698
+ process.stderr.write(`Warning: corrupt settings file at ${path}, resetting to defaults
699
+ `);
700
+ return {};
701
+ }
702
+ }
703
+ function writeSettings(path, settings) {
704
+ mkdirSync2(dirname2(path), { recursive: true });
705
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
706
+ }
707
+ function updateModelInSettings(settingsPath, modelId) {
708
+ const settings = readSettings(settingsPath);
709
+ const currentProvider = settings.currentProvider;
710
+ const providers = settings.providers;
711
+ if (typeof currentProvider === "string" && isSettingsData(providers)) {
712
+ const providerMap = providers;
713
+ providerMap[currentProvider] = {
714
+ ...isSettingsData(providerMap[currentProvider]) ? providerMap[currentProvider] : {},
715
+ model: modelId
716
+ };
717
+ settings.providers = providerMap;
718
+ } else {
719
+ settings.provider = {
720
+ ...isSettingsData(settings.provider) ? settings.provider : {},
721
+ model: modelId
722
+ };
723
+ }
724
+ writeSettings(settingsPath, settings);
725
+ }
726
+ function isSettingsData(value) {
727
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
728
+ }
729
+ function deleteSettings(path) {
730
+ if (existsSync2(path)) {
731
+ unlinkSync(path);
732
+ return true;
733
+ }
734
+ return false;
735
+ }
736
+
737
+ // src/utils/provider-setup.ts
738
+ import { join as join4 } from "path";
739
+ import { homedir } from "os";
740
+
741
+ // src/utils/settings-check.ts
742
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
743
+ function checkSettingsFile(filePath, providerDefinitions = []) {
744
+ if (!existsSync3(filePath)) return "missing";
745
+ try {
746
+ const raw = readFileSync3(filePath, "utf8").trim();
747
+ if (raw.length === 0) return "incomplete";
748
+ const parsed = JSON.parse(raw);
749
+ if (!hasUsableProviderConfig(parsed, providerDefinitions)) return "incomplete";
750
+ return "valid";
751
+ } catch {
752
+ return "corrupt";
753
+ }
754
+ }
755
+ function hasUsableProviderConfig(settings, providerDefinitions) {
756
+ if (settings.provider && isUsableProviderProfile(settings.provider.name, settings.provider, providerDefinitions)) {
757
+ return true;
758
+ }
759
+ if (typeof settings.currentProvider !== "string") {
760
+ return false;
761
+ }
762
+ const profile = settings.providers?.[settings.currentProvider];
763
+ return isUsableProviderProfile(profile?.type, profile, providerDefinitions);
764
+ }
765
+ function isUsableProviderProfile(type, profile, providerDefinitions) {
766
+ if (!profile) {
767
+ return false;
768
+ }
769
+ if (profile.apiKey) {
770
+ return true;
771
+ }
772
+ if (!type) {
773
+ return false;
774
+ }
775
+ const definition = findProviderDefinition(providerDefinitions, type);
776
+ if (definition === void 0) {
777
+ return false;
778
+ }
779
+ return definition.requiresApiKey !== true || definition.defaults?.apiKey !== void 0;
780
+ }
781
+
782
+ // src/utils/provider-settings.ts
783
+ function upsertProviderProfile(settings, profileName, profile) {
784
+ return {
785
+ ...settings,
786
+ providers: {
787
+ ...settings.providers ?? {},
788
+ [profileName]: profile
789
+ }
790
+ };
791
+ }
792
+ function setCurrentProvider(settings, profileName) {
793
+ if (!settings.providers?.[profileName]) {
794
+ throw new Error(`Provider profile "${profileName}" was not found`);
795
+ }
796
+ return {
797
+ ...settings,
798
+ currentProvider: profileName
799
+ };
800
+ }
801
+ function validateProviderProfile(profileName, profile, options = {}) {
802
+ if (!profile.type) {
803
+ throw new Error(`Provider profile "${profileName}" is missing type`);
804
+ }
805
+ if (!profile.model) {
806
+ throw new Error(`Provider profile "${profileName}" is missing model`);
807
+ }
808
+ const definition = findProviderDefinition(options.providerDefinitions ?? [], profile.type);
809
+ if (definition?.requiresApiKey === true && !profile.apiKey && definition.defaults?.apiKey === void 0) {
810
+ throw new Error(`Provider profile "${profileName}" is missing apiKey`);
811
+ }
812
+ }
813
+ function buildProviderSetupPatch(input, options = {}) {
814
+ const profile = buildProviderProfile(input, options);
815
+ validateProviderProfile(input.profile, profile, options);
816
+ return {
817
+ ...input.setCurrent && { currentProvider: input.profile },
818
+ providers: {
819
+ [input.profile]: profile
820
+ }
821
+ };
822
+ }
823
+ function buildProviderProfile(input, options = {}) {
824
+ const defaults = getProviderDefaults(input.type, options.providerDefinitions ?? []);
825
+ const apiKey = input.apiKeyEnv !== void 0 ? `$ENV:${input.apiKeyEnv}` : input.apiKey ?? defaults.apiKey;
826
+ const baseURL = input.baseURL ?? defaults.baseURL;
827
+ return {
828
+ type: input.type,
829
+ model: input.model ?? defaults.model,
830
+ ...apiKey !== void 0 && { apiKey },
831
+ ...baseURL !== void 0 && { baseURL },
832
+ ...input.timeout !== void 0 && { timeout: input.timeout }
833
+ };
834
+ }
835
+ function getProviderDefaults(type, providerDefinitions) {
836
+ return findProviderDefinition(providerDefinitions, type)?.defaults ?? {};
837
+ }
838
+ function mergeProviderPatch(settings, patch) {
839
+ const [profileName, profile] = Object.entries(patch.providers)[0] ?? [];
840
+ if (!profileName || !profile) {
841
+ return settings;
842
+ }
843
+ const withProfile = upsertProviderProfile(settings, profileName, profile);
844
+ return patch.currentProvider ? setCurrentProvider(withProfile, patch.currentProvider) : withProfile;
845
+ }
846
+
847
+ // src/utils/provider-configuration.ts
848
+ function readProviderDocument(settingsPath) {
849
+ return readSettings(settingsPath);
850
+ }
851
+ function applyProviderConfiguration(settingsPath, input, options = {}) {
852
+ const settings = readProviderDocument(settingsPath);
853
+ const patch = buildProviderSetupPatch(input, options);
854
+ const next = mergeProviderPatch(settings, patch);
855
+ writeSettings(settingsPath, next);
856
+ return next;
857
+ }
858
+ function applyProviderSwitch(settingsPath, profileName, options = {}) {
859
+ const settings = readProviderDocument(settingsPath);
860
+ const hasLocalProfile = settings.providers?.[profileName] !== void 0;
861
+ const hasKnownProfile = options.knownProviders?.[profileName] !== void 0;
862
+ const next = hasLocalProfile || hasKnownProfile ? { ...settings, currentProvider: profileName } : setCurrentProvider(settings, profileName);
863
+ writeSettings(settingsPath, next);
864
+ return next;
865
+ }
866
+
867
+ // src/utils/provider-setup-flow.ts
868
+ function createProviderSetupFlow(type, providerDefinitions) {
869
+ return {
870
+ type,
871
+ steps: getProviderSetupSteps(type, providerDefinitions),
872
+ stepIndex: 0,
873
+ values: {}
874
+ };
875
+ }
876
+ function getProviderSetupStep(state) {
877
+ const step = state.steps[state.stepIndex];
878
+ if (step === void 0) {
879
+ throw new Error(`Provider setup step ${state.stepIndex} is out of range`);
880
+ }
881
+ return step;
882
+ }
883
+ function submitProviderSetupValue(state, rawValue) {
884
+ const step = getProviderSetupStep(state);
885
+ const value = rawValue.trim() || step.defaultValue || "";
886
+ const validationMessage = validateProviderSetupValue(step, value);
887
+ if (validationMessage !== void 0) {
888
+ return { status: "error", state, message: validationMessage };
889
+ }
890
+ const nextState = {
891
+ ...state,
892
+ stepIndex: state.stepIndex + 1,
893
+ values: { ...state.values, [step.key]: value }
894
+ };
895
+ if (nextState.stepIndex < state.steps.length) {
896
+ return { status: "next", state: nextState };
897
+ }
898
+ return { status: "complete", input: buildProviderSetupInput(nextState) };
899
+ }
900
+ async function runProviderSetupPromptFlow(type, promptInput2, providerDefinitions) {
901
+ let state = createProviderSetupFlow(type, providerDefinitions);
902
+ const stepCount = state.steps.length;
903
+ while (state.stepIndex < stepCount) {
904
+ const step = getProviderSetupStep(state);
905
+ const value = await promptInput2(formatProviderSetupPromptLabel(step), step.masked === true);
906
+ const result = submitProviderSetupValue(state, value);
907
+ if (result.status === "complete") {
908
+ return result.input;
909
+ }
910
+ if (result.status === "error") {
911
+ throw new Error(result.message);
912
+ }
913
+ state = result.state;
914
+ }
915
+ throw new Error("Provider setup flow ended without completion");
916
+ }
917
+ function formatProviderSetupPromptLabel(step) {
918
+ const suffix = step.defaultValue !== void 0 ? ` (default: ${step.defaultValue})` : "";
919
+ return ` ${step.title}${suffix}: `;
920
+ }
921
+ function validateProviderSetupValue(step, value) {
922
+ if (step.required === true && value.length === 0) {
923
+ return "Required";
924
+ }
925
+ return void 0;
926
+ }
927
+ function getProviderSetupSteps(type, providerDefinitions) {
928
+ const definition = findProviderDefinition(providerDefinitions, type);
929
+ if (definition === void 0) {
930
+ throw new Error(
931
+ `Unknown provider: ${type}. Currently supported: ${formatSupportedProviderTypes(providerDefinitions)}`
932
+ );
933
+ }
934
+ if (definition.setupSteps !== void 0) {
935
+ return [...definition.setupSteps];
936
+ }
937
+ const steps = [
938
+ {
939
+ key: "model",
940
+ title: `${definition.type} model`,
941
+ defaultValue: definition.defaults?.model,
942
+ required: definition.defaults?.model === void 0
943
+ }
944
+ ];
945
+ if (definition.defaults?.baseURL !== void 0) {
946
+ steps.unshift({
947
+ key: "baseURL",
948
+ title: `${definition.type} base URL`,
949
+ defaultValue: definition.defaults.baseURL
950
+ });
951
+ }
952
+ if (definition.requiresApiKey === true) {
953
+ steps.push({
954
+ key: "apiKey",
955
+ title: `${definition.type} API key`,
956
+ defaultValue: definition.defaults?.apiKey,
957
+ required: definition.defaults?.apiKey === void 0,
958
+ masked: true
959
+ });
960
+ }
961
+ return steps;
962
+ }
963
+ function buildProviderSetupInput(state) {
964
+ return {
965
+ profile: state.type,
966
+ type: state.type,
967
+ model: state.values.model,
968
+ apiKey: state.values.apiKey,
969
+ ...state.values.baseURL !== void 0 && { baseURL: state.values.baseURL },
970
+ setCurrent: true
971
+ };
972
+ }
973
+
974
+ // src/utils/provider-setup.ts
975
+ function getSettingsPathForScope(cwd, scope) {
976
+ if (scope === void 0 || scope === "user") {
977
+ return getUserSettingsPath();
978
+ }
979
+ if (scope === "project-local") {
980
+ return join4(cwd, ".robota", "settings.local.json");
981
+ }
982
+ throw new Error(`Invalid --settings-scope "${scope}". Valid: user | project-local`);
983
+ }
984
+ function handleProviderConfigurationArgs(cwd, args, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
985
+ const settingsPath = getSettingsPathForScope(cwd, args.settingsScope);
986
+ if (args.configureProvider) {
987
+ applyProviderConfiguration(settingsPath, buildSetupInputFromArgs(args), {
988
+ providerDefinitions
989
+ });
990
+ process.stdout.write(`Provider profile saved to ${settingsPath}
991
+ `);
992
+ return !args.printMode && args.positional.length === 0;
993
+ }
994
+ if (args.provider && args.setCurrent) {
995
+ applyProviderSwitch(settingsPath, args.provider, {
996
+ knownProviders: readMergedProviderSettings(cwd).providers
997
+ });
998
+ process.stdout.write(`Current provider set to ${args.provider}
999
+ `);
1000
+ return !args.printMode && args.positional.length === 0;
1001
+ }
1002
+ return false;
1003
+ }
1004
+ async function ensureConfig(cwd, args, promptInput2, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
1005
+ const checks = getSettingsCheckPaths(cwd).map((path) => ({
1006
+ path,
1007
+ status: checkSettingsFile(path, providerDefinitions)
1008
+ }));
1009
+ if (checks.some((check) => check.status === "valid")) {
1010
+ return;
1011
+ }
1012
+ if (!isInteractiveTerminal()) {
1013
+ throw new Error(formatMissingProviderConfigMessage());
1014
+ }
1015
+ await runInteractiveProviderSetup(cwd, args, promptInput2, providerDefinitions);
1016
+ }
1017
+ async function runInteractiveProviderSetup(cwd, args, promptInput2, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
1018
+ const defaultProviderType = providerDefinitions[0]?.type ?? "";
1019
+ const supportedTypes = providerDefinitions.map((definition) => definition.type).join("/");
1020
+ const providerChoice = await promptInput2(` Provider (${supportedTypes}, default: ${defaultProviderType}): `) || defaultProviderType;
1021
+ const type = parseProviderSetupType(providerChoice);
1022
+ const settingsPath = getSettingsPathForScope(cwd, args.settingsScope);
1023
+ const input = await runProviderSetupPromptFlow(type, promptInput2, providerDefinitions);
1024
+ applyProviderConfiguration(settingsPath, input, {
1025
+ providerDefinitions
1026
+ });
1027
+ const language = await promptInput2(" Response language (ko/en/ja/zh, default: en): ");
1028
+ if (language) {
1029
+ const settings = readSettings(settingsPath);
1030
+ settings.language = language;
1031
+ writeSettings(settingsPath, settings);
1032
+ }
1033
+ process.stdout.write(`
1034
+ Config saved to ${settingsPath}
1035
+
1036
+ `);
1037
+ }
1038
+ function parseProviderSetupType(value) {
1039
+ return value.trim();
1040
+ }
1041
+ function buildSetupInputFromArgs(args) {
1042
+ const type = args.providerType ?? args.configureProvider;
1043
+ if (!args.configureProvider || !type) {
1044
+ throw new Error("--configure-provider requires a provider profile and --type");
1045
+ }
1046
+ return {
1047
+ profile: args.configureProvider,
1048
+ type,
1049
+ ...args.model !== void 0 && { model: args.model },
1050
+ ...args.apiKey !== void 0 && { apiKey: args.apiKey },
1051
+ ...args.apiKeyEnv !== void 0 && { apiKeyEnv: args.apiKeyEnv },
1052
+ ...args.baseURL !== void 0 && { baseURL: args.baseURL },
1053
+ setCurrent: args.setCurrent
1054
+ };
1055
+ }
1056
+ function getSettingsCheckPaths(cwd) {
1057
+ return [
1058
+ getUserSettingsPath(),
1059
+ join4(homedir(), ".claude", "settings.json"),
1060
+ join4(cwd, ".robota", "settings.json"),
1061
+ join4(cwd, ".robota", "settings.local.json"),
1062
+ join4(cwd, ".claude", "settings.json"),
1063
+ join4(cwd, ".claude", "settings.local.json")
1064
+ ];
1065
+ }
1066
+ function isInteractiveTerminal() {
1067
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
1068
+ }
1069
+ function formatMissingProviderConfigMessage() {
1070
+ return [
1071
+ "No provider configuration found.",
1072
+ "Run `robota --configure` in an interactive terminal, or configure a provider:",
1073
+ " robota --configure-provider gemma --type gemma --base-url http://localhost:1234/v1 --model supergemma4-26b-uncensored-v2 --api-key lm-studio --set-current",
1074
+ " robota --configure-provider openai --type openai --model <openai-compatible-model> --api-key-env OPENAI_API_KEY --set-current"
1075
+ ].join("\n");
1076
+ }
1077
+
1078
+ // src/cli.ts
1079
+ import { createHeadlessTransport } from "@robota-sdk/agent-transport-headless";
1080
+
1081
+ // src/ui/render.tsx
1082
+ import { render } from "ink";
1083
+
1084
+ // src/ui/App.tsx
1085
+ import { useState as useState14, useEffect as useEffect4 } from "react";
1086
+ import { Box as Box14, Text as Text16, useApp as useApp2, useInput as useInput8 } from "ink";
1087
+ import { getModelName as getModelName2, createSystemMessage as createSystemMessage4, messageToHistoryEntry as messageToHistoryEntry4 } from "@robota-sdk/agent-core";
1088
+
1089
+ // src/ui/hooks/useInteractiveSession.ts
1090
+ import { useState, useRef, useCallback as useCallback2, useEffect } from "react";
1091
+ import { homedir as homedir2 } from "os";
1092
+ import { join as join5 } from "path";
1093
+ import {
1094
+ InteractiveSession,
1095
+ CommandRegistry,
1096
+ BuiltinCommandSource,
1097
+ SkillCommandSource,
1098
+ PluginCommandSource,
1099
+ BundlePluginLoader
1100
+ } from "@robota-sdk/agent-sdk";
1101
+ import { createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2 } from "@robota-sdk/agent-core";
1102
+
1103
+ // src/ui/background-task-view-model.ts
1104
+ var BACKGROUND_PREVIEW_LENGTH = 120;
1105
+ var BACKGROUND_PREVIEW_WHITESPACE = /\s+/g;
1106
+ var BACKGROUND_PREVIEW_SEPARATOR = " ";
1107
+ var SUCCESS_EXIT_CODE = 0;
1108
+ function toBackgroundTaskViewModel(state, partialText) {
1109
+ return {
1110
+ id: state.id,
1111
+ kind: state.kind,
1112
+ label: state.label,
1113
+ status: state.status,
1114
+ statusLabel: getBackgroundTaskStatusLabel(state),
1115
+ mode: state.mode,
1116
+ currentAction: state.currentAction,
1117
+ unread: state.unread,
1118
+ preview: trimBackgroundPreview(state.promptPreview ?? state.commandPreview) ?? "",
1119
+ resultPreview: trimBackgroundPreview(state.result?.output ?? partialText),
1120
+ errorPreview: trimBackgroundPreview(state.error?.message),
1121
+ startedAt: state.startedAt,
1122
+ lastActivityAt: state.lastActivityAt,
1123
+ timeoutReason: state.timeoutReason
1124
+ };
1125
+ }
1126
+ function getBackgroundTaskStatusLabel(state) {
1127
+ if (state.status === "failed" && state.timeoutReason) {
1128
+ if (state.timeoutReason === "idle" || state.timeoutReason === "max_runtime") {
1129
+ return "timed out";
1130
+ }
1131
+ return state.timeoutReason.replace(/_/g, " ");
1132
+ }
1133
+ return state.status;
1134
+ }
1135
+ function shouldHideAtNextUserTurn(state) {
1136
+ return state.status === "completed" && !state.error && (state.result?.exitCode === void 0 || state.result.exitCode === SUCCESS_EXIT_CODE) && !state.result?.signalCode && !state.worktreePath && !state.branchName;
1137
+ }
1138
+ function trimBackgroundPreview(value) {
1139
+ if (!value) return void 0;
1140
+ const preview = value.trim().replace(BACKGROUND_PREVIEW_WHITESPACE, BACKGROUND_PREVIEW_SEPARATOR);
1141
+ if (!preview) return void 0;
1142
+ return preview.length > BACKGROUND_PREVIEW_LENGTH ? `${preview.slice(0, BACKGROUND_PREVIEW_LENGTH)}...` : preview;
1143
+ }
1144
+
1145
+ // src/ui/tui-state-manager.ts
1146
+ var MAX_RENDERED_MESSAGES = 100;
1147
+ var STREAMING_DEBOUNCE_MS = 300;
1148
+ function createDebouncedNotify(notify, ms) {
1149
+ let timer = null;
1150
+ return {
1151
+ schedule() {
1152
+ if (!timer) {
1153
+ timer = setTimeout(() => {
1154
+ timer = null;
1155
+ notify();
1156
+ }, ms);
1157
+ }
1158
+ },
1159
+ flush() {
1160
+ if (timer) {
1161
+ clearTimeout(timer);
1162
+ timer = null;
1163
+ }
1164
+ }
1165
+ };
1166
+ }
1167
+ var TuiStateManager = class {
1168
+ // ── Rendering state ───────────────────────────────────────────
1169
+ history = [];
1170
+ streamingText = "";
1171
+ activeTools = [];
1172
+ isThinking = false;
1173
+ isAborting = false;
1174
+ pendingPrompt = null;
1175
+ contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
1176
+ backgroundTasks = [];
1177
+ /** Called after any state change. React hook sets this to trigger re-render. */
1178
+ onChange = null;
1179
+ // ── Internal ──────────────────────────────────────────────────
1180
+ streamBuf = "";
1181
+ backgroundTextBuffers = /* @__PURE__ */ new Map();
1182
+ backgroundTasksHiddenOnNextTurn = /* @__PURE__ */ new Set();
1183
+ debouncedStreamNotify = createDebouncedNotify(() => this.notify(), STREAMING_DEBOUNCE_MS);
1184
+ notify() {
1185
+ this.onChange?.();
1186
+ }
1187
+ // ── Event handlers (InteractiveSession → state) ───────────────
1188
+ onTextDelta = (delta) => {
1189
+ this.streamBuf += delta;
1190
+ this.streamingText = this.streamBuf;
1191
+ this.debouncedStreamNotify.schedule();
1192
+ };
1193
+ onToolStart = (state) => {
1194
+ this.activeTools = [...this.activeTools, state];
1195
+ this.notify();
1196
+ };
1197
+ onToolEnd = (state) => {
1198
+ const idx = this.activeTools.findIndex((t) => t.toolName === state.toolName && t.isRunning);
1199
+ if (idx !== -1) {
1200
+ const updated = [...this.activeTools];
1201
+ updated[idx] = state;
1202
+ this.activeTools = updated;
1203
+ }
1204
+ this.notify();
1205
+ };
1206
+ onThinking = (thinking) => {
1207
+ this.isThinking = thinking;
1208
+ if (thinking) {
1209
+ this.debouncedStreamNotify.flush();
1210
+ this.streamBuf = "";
1211
+ this.streamingText = "";
1212
+ this.activeTools = [];
1213
+ } else {
1214
+ this.isAborting = false;
1215
+ }
1216
+ this.notify();
1217
+ };
1218
+ onComplete = (result) => {
1219
+ this.debouncedStreamNotify.flush();
1220
+ this.streamBuf = "";
1221
+ this.streamingText = "";
1222
+ this.activeTools = [];
1223
+ this.contextState = {
1224
+ percentage: result.contextState.usedPercentage,
1225
+ usedTokens: result.contextState.usedTokens,
1226
+ maxTokens: result.contextState.maxTokens
1227
+ };
1228
+ this.notify();
1229
+ };
1230
+ onInterrupted = () => {
1231
+ this.debouncedStreamNotify.flush();
1232
+ this.streamBuf = "";
1233
+ this.streamingText = "";
1234
+ this.activeTools = [];
1235
+ this.notify();
1236
+ };
1237
+ onError = () => {
1238
+ this.debouncedStreamNotify.flush();
1239
+ this.streamBuf = "";
1240
+ this.streamingText = "";
1241
+ this.activeTools = [];
1242
+ this.notify();
1243
+ };
1244
+ onBackgroundTaskEvent = (event) => {
1245
+ if ("task" in event) {
1246
+ this.upsertBackgroundTask(event.task);
1247
+ return;
1248
+ }
1249
+ if (event.type === "background_task_closed") {
1250
+ this.backgroundTextBuffers.delete(event.taskId);
1251
+ this.backgroundTasksHiddenOnNextTurn.delete(event.taskId);
1252
+ this.backgroundTasks = this.backgroundTasks.filter((task) => task.id !== event.taskId);
1253
+ this.notify();
1254
+ return;
1255
+ }
1256
+ if (event.type === "background_task_text_delta") {
1257
+ this.appendBackgroundTaskText(event.taskId, event.delta);
1258
+ return;
1259
+ }
1260
+ if (event.type === "background_task_tool_start") {
1261
+ this.updateBackgroundTaskAction(event.taskId, event.firstArg ?? event.toolName);
1262
+ return;
1263
+ }
1264
+ if (event.type === "background_task_tool_end") {
1265
+ this.updateBackgroundTaskAction(event.taskId, event.success ? void 0 : event.error);
1266
+ }
1267
+ };
1268
+ // ── State updates from external sources ───────────────────────
1269
+ /** Sync history from InteractiveSession */
1270
+ syncHistory(entries) {
1271
+ if (entries.length === 0) return;
1272
+ this.history = entries.length > MAX_RENDERED_MESSAGES ? entries.slice(-MAX_RENDERED_MESSAGES) : [...entries];
1273
+ this.notify();
1274
+ }
1275
+ /** Add a single history entry */
1276
+ addEntry(entry) {
1277
+ const updated = [...this.history, entry];
1278
+ this.history = updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
1279
+ this.notify();
1280
+ }
1281
+ /** Update pending prompt state */
1282
+ setPendingPrompt(prompt) {
1283
+ this.pendingPrompt = prompt;
1284
+ this.notify();
1285
+ }
1286
+ /** Set aborting flag */
1287
+ setAborting(aborting) {
1288
+ this.isAborting = aborting;
1289
+ this.notify();
1290
+ }
1291
+ /** Update context state */
1292
+ setContextState(state) {
1293
+ this.contextState = state;
1294
+ this.notify();
1295
+ }
1296
+ onUserTurnAccepted() {
1297
+ if (this.backgroundTasksHiddenOnNextTurn.size === 0) return;
1298
+ const visible = this.backgroundTasks.filter(
1299
+ (task) => !this.backgroundTasksHiddenOnNextTurn.has(task.id)
1300
+ );
1301
+ this.backgroundTasksHiddenOnNextTurn.clear();
1302
+ if (visible.length === this.backgroundTasks.length) return;
1303
+ this.backgroundTasks = visible;
1304
+ this.notify();
1305
+ }
1306
+ upsertBackgroundTask(state) {
1307
+ const partialText = state.result ? void 0 : this.backgroundTextBuffers.get(state.id);
1308
+ const viewModel = toBackgroundTaskViewModel(state, partialText);
1309
+ const index = this.backgroundTasks.findIndex((task) => task.id === state.id);
1310
+ if (index === -1) {
1311
+ this.backgroundTasks = [...this.backgroundTasks, viewModel];
1312
+ } else {
1313
+ const updated = [...this.backgroundTasks];
1314
+ updated[index] = viewModel;
1315
+ this.backgroundTasks = updated;
1316
+ }
1317
+ if (state.status === "completed" || state.status === "failed" || state.status === "cancelled") {
1318
+ this.backgroundTextBuffers.delete(state.id);
1319
+ }
1320
+ if (shouldHideAtNextUserTurn(state)) {
1321
+ this.backgroundTasksHiddenOnNextTurn.add(state.id);
1322
+ } else {
1323
+ this.backgroundTasksHiddenOnNextTurn.delete(state.id);
1324
+ }
1325
+ this.notify();
1326
+ }
1327
+ appendBackgroundTaskText(taskId, delta) {
1328
+ const nextText = `${this.backgroundTextBuffers.get(taskId) ?? ""}${delta}`;
1329
+ this.backgroundTextBuffers.set(taskId, nextText);
1330
+ this.backgroundTasks = this.backgroundTasks.map(
1331
+ (task) => task.id === taskId ? { ...task, resultPreview: trimBackgroundPreview(nextText) } : task
1332
+ );
1333
+ this.notify();
1334
+ }
1335
+ updateBackgroundTaskAction(taskId, currentAction) {
1336
+ this.backgroundTasks = this.backgroundTasks.map(
1337
+ (task) => task.id === taskId ? { ...task, currentAction } : task
1338
+ );
1339
+ this.notify();
1340
+ }
1341
+ };
1342
+
1343
+ // src/ui/hooks/useSlashRouting.ts
1344
+ import { useCallback } from "react";
1345
+ import { randomUUID as randomUUID2 } from "crypto";
1346
+ import { createSystemMessage, messageToHistoryEntry } from "@robota-sdk/agent-core";
1347
+
1348
+ // src/utils/provider-command.ts
1349
+ async function handleProviderCommand(cwd, args, deps = {}) {
1350
+ const settings = readMergedProviderSettings(cwd);
1351
+ const providerDefinitions = deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
1352
+ const [subcommand = "current", profileArg] = args.trim().split(/\s+/);
1353
+ if (subcommand === "list") {
1354
+ return {
1355
+ message: formatProviderList(settings.currentProvider, settings.providers),
1356
+ success: true
1357
+ };
1358
+ }
1359
+ if (subcommand === "current" || subcommand === "") {
1360
+ return {
1361
+ message: formatCurrentProvider(settings.currentProvider, settings.providers),
1362
+ success: true
1363
+ };
1364
+ }
1365
+ if (subcommand === "use") {
1366
+ return buildProviderSwitch(settings.providers, profileArg);
1367
+ }
1368
+ if (subcommand === "test") {
1369
+ return await testProvider(settings.currentProvider, settings.providers, profileArg, deps);
1370
+ }
1371
+ if (subcommand === "add") {
1372
+ return buildProviderSetup(profileArg, providerDefinitions);
1373
+ }
1374
+ return {
1375
+ message: "Usage: provider [current|list|use <profile>|add <type>|test [profile]]",
1376
+ success: false
1377
+ };
1378
+ }
1379
+ function formatProviderList(currentProvider, providers) {
1380
+ const entries = Object.entries(providers ?? {});
1381
+ if (entries.length === 0) {
1382
+ return "No provider profiles configured.";
1383
+ }
1384
+ return entries.map(([name, profile]) => {
1385
+ const marker = name === currentProvider ? "*" : "-";
1386
+ return `${marker} ${name}: ${profile.type ?? "unknown"} ${profile.model ?? "(no model)"}`;
1387
+ }).join("\n");
1388
+ }
1389
+ function formatCurrentProvider(currentProvider, providers) {
1390
+ if (!currentProvider) {
1391
+ return "No current provider configured.";
1392
+ }
1393
+ const profile = providers?.[currentProvider];
1394
+ if (!profile) {
1395
+ return `Current provider "${currentProvider}" was not found in providers.`;
1396
+ }
1397
+ return [
1398
+ `Current provider: ${currentProvider}`,
1399
+ `Type: ${profile.type ?? "unknown"}`,
1400
+ `Model: ${profile.model ?? "(no model)"}`,
1401
+ ...profile.baseURL ? [`Base URL: ${profile.baseURL}`] : []
1402
+ ].join("\n");
1403
+ }
1404
+ function buildProviderSwitch(providers, profileName) {
1405
+ if (!profileName) {
1406
+ return { message: "Usage: provider use <profile>", success: false };
1407
+ }
1408
+ if (!providers?.[profileName]) {
1409
+ return { message: `Provider profile "${profileName}" was not found.`, success: false };
1410
+ }
1411
+ return {
1412
+ message: `Provider change requested: ${profileName}`,
1413
+ success: true,
1414
+ data: { providerSwitch: { profile: profileName } }
1415
+ };
1416
+ }
1417
+ function buildProviderSetup(type, providerDefinitions) {
1418
+ if (!type || findProviderDefinition(providerDefinitions, type) === void 0) {
1419
+ return {
1420
+ message: `Usage: provider add <type>. Supported: ${formatSupportedProviderTypes(providerDefinitions)}`,
1421
+ success: false
1422
+ };
1423
+ }
1424
+ return {
1425
+ message: `Provider setup requested: ${type}`,
1426
+ success: true,
1427
+ data: { providerSetup: { type } }
1428
+ };
1429
+ }
1430
+ async function testProvider(currentProvider, providers, profileArg, deps) {
1431
+ const profileName = profileArg ?? currentProvider;
1432
+ if (!profileName) {
1433
+ return { message: "No provider profile selected.", success: false };
1434
+ }
1435
+ const profile = providers?.[profileName];
1436
+ if (!profile) {
1437
+ return { message: `Provider profile "${profileName}" was not found.`, success: false };
1438
+ }
1439
+ try {
1440
+ validateProviderProfile(profileName, profile, {
1441
+ providerDefinitions: deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS
1442
+ });
1443
+ } catch (error) {
1444
+ return { message: error instanceof Error ? error.message : String(error), success: false };
1445
+ }
1446
+ const providerDefinitions = deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
1447
+ const definition = profile.type ? findProviderDefinition(providerDefinitions, profile.type) : void 0;
1448
+ const probe = deps.probe ?? definition?.probeProfile ?? probeProviderProfile;
1449
+ const result = await probe(profile);
1450
+ return {
1451
+ message: result.ok ? `Provider "${profileName}" test passed: ${result.message}` : `Provider "${profileName}" test failed: ${result.message}; manual configuration can continue.`,
1452
+ success: true,
1453
+ data: { providerTest: { profile: profileName } }
1454
+ };
1455
+ }
1456
+ async function probeProviderProfile(profile) {
1457
+ void profile;
1458
+ return { ok: true, message: "Profile fields are valid; no endpoint probe configured." };
1459
+ }
1460
+
1461
+ // src/ui/hooks/useSlashRouting.ts
1462
+ function useSlashRouting(cwd, interactiveSession, registry, manager, providerDefinitions) {
1463
+ return useCallback(
1464
+ async (input) => {
1465
+ manager.onUserTurnAccepted();
1466
+ if (!input.startsWith("/")) {
1467
+ await interactiveSession.submit(input);
1468
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
1469
+ return;
1470
+ }
1471
+ const parts = input.slice(1).split(/\s+/);
1472
+ const cmd = parts[0]?.toLowerCase() ?? "";
1473
+ const args = parts.slice(1).join(" ");
1474
+ if (cmd === "provider") {
1475
+ await routeProviderCommand(cwd, args, interactiveSession, manager, providerDefinitions);
1476
+ return;
1477
+ }
1478
+ const result = await interactiveSession.executeCommand(cmd, args);
1479
+ if (result) {
1480
+ applySystemCommandResult(result, interactiveSession, manager);
1481
+ return;
1482
+ }
1483
+ if (await routeSkillCommand(input, cmd, registry, interactiveSession, manager)) {
1484
+ return;
1485
+ }
1486
+ if (routeTuiCommand(cmd, interactiveSession)) {
1487
+ return;
1488
+ }
1489
+ manager.addEntry(
1490
+ messageToHistoryEntry(
1491
+ createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`)
1492
+ )
1493
+ );
1494
+ },
1495
+ [cwd, interactiveSession, registry, manager, providerDefinitions]
1496
+ );
1497
+ }
1498
+ async function routeProviderCommand(cwd, args, interactiveSession, manager, providerDefinitions) {
1499
+ const result = await handleProviderCommand(cwd, args, { providerDefinitions });
1500
+ manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
1501
+ const providerSwitch = result.data?.providerSwitch;
1502
+ if (providerSwitch?.profile) {
1503
+ getEffects(interactiveSession)._pendingProviderProfile = providerSwitch.profile;
1504
+ }
1505
+ const providerSetup = result.data?.providerSetup;
1506
+ if (providerSetup?.type) {
1507
+ getEffects(interactiveSession)._pendingProviderSetupType = providerSetup.type;
1508
+ }
1509
+ }
1510
+ function applySystemCommandResult(result, interactiveSession, manager) {
1511
+ manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
1512
+ const data = result.data;
1513
+ const effects = getEffects(interactiveSession);
1514
+ if (typeof data?.modelId === "string") {
1515
+ effects._pendingModelId = data.modelId;
1516
+ return;
1517
+ }
1518
+ if (typeof data?.language === "string") {
1519
+ effects._pendingLanguage = data.language;
1520
+ return;
1521
+ }
1522
+ if (data?.resetRequested === true) {
1523
+ effects._resetRequested = true;
1524
+ return;
1525
+ }
1526
+ if (data?.triggerResumePicker === true) {
1527
+ effects._triggerResumePicker = true;
1528
+ return;
1529
+ }
1530
+ if (typeof data?.name === "string") {
1531
+ effects._sessionName = data.name;
1532
+ return;
1533
+ }
1534
+ const ctx = interactiveSession.getContextState();
1535
+ manager.setContextState({
1536
+ percentage: ctx.usedPercentage,
1537
+ usedTokens: ctx.usedTokens,
1538
+ maxTokens: ctx.maxTokens
1539
+ });
1540
+ }
1541
+ async function routeSkillCommand(input, cmd, registry, interactiveSession, manager) {
1542
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
1543
+ if (!skillCmd) {
1544
+ return false;
1545
+ }
1546
+ manager.addEntry({
1547
+ id: randomUUID2(),
1548
+ timestamp: /* @__PURE__ */ new Date(),
1549
+ category: "event",
1550
+ type: "skill-invocation",
1551
+ data: {
1552
+ skillName: cmd,
1553
+ source: skillCmd.source,
1554
+ message: `Invoking ${skillCmd.source}: ${cmd}`
1555
+ }
1556
+ });
1557
+ const args = input.slice(1 + cmd.length).trimStart();
1558
+ const qualifiedName = registry.resolveQualifiedName(cmd);
1559
+ const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
1560
+ await interactiveSession.executeSkillCommand(skillCmd, args, input, hookInput);
1561
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
1562
+ return true;
1563
+ }
1564
+ function routeTuiCommand(cmd, interactiveSession) {
1565
+ if (cmd === "exit") {
1566
+ getEffects(interactiveSession)._exitRequested = true;
1567
+ return true;
1568
+ }
1569
+ if (cmd === "plugin") {
1570
+ getEffects(interactiveSession)._triggerPluginTUI = true;
1571
+ return true;
1572
+ }
1573
+ return false;
1574
+ }
1575
+ function getEffects(interactiveSession) {
1576
+ return interactiveSession;
1577
+ }
1578
+
1579
+ // src/ui/hooks/useInteractiveSession.ts
1580
+ function initializeSession(props, permissionHandler) {
1581
+ const interactiveSession = new InteractiveSession({
1582
+ cwd: props.cwd,
1583
+ provider: props.provider,
1584
+ permissionMode: props.permissionMode,
1585
+ maxTurns: props.maxTurns,
1586
+ permissionHandler,
1587
+ sessionStore: props.sessionStore,
1588
+ resumeSessionId: props.resumeSessionId,
1589
+ forkSession: props.forkSession,
1590
+ sessionName: props.sessionName,
1591
+ backgroundTaskRunners: props.backgroundTaskRunners,
1592
+ subagentRunnerFactory: props.subagentRunnerFactory,
1593
+ commandModules: props.commandModules
1594
+ });
1595
+ const registry = new CommandRegistry();
1596
+ registry.addSource(new BuiltinCommandSource());
1597
+ for (const module of props.commandModules ?? []) {
1598
+ registry.addModule(module);
1599
+ }
1600
+ registry.addSource(new SkillCommandSource(props.cwd));
1601
+ const pluginsDir = join5(homedir2(), ".robota", "plugins");
1602
+ const loader = new BundlePluginLoader(pluginsDir);
1603
+ try {
1604
+ const plugins = loader.loadPluginsSync();
1605
+ if (plugins.length > 0) {
1606
+ registry.addSource(new PluginCommandSource(plugins));
1607
+ }
1608
+ } catch {
1609
+ }
1610
+ const manager = new TuiStateManager();
1611
+ return { interactiveSession, registry, manager };
1612
+ }
1613
+ function useInteractiveSession(props) {
1614
+ const [, forceRender] = useState(0);
1615
+ const [permissionRequest, setPermissionRequest] = useState(null);
1616
+ const [isShuttingDown, setIsShuttingDown] = useState(false);
1617
+ const permissionQueueRef = useRef([]);
1618
+ const processingRef = useRef(false);
1619
+ const processNextPermission = useCallback2(() => {
1620
+ if (processingRef.current) return;
1621
+ const next = permissionQueueRef.current[0];
1622
+ if (!next) {
1623
+ setPermissionRequest(null);
1624
+ return;
1625
+ }
1626
+ processingRef.current = true;
1627
+ setPermissionRequest({
1628
+ toolName: next.toolName,
1629
+ toolArgs: next.toolArgs,
1630
+ resolve: (result) => {
1631
+ permissionQueueRef.current.shift();
1632
+ processingRef.current = false;
1633
+ setPermissionRequest(null);
1634
+ next.resolve(result);
1635
+ setTimeout(() => processNextPermission(), 0);
1636
+ }
1637
+ });
1638
+ }, []);
1639
+ const permissionHandler = useCallback2(
1640
+ (toolName, toolArgs) => new Promise((resolve) => {
1641
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
1642
+ processNextPermission();
1643
+ }),
1644
+ [processNextPermission]
1645
+ );
1646
+ const stateRef = useRef(null);
1647
+ if (stateRef.current === null) {
1648
+ stateRef.current = initializeSession(props, permissionHandler);
1649
+ }
1650
+ const { interactiveSession, registry, manager } = stateRef.current;
1651
+ manager.onChange = () => forceRender((n) => n + 1);
1652
+ if (manager.history.length === 0) {
1653
+ const restored = interactiveSession.getFullHistory();
1654
+ if (restored.length > 0) {
1655
+ manager.syncHistory(restored);
1656
+ }
1657
+ }
1658
+ useEffect(() => {
1659
+ interactiveSession.on("text_delta", manager.onTextDelta);
1660
+ interactiveSession.on("tool_start", manager.onToolStart);
1661
+ interactiveSession.on("tool_end", manager.onToolEnd);
1662
+ interactiveSession.on("thinking", manager.onThinking);
1663
+ interactiveSession.on("complete", manager.onComplete);
1664
+ interactiveSession.on("interrupted", manager.onInterrupted);
1665
+ interactiveSession.on("error", manager.onError);
1666
+ interactiveSession.on("background_task_event", manager.onBackgroundTaskEvent);
1667
+ const initCheck = setInterval(() => {
1668
+ try {
1669
+ const ctx = interactiveSession.getContextState();
1670
+ manager.setContextState({
1671
+ percentage: ctx.usedPercentage,
1672
+ usedTokens: ctx.usedTokens,
1673
+ maxTokens: ctx.maxTokens
1674
+ });
1675
+ const restored = interactiveSession.getFullHistory();
1676
+ if (restored.length > 0) {
1677
+ manager.syncHistory(restored);
1678
+ }
1679
+ clearInterval(initCheck);
1680
+ } catch {
1681
+ }
1682
+ }, 200);
1683
+ return () => {
1684
+ clearInterval(initCheck);
1685
+ interactiveSession.off("text_delta", manager.onTextDelta);
1686
+ interactiveSession.off("tool_start", manager.onToolStart);
1687
+ interactiveSession.off("tool_end", manager.onToolEnd);
1688
+ interactiveSession.off("thinking", manager.onThinking);
1689
+ interactiveSession.off("complete", manager.onComplete);
1690
+ interactiveSession.off("interrupted", manager.onInterrupted);
1691
+ interactiveSession.off("error", manager.onError);
1692
+ interactiveSession.off("background_task_event", manager.onBackgroundTaskEvent);
1693
+ };
1694
+ }, [interactiveSession, manager]);
1695
+ useEffect(() => {
1696
+ manager.syncHistory(interactiveSession.getFullHistory());
1697
+ if (!manager.isThinking) {
1698
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
1699
+ }
1700
+ }, [manager.isThinking, interactiveSession, manager]);
1701
+ const handleSubmit = useSlashRouting(
1702
+ props.cwd,
1703
+ interactiveSession,
1704
+ registry,
1705
+ manager,
1706
+ props.providerDefinitions ?? []
1707
+ );
1708
+ const handleAbort = useCallback2(() => {
1709
+ manager.setAborting(true);
1710
+ interactiveSession.abort();
1711
+ }, [interactiveSession, manager]);
1712
+ const handleCancelQueue = useCallback2(() => {
1713
+ interactiveSession.cancelQueue();
1714
+ manager.setPendingPrompt(null);
1715
+ }, [interactiveSession, manager]);
1716
+ const handleShutdown = useCallback2(
1717
+ async (reason = "prompt_input_exit") => {
1718
+ if (isShuttingDown) return;
1719
+ setIsShuttingDown(true);
1720
+ manager.addEntry(messageToHistoryEntry2(createSystemMessage2("Shutting down...")));
1721
+ await interactiveSession.shutdown({ reason, message: "CLI shutdown" });
1722
+ },
1723
+ [interactiveSession, manager, isShuttingDown]
1724
+ );
1725
+ return {
1726
+ interactiveSession,
1727
+ registry,
1728
+ history: manager.history,
1729
+ addEntry: (entry) => manager.addEntry(entry),
1730
+ streamingText: manager.streamingText,
1731
+ activeTools: manager.activeTools,
1732
+ isThinking: manager.isThinking,
1733
+ isAborting: manager.isAborting,
1734
+ isShuttingDown,
1735
+ pendingPrompt: manager.pendingPrompt,
1736
+ backgroundTasks: manager.backgroundTasks,
1737
+ permissionRequest,
1738
+ contextState: manager.contextState,
1739
+ handleSubmit,
1740
+ handleAbort,
1741
+ handleCancelQueue,
1742
+ handleShutdown
1743
+ };
1744
+ }
1745
+
1746
+ // src/ui/hooks/usePluginCallbacks.ts
1747
+ import { useMemo } from "react";
1748
+ import { homedir as homedir3 } from "os";
1749
+ import { join as join6 } from "path";
1750
+ import {
1751
+ PluginSettingsStore,
1752
+ BundlePluginLoader as BundlePluginLoader2,
1753
+ BundlePluginInstaller,
1754
+ MarketplaceClient
1755
+ } from "@robota-sdk/agent-sdk";
1756
+ function usePluginCallbacks(cwd) {
1757
+ return useMemo(() => {
1758
+ const home = homedir3();
1759
+ const pluginsDir = join6(home, ".robota", "plugins");
1760
+ const userSettingsPath = join6(home, ".robota", "settings.json");
1761
+ const settingsStore = new PluginSettingsStore(userSettingsPath);
1762
+ const marketplace = new MarketplaceClient({ pluginsDir });
1763
+ const installer = new BundlePluginInstaller({
1764
+ pluginsDir,
1765
+ settingsStore,
1766
+ marketplaceClient: marketplace
1767
+ });
1768
+ const loader = new BundlePluginLoader2(pluginsDir);
1769
+ return {
1770
+ listInstalled: async () => {
1771
+ const plugins = await loader.loadAll();
1772
+ const enabledMap = settingsStore.getEnabledPlugins();
1773
+ return plugins.map((p) => {
1774
+ const parts = p.pluginDir.split("/");
1775
+ const cacheIdx = parts.indexOf("cache");
1776
+ const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
1777
+ const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
1778
+ return {
1779
+ name: fullId,
1780
+ description: p.manifest.description,
1781
+ enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
1782
+ };
1783
+ });
1784
+ },
1785
+ listAvailablePlugins: async (marketplaceName) => {
1786
+ let manifest;
1787
+ try {
1788
+ manifest = marketplace.fetchManifest(marketplaceName);
1789
+ } catch {
1790
+ return [];
1791
+ }
1792
+ const installed = installer.getInstalledPlugins();
1793
+ const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
1794
+ return manifest.plugins.map((p) => ({
1795
+ name: p.name,
1796
+ description: p.description,
1797
+ installed: installedNames.has(p.name)
1798
+ }));
1799
+ },
1800
+ install: async (pluginId, scope) => {
1801
+ const [name, marketplaceName] = pluginId.split("@");
1802
+ if (!name || !marketplaceName) {
1803
+ throw new Error("Plugin ID must be in format: name@marketplace");
1804
+ }
1805
+ if (scope === "project") {
1806
+ const projectPluginsDir = join6(cwd, ".robota", "plugins");
1807
+ const projectInstaller = new BundlePluginInstaller({
1808
+ pluginsDir: projectPluginsDir,
1809
+ settingsStore,
1810
+ marketplaceClient: marketplace
1811
+ });
1812
+ await projectInstaller.install(name, marketplaceName);
1813
+ } else {
1814
+ await installer.install(name, marketplaceName);
1815
+ }
1816
+ },
1817
+ uninstall: async (pluginId) => {
1818
+ await installer.uninstall(pluginId);
1819
+ },
1820
+ enable: async (pluginId) => {
1821
+ await installer.enable(pluginId);
1822
+ },
1823
+ disable: async (pluginId) => {
1824
+ await installer.disable(pluginId);
1825
+ },
1826
+ marketplaceAdd: async (source) => {
1827
+ if (source.includes("/") && !source.includes(":")) {
1828
+ return marketplace.addMarketplace({ type: "github", repo: source });
1829
+ } else {
1830
+ return marketplace.addMarketplace({ type: "git", url: source });
1831
+ }
1832
+ },
1833
+ marketplaceRemove: async (name) => {
1834
+ const installedFromMarketplace = installer.getPluginsByMarketplace(name);
1835
+ for (const record of installedFromMarketplace) {
1836
+ await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
1837
+ }
1838
+ marketplace.removeMarketplace(name);
1839
+ },
1840
+ marketplaceUpdate: async (name) => {
1841
+ marketplace.updateMarketplace(name);
1842
+ },
1843
+ marketplaceList: async () => {
1844
+ return marketplace.listMarketplaces().map((m) => ({
1845
+ name: m.name,
1846
+ type: m.source.type
1847
+ }));
1848
+ },
1849
+ reloadPlugins: async () => {
1850
+ }
1851
+ };
1852
+ }, [cwd]);
1853
+ }
1854
+
1855
+ // src/ui/hooks/useSideEffects.ts
1856
+ import { useState as useState2, useRef as useRef2, useCallback as useCallback3 } from "react";
1857
+ import { useApp } from "ink";
1858
+ import { createSystemMessage as createSystemMessage3, messageToHistoryEntry as messageToHistoryEntry3, getModelName } from "@robota-sdk/agent-core";
1859
+ var EXIT_DELAY_MS = 500;
1860
+ function useSideEffects({
1861
+ cwd,
1862
+ interactiveSession,
1863
+ addEntry,
1864
+ baseHandleSubmit,
1865
+ setSessionName,
1866
+ providerDefinitions
1867
+ }) {
1868
+ const { exit } = useApp();
1869
+ const [pendingModelId, setPendingModelId] = useState2(null);
1870
+ const pendingModelChangeRef = useRef2(null);
1871
+ const [pendingProviderProfile, setPendingProviderProfile] = useState2(null);
1872
+ const pendingProviderProfileRef = useRef2(null);
1873
+ const [pendingProviderSetupType, setPendingProviderSetupType] = useState2(null);
1874
+ const [showPluginTUI, setShowPluginTUI] = useState2(false);
1875
+ const [showSessionPicker, setShowSessionPicker] = useState2(false);
1876
+ const requestShutdown = useCallback3(
1877
+ (reason, message) => {
1878
+ addEntry(messageToHistoryEntry3(createSystemMessage3("Shutting down...")));
1879
+ setTimeout(() => {
1880
+ void interactiveSession.shutdown({ reason, message }).finally(() => exit());
1881
+ }, EXIT_DELAY_MS);
1882
+ },
1883
+ [interactiveSession, addEntry, exit]
1884
+ );
1885
+ const handleSubmit = useCallback3(
1886
+ async (input) => {
1887
+ await baseHandleSubmit(input);
1888
+ const sideEffects = interactiveSession;
1889
+ if (sideEffects._pendingModelId) {
1890
+ const modelId = sideEffects._pendingModelId;
1891
+ delete sideEffects._pendingModelId;
1892
+ pendingModelChangeRef.current = modelId;
1893
+ setPendingModelId(modelId);
1894
+ return;
1895
+ }
1896
+ if (sideEffects._pendingLanguage) {
1897
+ const lang = sideEffects._pendingLanguage;
1898
+ delete sideEffects._pendingLanguage;
1899
+ const settingsPath = getUserSettingsPath();
1900
+ const settings = readSettings(settingsPath);
1901
+ settings.language = lang;
1902
+ writeSettings(settingsPath, settings);
1903
+ addEntry(
1904
+ messageToHistoryEntry3(createSystemMessage3(`Language set to "${lang}". Restarting...`))
1905
+ );
1906
+ requestShutdown("other", "Language change restart");
1907
+ return;
1908
+ }
1909
+ if (sideEffects._pendingProviderProfile) {
1910
+ const profile = sideEffects._pendingProviderProfile;
1911
+ delete sideEffects._pendingProviderProfile;
1912
+ pendingProviderProfileRef.current = profile;
1913
+ setPendingProviderProfile(profile);
1914
+ return;
1915
+ }
1916
+ if (sideEffects._pendingProviderSetupType) {
1917
+ const type = sideEffects._pendingProviderSetupType;
1918
+ delete sideEffects._pendingProviderSetupType;
1919
+ setPendingProviderSetupType(type);
1920
+ return;
1921
+ }
1922
+ if (sideEffects._resetRequested) {
1923
+ delete sideEffects._resetRequested;
1924
+ const settingsPath = getUserSettingsPath();
1925
+ if (deleteSettings(settingsPath)) {
1926
+ addEntry(
1927
+ messageToHistoryEntry3(createSystemMessage3(`Deleted ${settingsPath}. Exiting...`))
1928
+ );
1929
+ } else {
1930
+ addEntry(messageToHistoryEntry3(createSystemMessage3("No user settings found.")));
1931
+ }
1932
+ requestShutdown("other", "Reset settings restart");
1933
+ return;
1934
+ }
1935
+ if (sideEffects._exitRequested) {
1936
+ delete sideEffects._exitRequested;
1937
+ requestShutdown("prompt_input_exit", "User requested exit");
1938
+ return;
1939
+ }
1940
+ if (sideEffects._triggerPluginTUI) {
1941
+ delete sideEffects._triggerPluginTUI;
1942
+ setShowPluginTUI(true);
1943
+ return;
1944
+ }
1945
+ if (sideEffects._triggerResumePicker) {
1946
+ delete sideEffects._triggerResumePicker;
1947
+ setShowSessionPicker(true);
1948
+ return;
1949
+ }
1950
+ if (sideEffects._sessionName) {
1951
+ const name = sideEffects._sessionName;
1952
+ delete sideEffects._sessionName;
1953
+ interactiveSession.setName(name);
1954
+ setSessionName(name);
1955
+ return;
1956
+ }
1957
+ },
1958
+ [interactiveSession, baseHandleSubmit, addEntry, requestShutdown, setSessionName]
1959
+ );
1960
+ const handleModelConfirm = useCallback3(
1961
+ (index) => {
1962
+ const modelId = pendingModelChangeRef.current;
1963
+ setPendingModelId(null);
1964
+ pendingModelChangeRef.current = null;
1965
+ if (index === 0 && modelId) {
1966
+ try {
1967
+ const settingsPath = getUserSettingsPath();
1968
+ updateModelInSettings(settingsPath, modelId);
1969
+ addEntry(
1970
+ messageToHistoryEntry3(
1971
+ createSystemMessage3(`Model changed to ${getModelName(modelId)}. Restarting...`)
1972
+ )
1973
+ );
1974
+ requestShutdown("other", "Model change restart");
1975
+ } catch (err) {
1976
+ addEntry(
1977
+ messageToHistoryEntry3(
1978
+ createSystemMessage3(`Failed: ${err instanceof Error ? err.message : String(err)}`)
1979
+ )
1980
+ );
1981
+ }
1982
+ } else {
1983
+ addEntry(messageToHistoryEntry3(createSystemMessage3("Model change cancelled.")));
1984
+ }
1985
+ },
1986
+ [addEntry, requestShutdown]
1987
+ );
1988
+ const handleProviderConfirm = useCallback3(
1989
+ (index) => {
1990
+ const profile = pendingProviderProfileRef.current;
1991
+ setPendingProviderProfile(null);
1992
+ pendingProviderProfileRef.current = null;
1993
+ if (index === 0 && profile) {
1994
+ try {
1995
+ const settingsPath = getUserSettingsPath();
1996
+ applyProviderSwitch(settingsPath, profile, {
1997
+ knownProviders: readMergedProviderSettings(cwd).providers
1998
+ });
1999
+ addEntry(
2000
+ messageToHistoryEntry3(
2001
+ createSystemMessage3(`Provider changed to ${profile}. Restarting...`)
2002
+ )
2003
+ );
2004
+ requestShutdown("other", "Provider change restart");
2005
+ } catch (err) {
2006
+ addEntry(
2007
+ messageToHistoryEntry3(
2008
+ createSystemMessage3(`Failed: ${err instanceof Error ? err.message : String(err)}`)
2009
+ )
2010
+ );
2011
+ }
2012
+ } else {
2013
+ addEntry(messageToHistoryEntry3(createSystemMessage3("Provider change cancelled.")));
2014
+ }
2015
+ },
2016
+ [cwd, addEntry, requestShutdown]
2017
+ );
2018
+ const handleProviderSetupSubmit = useCallback3(
2019
+ (input) => {
2020
+ setPendingProviderSetupType(null);
2021
+ try {
2022
+ const settingsPath = getUserSettingsPath();
2023
+ applyProviderConfiguration(settingsPath, input, { providerDefinitions });
2024
+ addEntry(
2025
+ messageToHistoryEntry3(
2026
+ createSystemMessage3(`Provider ${input.profile} configured. Restarting...`)
2027
+ )
2028
+ );
2029
+ requestShutdown("other", "Provider setup restart");
2030
+ } catch (err) {
2031
+ addEntry(
2032
+ messageToHistoryEntry3(
2033
+ createSystemMessage3(`Failed: ${err instanceof Error ? err.message : String(err)}`)
2034
+ )
2035
+ );
2036
+ }
2037
+ },
2038
+ [addEntry, requestShutdown, providerDefinitions]
2039
+ );
2040
+ const handleProviderSetupCancel = useCallback3(() => {
2041
+ setPendingProviderSetupType(null);
2042
+ addEntry(messageToHistoryEntry3(createSystemMessage3("Provider setup cancelled.")));
2043
+ }, [addEntry]);
2044
+ return {
2045
+ handleSubmit,
2046
+ pendingModelId,
2047
+ pendingProviderProfile,
2048
+ pendingProviderSetupType,
2049
+ showPluginTUI,
2050
+ showSessionPicker,
2051
+ setPendingModelId,
2052
+ setShowPluginTUI,
2053
+ setShowSessionPicker,
2054
+ handleModelConfirm,
2055
+ handleProviderConfirm,
2056
+ handleProviderSetupSubmit,
2057
+ handleProviderSetupCancel
2058
+ };
2059
+ }
2060
+
2061
+ // src/ui/MessageList.tsx
2062
+ import React from "react";
2063
+ import { Box as Box2, Text as Text2 } from "ink";
2064
+ import { isToolMessage, isAssistantMessage } from "@robota-sdk/agent-core";
2065
+
2066
+ // src/ui/render-markdown.ts
2067
+ import { marked } from "marked";
2068
+ import TerminalRenderer from "marked-terminal";
2069
+ marked.setOptions({
2070
+ renderer: new TerminalRenderer()
2071
+ });
2072
+ function renderMarkdown(md) {
2073
+ const result = marked.parse(md);
2074
+ return typeof result === "string" ? result.trimEnd() : md;
2075
+ }
2076
+
2077
+ // src/ui/DiffBlock.tsx
2078
+ import { Box, Text } from "ink";
2079
+ import { jsxs } from "react/jsx-runtime";
2080
+ var MAX_DIFF_LINES = 12;
2081
+ var TRUNCATED_SHOW = 10;
2082
+ function DiffBlock({ file, lines }) {
2083
+ const truncated = lines.length > MAX_DIFF_LINES;
2084
+ const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
2085
+ const remaining = lines.length - TRUNCATED_SHOW;
2086
+ const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
2087
+ const numWidth = String(maxLineNum).length;
2088
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [
2089
+ file && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
2090
+ "\u2502 ",
2091
+ file
2092
+ ] }),
2093
+ visible.map((line, i) => {
2094
+ const lineNum = String(line.lineNumber).padStart(numWidth, " ");
2095
+ if (line.type === "context") {
2096
+ return /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
2097
+ "\u2502 ",
2098
+ lineNum,
2099
+ " ",
2100
+ line.text
2101
+ ] }, i);
2102
+ }
2103
+ const prefix = line.type === "remove" ? "-" : "+";
2104
+ const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
2105
+ const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
2106
+ return /* @__PURE__ */ jsxs(Text, { color: fgColor, backgroundColor: bgColor, children: [
2107
+ "\u2502 ",
2108
+ lineNum,
2109
+ " ",
2110
+ prefix,
2111
+ " ",
2112
+ line.text
2113
+ ] }, i);
2114
+ }),
2115
+ truncated && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
2116
+ "\u2502 ... and ",
2117
+ remaining,
2118
+ " more lines"
2119
+ ] })
2120
+ ] });
2121
+ }
2122
+
2123
+ // src/ui/MessageList.tsx
2124
+ import { Fragment, jsx, jsxs as jsxs2 } from "react/jsx-runtime";
2125
+ function RoleLabel({ role }) {
2126
+ switch (role) {
2127
+ case "user":
2128
+ return /* @__PURE__ */ jsxs2(Text2, { color: "green", bold: true, children: [
2129
+ "You:",
2130
+ " "
2131
+ ] });
2132
+ case "assistant":
2133
+ return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", bold: true, children: [
2134
+ "Robota:",
2135
+ " "
2136
+ ] });
2137
+ case "system":
2138
+ return /* @__PURE__ */ jsxs2(Text2, { color: "yellow", bold: true, children: [
2139
+ "System:",
2140
+ " "
2141
+ ] });
2142
+ case "tool":
2143
+ return /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
2144
+ "Tool:",
2145
+ " "
2146
+ ] });
2147
+ }
2148
+ }
2149
+ function ToolMessage({ message }) {
2150
+ if (!isToolMessage(message)) {
2151
+ return /* @__PURE__ */ jsx(Fragment, {});
2152
+ }
2153
+ const toolName = message.name;
2154
+ const content = message.content;
2155
+ let summaries = null;
2156
+ try {
2157
+ const parsed = JSON.parse(content);
2158
+ if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
2159
+ summaries = parsed;
2160
+ }
2161
+ } catch {
2162
+ }
2163
+ if (summaries) {
2164
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2165
+ /* @__PURE__ */ jsxs2(Box2, { children: [
2166
+ /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
2167
+ "Tool:",
2168
+ " "
2169
+ ] }),
2170
+ toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
2171
+ "[",
2172
+ toolName,
2173
+ "]"
2174
+ ] })
2175
+ ] }),
2176
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
2177
+ summaries.map((s, i) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2178
+ /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
2179
+ " ",
2180
+ "\u2713",
2181
+ " ",
2182
+ s.line
2183
+ ] }),
2184
+ s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ jsx(DiffBlock, { file: s.diffFile, lines: s.diffLines })
2185
+ ] }, i))
2186
+ ] });
2187
+ }
2188
+ const lines = content.split("\n").filter((l) => l.trim());
2189
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2190
+ /* @__PURE__ */ jsxs2(Box2, { children: [
2191
+ /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
2192
+ "Tool:",
2193
+ " "
2194
+ ] }),
2195
+ toolName && /* @__PURE__ */ jsxs2(Text2, { color: "white", dimColor: true, children: [
2196
+ "[",
2197
+ toolName,
2198
+ "]"
2199
+ ] })
2200
+ ] }),
2201
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
2202
+ lines.map((line, i) => /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
2203
+ " ",
2204
+ "\u2713",
2205
+ " ",
2206
+ line
2207
+ ] }, i))
2208
+ ] });
2209
+ }
2210
+ var MessageItem = React.memo(function MessageItem2({
2211
+ message
2212
+ }) {
2213
+ if (isToolMessage(message)) {
2214
+ return /* @__PURE__ */ jsx(ToolMessage, { message });
2215
+ }
2216
+ const content = message.content ?? "";
2217
+ const isInterrupted = message.state === "interrupted";
2218
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2219
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsx(RoleLabel, { role: message.role }) }),
2220
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
2221
+ /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: isAssistantMessage(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
2222
+ ] });
2223
+ });
2224
+ function ToolSummaryEntry({ entry }) {
2225
+ const data = entry.data;
2226
+ const lines = data?.summary?.split("\n") ?? [];
2227
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2228
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
2229
+ "Tool:",
2230
+ " "
2231
+ ] }) }),
2232
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
2233
+ lines.map((line, i) => /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
2234
+ " ",
2235
+ line
2236
+ ] }, i))
2237
+ ] });
2238
+ }
2239
+ function EventEntry({ entry }) {
2240
+ const eventData = entry.data;
2241
+ const eventMessage = typeof eventData?.message === "string" ? eventData.message : typeof eventData?.content === "string" ? eventData.content : entry.type;
2242
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2243
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "yellow", bold: true, children: [
2244
+ "System:",
2245
+ " "
2246
+ ] }) }),
2247
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
2248
+ /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: eventMessage }) })
2249
+ ] });
2250
+ }
2251
+ function EntryItem({ entry }) {
2252
+ if (entry.category === "chat") {
2253
+ const message = entry.data;
2254
+ return /* @__PURE__ */ jsx(MessageItem, { message });
2255
+ }
2256
+ if (entry.type === "tool-summary") {
2257
+ return /* @__PURE__ */ jsx(ToolSummaryEntry, { entry });
2258
+ }
2259
+ if (entry.type === "tool-start" || entry.type === "tool-end") {
2260
+ return /* @__PURE__ */ jsx(Fragment, {});
2261
+ }
2262
+ return /* @__PURE__ */ jsx(EventEntry, { entry });
2263
+ }
2264
+ function MessageList({ history }) {
2265
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", children: history.map((entry) => /* @__PURE__ */ jsx(EntryItem, { entry }, entry.id)) });
2266
+ }
2267
+
2268
+ // src/ui/StatusBar.tsx
2269
+ import { Box as Box3, Text as Text3 } from "ink";
2270
+ import { formatTokenCount } from "@robota-sdk/agent-core";
2271
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs3 } from "react/jsx-runtime";
2272
+ var CONTEXT_YELLOW_THRESHOLD = 70;
2273
+ var CONTEXT_RED_THRESHOLD = 90;
2274
+ function getContextColor(percentage) {
2275
+ if (percentage >= CONTEXT_RED_THRESHOLD) return "red";
2276
+ if (percentage >= CONTEXT_YELLOW_THRESHOLD) return "yellow";
2277
+ return "green";
2278
+ }
2279
+ function StatusBar({
2280
+ permissionMode,
2281
+ modelName,
2282
+ sessionId: _sessionId,
2283
+ messageCount,
2284
+ isThinking,
2285
+ contextPercentage,
2286
+ contextUsedTokens,
2287
+ contextMaxTokens,
2288
+ sessionName
2289
+ }) {
2290
+ const contextColor = getContextColor(contextPercentage);
2291
+ return /* @__PURE__ */ jsxs3(
2292
+ Box3,
2293
+ {
2294
+ borderStyle: "single",
2295
+ borderColor: "gray",
2296
+ paddingLeft: 1,
2297
+ paddingRight: 1,
2298
+ justifyContent: "space-between",
2299
+ children: [
2300
+ /* @__PURE__ */ jsxs3(Text3, { children: [
2301
+ /* @__PURE__ */ jsx2(Text3, { color: "cyan", bold: true, children: "Mode:" }),
2302
+ " ",
2303
+ /* @__PURE__ */ jsx2(Text3, { children: permissionMode }),
2304
+ sessionName && /* @__PURE__ */ jsxs3(Fragment2, { children: [
2305
+ " | ",
2306
+ /* @__PURE__ */ jsx2(Text3, { color: "magenta", children: sessionName })
2307
+ ] }),
2308
+ " | ",
2309
+ /* @__PURE__ */ jsx2(Text3, { dimColor: true, children: modelName }),
2310
+ " | ",
2311
+ /* @__PURE__ */ jsxs3(Text3, { color: contextColor, children: [
2312
+ "Context: ",
2313
+ Math.round(contextPercentage),
2314
+ "% (",
2315
+ formatTokenCount(contextUsedTokens),
2316
+ "/",
2317
+ formatTokenCount(contextMaxTokens),
2318
+ ")"
2319
+ ] })
2320
+ ] }),
2321
+ /* @__PURE__ */ jsxs3(Text3, { children: [
2322
+ isThinking && /* @__PURE__ */ jsx2(Text3, { color: "yellow", children: "Thinking... " }),
2323
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
2324
+ "msgs: ",
2325
+ messageCount
2326
+ ] })
2327
+ ] })
2328
+ ]
2329
+ }
2330
+ );
2331
+ }
2332
+
2333
+ // src/ui/InputArea.tsx
2334
+ import { useState as useState6, useCallback as useCallback4, useRef as useRef4 } from "react";
2335
+ import { Box as Box5, Text as Text7, useInput as useInput2, useStdout } from "ink";
2336
+
2337
+ // src/ui/CjkTextInput.tsx
2338
+ import { useRef as useRef3, useState as useState3 } from "react";
2339
+ import { Text as Text4, useInput } from "ink";
2340
+ import chalk from "chalk";
2341
+
2342
+ // src/ui/flows/cjk-text-input-flow.ts
2343
+ import stringWidth from "string-width";
2344
+ var PASTE_START = "[200~";
2345
+ var PASTE_END = "[201~";
2346
+ var LAST_ASCII_CONTROL_CODE = 31;
2347
+ var DELETE_CONTROL_CODE = 127;
2348
+ function createCjkTextInputFlowState(value) {
2349
+ return { value, cursor: value.length, isPasting: false, pasteBuffer: "" };
2350
+ }
2351
+ function syncCjkTextInputFlowState(state, value, cursorHint) {
2352
+ if (value === state.value) {
2353
+ return state;
2354
+ }
2355
+ return {
2356
+ ...state,
2357
+ value,
2358
+ cursor: cursorHint != null ? Math.min(cursorHint, value.length) : value.length
2359
+ };
2360
+ }
2361
+ function applyCjkTextInput(state, input, key, options) {
2362
+ const pasteResult = applyPasteBoundaryInput(state, input, options);
2363
+ if (pasteResult !== void 0) return pasteResult;
2364
+ const controlResult = applyControlInput(state, input, key, options);
2365
+ if (controlResult !== void 0) return controlResult;
2366
+ const cursorResult = applyCursorInput(state, key, options.availableWidth);
2367
+ if (cursorResult !== void 0) return cursorResult;
2368
+ return insertPrintableInput(state, input);
2369
+ }
2370
+ function applyPasteBoundaryInput(state, input, options) {
2371
+ if (input === PASTE_START || input.startsWith(PASTE_START)) {
2372
+ return startBracketedPaste(state, input);
2373
+ }
2374
+ if (state.isPasting) {
2375
+ return continueBracketedPaste(state, input, options);
2376
+ }
2377
+ return void 0;
2378
+ }
2379
+ function applyControlInput(state, input, key, options) {
2380
+ if (key.ctrl === true && input === "c" || key.tab === true) {
2381
+ return { state, effect: { type: "none" } };
2382
+ }
2383
+ if (key.return === true) {
2384
+ return { state, effect: { type: "submit", value: state.value } };
2385
+ }
2386
+ if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && options.canPaste) {
2387
+ return {
2388
+ state,
2389
+ effect: { type: "paste", text: input.replace(/\r\n?/g, "\n"), cursor: state.cursor }
2390
+ };
2391
+ }
2392
+ return void 0;
2393
+ }
2394
+ function applyCursorInput(state, key, availableWidth) {
2395
+ if (key.upArrow === true || key.downArrow === true) {
2396
+ return moveCursorVertically(state, key.upArrow === true ? "up" : "down", availableWidth);
2397
+ }
2398
+ if (key.leftArrow === true) {
2399
+ return moveCursorHorizontally(state, "left");
2400
+ }
2401
+ if (key.rightArrow === true) {
2402
+ return moveCursorHorizontally(state, "right");
2403
+ }
2404
+ if (key.backspace === true || key.delete === true) {
2405
+ return deleteBeforeCursor(state);
2406
+ }
2407
+ return void 0;
2408
+ }
2409
+ function filterPrintable(input) {
2410
+ if (!input || input.length === 0) return "";
2411
+ let output = "";
2412
+ for (const char of input) {
2413
+ const code = char.charCodeAt(0);
2414
+ if (code > LAST_ASCII_CONTROL_CODE && code !== DELETE_CONTROL_CODE) {
2415
+ output += char;
2416
+ }
2417
+ }
2418
+ return output;
2419
+ }
2420
+ function insertAtCursor(value, cursor, input) {
2421
+ const next = value.slice(0, cursor) + input + value.slice(cursor);
2422
+ return { value: next, cursor: cursor + input.length };
2423
+ }
2424
+ function displayOffset(chars, charIndex, width) {
2425
+ let offset = 0;
2426
+ for (let i = 0; i < charIndex && i < chars.length; i++) {
2427
+ const w = stringWidth(chars[i]);
2428
+ const col = offset % width;
2429
+ if (col + w > width) offset += width - col;
2430
+ offset += w;
2431
+ }
2432
+ return offset;
2433
+ }
2434
+ function charIndexAtDisplayOffset(chars, target, width) {
2435
+ let offset = 0;
2436
+ for (let i = 0; i < chars.length; i++) {
2437
+ if (offset >= target) return i;
2438
+ const w = stringWidth(chars[i]);
2439
+ const col = offset % width;
2440
+ if (col + w > width) offset += width - col;
2441
+ offset += w;
2442
+ }
2443
+ return chars.length;
2444
+ }
2445
+ function startBracketedPaste(state, input) {
2446
+ return {
2447
+ state: { ...state, isPasting: true, pasteBuffer: input.slice(PASTE_START.length) },
2448
+ effect: { type: "none" }
2449
+ };
2450
+ }
2451
+ function continueBracketedPaste(state, input, options) {
2452
+ if (input !== PASTE_END && !input.includes(PASTE_END)) {
2453
+ return {
2454
+ state: { ...state, pasteBuffer: state.pasteBuffer + input },
2455
+ effect: { type: "none" }
2456
+ };
2457
+ }
2458
+ const beforeMarker = input.split(PASTE_END)[0] ?? "";
2459
+ const text = (state.pasteBuffer + beforeMarker).replace(/\r\n?/g, "\n");
2460
+ const nextState = { ...state, isPasting: false, pasteBuffer: "" };
2461
+ if (text.length === 0) {
2462
+ return { state: nextState, effect: { type: "none" } };
2463
+ }
2464
+ if (text.includes("\n") && options.canPaste) {
2465
+ return { state: nextState, effect: { type: "paste", text, cursor: state.cursor } };
2466
+ }
2467
+ return insertPrintableInput(nextState, text);
2468
+ }
2469
+ function moveCursorVertically(state, direction, availableWidth) {
2470
+ if (!availableWidth || availableWidth <= 0) {
2471
+ return { state, effect: { type: "none" } };
2472
+ }
2473
+ const chars = [...state.value];
2474
+ const offset = displayOffset(chars, state.cursor, availableWidth);
2475
+ const target = direction === "up" ? offset - availableWidth : offset + availableWidth;
2476
+ if (target < 0) {
2477
+ return { state, effect: { type: "none" } };
2478
+ }
2479
+ const cursor = charIndexAtDisplayOffset(chars, target, availableWidth);
2480
+ if (cursor === state.cursor) {
2481
+ return { state, effect: { type: "none" } };
2482
+ }
2483
+ return { state: { ...state, cursor }, effect: { type: "render" } };
2484
+ }
2485
+ function moveCursorHorizontally(state, direction) {
2486
+ if (direction === "left" && state.cursor > 0) {
2487
+ return { state: { ...state, cursor: state.cursor - 1 }, effect: { type: "render" } };
2488
+ }
2489
+ if (direction === "right" && state.cursor < state.value.length) {
2490
+ return { state: { ...state, cursor: state.cursor + 1 }, effect: { type: "render" } };
2491
+ }
2492
+ return { state, effect: { type: "none" } };
2493
+ }
2494
+ function deleteBeforeCursor(state) {
2495
+ if (state.cursor === 0) {
2496
+ return { state, effect: { type: "none" } };
2497
+ }
2498
+ const value = state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor);
2499
+ return {
2500
+ state: { ...state, value, cursor: state.cursor - 1 },
2501
+ effect: { type: "change", value }
2502
+ };
2503
+ }
2504
+ function insertPrintableInput(state, input) {
2505
+ const printable = filterPrintable(input);
2506
+ if (printable.length === 0) {
2507
+ return { state, effect: { type: "none" } };
2508
+ }
2509
+ const result = insertAtCursor(state.value, state.cursor, printable);
2510
+ return {
2511
+ state: { ...state, value: result.value, cursor: result.cursor },
2512
+ effect: { type: "change", value: result.value }
2513
+ };
2514
+ }
2515
+
2516
+ // src/ui/CjkTextInput.tsx
2517
+ import { jsx as jsx3 } from "react/jsx-runtime";
2518
+ function CjkTextInput({
2519
+ value,
2520
+ onChange,
2521
+ onSubmit,
2522
+ onPaste,
2523
+ placeholder = "",
2524
+ focus = true,
2525
+ showCursor = true,
2526
+ availableWidth,
2527
+ cursorHint = null
2528
+ }) {
2529
+ const stateRef = useRef3(createCjkTextInputFlowState(value));
2530
+ const [, forceRender] = useState3(0);
2531
+ stateRef.current = syncCjkTextInputFlowState(stateRef.current, value, cursorHint);
2532
+ useInput(
2533
+ (input, key) => {
2534
+ try {
2535
+ const result = applyCjkTextInput(stateRef.current, input, key, {
2536
+ availableWidth,
2537
+ canPaste: onPaste !== void 0
2538
+ });
2539
+ stateRef.current = result.state;
2540
+ applyCjkTextInputEffect(result.effect, onChange, onSubmit, onPaste, forceRender);
2541
+ } catch {
2542
+ }
2543
+ },
2544
+ { isActive: focus }
2545
+ );
2546
+ return /* @__PURE__ */ jsx3(Text4, { children: renderWithCursor(
2547
+ stateRef.current.value,
2548
+ stateRef.current.cursor,
2549
+ placeholder,
2550
+ showCursor && focus
2551
+ ) });
2552
+ }
2553
+ function applyCjkTextInputEffect(effect, onChange, onSubmit, onPaste, forceRender) {
2554
+ if (effect.type === "change") {
2555
+ onChange(effect.value);
2556
+ } else if (effect.type === "submit") {
2557
+ onSubmit?.(effect.value);
2558
+ } else if (effect.type === "paste") {
2559
+ onPaste?.(effect.text, effect.cursor);
2560
+ } else if (effect.type === "render") {
2561
+ forceRender((n) => n + 1);
2562
+ }
2563
+ }
2564
+ function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
2565
+ if (!showCursor) {
2566
+ return value.length > 0 ? value : placeholder ? chalk.gray(placeholder) : "";
2567
+ }
2568
+ if (value.length === 0) {
2569
+ if (placeholder.length > 0) {
2570
+ return chalk.inverse(placeholder[0]) + chalk.gray(placeholder.slice(1));
2571
+ }
2572
+ return chalk.inverse(" ");
2573
+ }
2574
+ const chars = [...value];
2575
+ let rendered = "";
2576
+ for (let i = 0; i < chars.length; i++) {
2577
+ const char = chars[i] ?? "";
2578
+ rendered += i === cursorOffset ? chalk.inverse(char) : char;
2579
+ }
2580
+ if (cursorOffset >= chars.length) {
2581
+ rendered += chalk.inverse(" ");
2582
+ }
2583
+ return rendered;
2584
+ }
2585
+
2586
+ // src/ui/WaveText.tsx
2587
+ import { useState as useState4, useEffect as useEffect2 } from "react";
2588
+ import { Text as Text5 } from "ink";
2589
+ import { jsx as jsx4 } from "react/jsx-runtime";
2590
+ var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
2591
+ var INTERVAL_MS = 400;
2592
+ var CHARS_PER_GROUP = 4;
2593
+ function WaveText({ text }) {
2594
+ const [tick, setTick] = useState4(0);
2595
+ useEffect2(() => {
2596
+ const timer = setInterval(() => {
2597
+ setTick((prev) => prev + 1);
2598
+ }, INTERVAL_MS);
2599
+ return () => clearInterval(timer);
2600
+ }, []);
2601
+ const chars = [...text];
2602
+ return /* @__PURE__ */ jsx4(Text5, { children: chars.map((char, i) => {
2603
+ const group = Math.floor(i / CHARS_PER_GROUP);
2604
+ const colorIndex = (tick + group) % WAVE_COLORS.length;
2605
+ return /* @__PURE__ */ jsx4(Text5, { color: WAVE_COLORS[colorIndex], children: char }, i);
2606
+ }) });
2607
+ }
2608
+
2609
+ // src/ui/SlashAutocomplete.tsx
2610
+ import { Box as Box4, Text as Text6 } from "ink";
2611
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2612
+ var MAX_VISIBLE = 8;
2613
+ function CommandRow(props) {
2614
+ const { cmd, isSelected, showSlash } = props;
2615
+ const indicator = isSelected ? "\u25B8 " : " ";
2616
+ const nameColor = isSelected ? "cyan" : void 0;
2617
+ const dimmed = !isSelected;
2618
+ return /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsxs4(Text6, { color: nameColor, dimColor: dimmed, children: [
2619
+ indicator,
2620
+ showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
2621
+ ] }) });
2622
+ }
2623
+ function SlashAutocomplete({
2624
+ commands,
2625
+ selectedIndex,
2626
+ visible,
2627
+ isSubcommandMode
2628
+ }) {
2629
+ if (!visible || commands.length === 0) return null;
2630
+ const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
2631
+ const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
2632
+ return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx5(
2633
+ CommandRow,
2634
+ {
2635
+ cmd,
2636
+ isSelected: scrollOffset + i === selectedIndex,
2637
+ showSlash: !isSubcommandMode
2638
+ },
2639
+ cmd.name
2640
+ )) });
2641
+ }
2642
+ function computeScrollOffset(selectedIndex, total) {
2643
+ if (total <= MAX_VISIBLE) return 0;
2644
+ if (selectedIndex < MAX_VISIBLE) return 0;
2645
+ const maxOffset = total - MAX_VISIBLE;
2646
+ return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
2647
+ }
2648
+
2649
+ // src/utils/paste-labels.ts
2650
+ var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
2651
+ function expandPasteLabels(text, store) {
2652
+ return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
2653
+ }
2654
+
2655
+ // src/ui/hooks/useAutocomplete.ts
2656
+ import React4, { useState as useState5, useMemo as useMemo2 } from "react";
2657
+ function parseSlashInput(value) {
2658
+ if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
2659
+ const afterSlash = value.slice(1);
2660
+ const spaceIndex = afterSlash.indexOf(" ");
2661
+ if (spaceIndex === -1) return { isSlash: true, parentCommand: "", filter: afterSlash };
2662
+ const parent = afterSlash.slice(0, spaceIndex);
2663
+ const rest = afterSlash.slice(spaceIndex + 1);
2664
+ return { isSlash: true, parentCommand: parent, filter: rest };
2665
+ }
2666
+ function useAutocomplete(value, registry) {
2667
+ const [selectedIndex, setSelectedIndex] = useState5(0);
2668
+ const [dismissed, setDismissed] = useState5(false);
2669
+ const prevValueRef = React4.useRef(value);
2670
+ if (prevValueRef.current !== value) {
2671
+ prevValueRef.current = value;
2672
+ if (dismissed) setDismissed(false);
2673
+ }
2674
+ const parsed = parseSlashInput(value);
2675
+ const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
2676
+ const filteredCommands = useMemo2(() => {
2677
+ if (!registry || !parsed.isSlash || dismissed) return [];
2678
+ if (isSubcommandMode) {
2679
+ const subs = registry.getSubcommands(parsed.parentCommand);
2680
+ if (subs.length === 0) return [];
2681
+ if (!parsed.filter) return subs;
2682
+ const lower = parsed.filter.toLowerCase();
2683
+ return subs.filter((c) => c.name.toLowerCase().startsWith(lower));
2684
+ }
2685
+ return registry.getCommands(parsed.filter);
2686
+ }, [registry, parsed.isSlash, parsed.parentCommand, parsed.filter, dismissed, isSubcommandMode]);
2687
+ const showPopup = parsed.isSlash && filteredCommands.length > 0 && !dismissed;
2688
+ if (selectedIndex >= filteredCommands.length && filteredCommands.length > 0) {
2689
+ setSelectedIndex(filteredCommands.length - 1);
2690
+ }
2691
+ return {
2692
+ showPopup,
2693
+ filteredCommands,
2694
+ selectedIndex,
2695
+ setSelectedIndex,
2696
+ isSubcommandMode,
2697
+ setShowPopup: (val) => {
2698
+ if (typeof val === "function") {
2699
+ setDismissed((prev) => {
2700
+ const nextVal = val(!prev);
2701
+ return !nextVal;
2702
+ });
2703
+ } else {
2704
+ setDismissed(!val);
2705
+ }
2706
+ }
2707
+ };
2708
+ }
2709
+
2710
+ // src/ui/flows/input-area-flow.ts
2711
+ function getAutocompletePopupAction(key) {
2712
+ if (key.upArrow === true) return "previous";
2713
+ if (key.downArrow === true) return "next";
2714
+ if (key.escape === true) return "close";
2715
+ if (key.tab === true) return "complete";
2716
+ return void 0;
2717
+ }
2718
+ function getPendingPromptInputAction(key) {
2719
+ if (key.backspace === true || key.delete === true) {
2720
+ return "cancelQueue";
2721
+ }
2722
+ return void 0;
2723
+ }
2724
+ function moveAutocompleteSelection(selectedIndex, commandCount, direction) {
2725
+ if (commandCount === 0) return 0;
2726
+ if (direction === "previous") {
2727
+ return selectedIndex > 0 ? selectedIndex - 1 : commandCount - 1;
2728
+ }
2729
+ return selectedIndex < commandCount - 1 ? selectedIndex + 1 : 0;
2730
+ }
2731
+ function resolveTabCompletion(value, command) {
2732
+ const parsed = parseSlashInput(value);
2733
+ if (parsed.parentCommand) {
2734
+ return { type: "insert", value: `/${parsed.parentCommand} ${command.name} ` };
2735
+ }
2736
+ if (command.subcommands && command.subcommands.length > 0) {
2737
+ return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
2738
+ }
2739
+ return { type: "insert", value: `/${command.name} ` };
2740
+ }
2741
+ function resolveEnterCommandSelection(value, command) {
2742
+ const parsed = parseSlashInput(value);
2743
+ if (parsed.parentCommand) {
2744
+ return { type: "submit", value: `/${parsed.parentCommand} ${command.name}` };
2745
+ }
2746
+ if (command.subcommands && command.subcommands.length > 0) {
2747
+ return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
2748
+ }
2749
+ return { type: "submit", value: `/${command.name}` };
2750
+ }
2751
+ function createPasteLabelChange(value, cursorPosition, pasteId, text) {
2752
+ const lineCount = text.split("\n").length;
2753
+ const label = `[Pasted text #${pasteId} +${lineCount} lines]`;
2754
+ return {
2755
+ value: value.slice(0, cursorPosition) + label + value.slice(cursorPosition),
2756
+ cursorHint: cursorPosition + label.length,
2757
+ label,
2758
+ lineCount
2759
+ };
2760
+ }
2761
+ function shouldSubmitInput(text) {
2762
+ return text.trim().length > 0;
2763
+ }
2764
+
2765
+ // src/ui/InputArea.tsx
2766
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2767
+ var BORDER_HORIZONTAL = 2;
2768
+ var PADDING_LEFT = 1;
2769
+ var PROMPT_WIDTH = 2;
2770
+ var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
2771
+ function InputArea({
2772
+ onSubmit,
2773
+ onCancelQueue,
2774
+ isDisabled,
2775
+ isAborting,
2776
+ pendingPrompt,
2777
+ registry,
2778
+ sessionName
2779
+ }) {
2780
+ const [value, setValue] = useState6("");
2781
+ const [cursorHint, setCursorHint] = useState6(null);
2782
+ const pasteStore = useRef4(/* @__PURE__ */ new Map());
2783
+ const { stdout } = useStdout();
2784
+ const terminalColumns = stdout?.columns ?? 80;
2785
+ const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
2786
+ const pasteIdRef = useRef4(0);
2787
+ const {
2788
+ showPopup,
2789
+ filteredCommands,
2790
+ selectedIndex,
2791
+ setSelectedIndex,
2792
+ isSubcommandMode,
2793
+ setShowPopup
2794
+ } = useAutocomplete(value, registry);
2795
+ const handlePaste = useCallback4((text, cursorPosition) => {
2796
+ pasteIdRef.current += 1;
2797
+ const id = pasteIdRef.current;
2798
+ pasteStore.current.set(id, text);
2799
+ setValue((prev) => {
2800
+ const change = createPasteLabelChange(prev, cursorPosition, id, text);
2801
+ setCursorHint(change.cursorHint);
2802
+ return change.value;
2803
+ });
2804
+ }, []);
2805
+ const tabCompleteCommand = useCallback4(
2806
+ (cmd) => {
2807
+ const result = resolveTabCompletion(value, cmd);
2808
+ if (result.type === "insert") {
2809
+ setValue(result.value);
2810
+ if (result.selectedIndex !== void 0) {
2811
+ setSelectedIndex(result.selectedIndex);
2812
+ }
2813
+ }
2814
+ },
2815
+ [value, setSelectedIndex]
2816
+ );
2817
+ const enterSelectCommand = useCallback4(
2818
+ (cmd) => {
2819
+ const result = resolveEnterCommandSelection(value, cmd);
2820
+ if (result.type === "insert") {
2821
+ setValue(result.value);
2822
+ if (result.selectedIndex !== void 0) {
2823
+ setSelectedIndex(result.selectedIndex);
2824
+ }
2825
+ return;
2826
+ }
2827
+ setValue("");
2828
+ onSubmit(result.value);
2829
+ },
2830
+ [value, onSubmit, setSelectedIndex]
2831
+ );
2832
+ const handleSubmit = useCallback4(
2833
+ (text) => {
2834
+ if (!shouldSubmitInput(text)) return;
2835
+ if (showPopup && filteredCommands[selectedIndex]) {
2836
+ enterSelectCommand(filteredCommands[selectedIndex]);
2837
+ return;
2838
+ }
2839
+ const expanded = expandPasteLabels(text.trim(), pasteStore.current);
2840
+ setValue("");
2841
+ pasteStore.current.clear();
2842
+ pasteIdRef.current = 0;
2843
+ onSubmit(expanded);
2844
+ },
2845
+ [showPopup, filteredCommands, selectedIndex, onSubmit, enterSelectCommand]
2846
+ );
2847
+ useInput2(
2848
+ (_input, key) => {
2849
+ if (!showPopup) return;
2850
+ const action = getAutocompletePopupAction(key);
2851
+ if (action === "previous" || action === "next") {
2852
+ setSelectedIndex(
2853
+ (prev) => moveAutocompleteSelection(prev, filteredCommands.length, action)
2854
+ );
2855
+ } else if (action === "close") {
2856
+ setShowPopup(false);
2857
+ } else if (action === "complete") {
2858
+ const cmd = filteredCommands[selectedIndex];
2859
+ if (cmd) tabCompleteCommand(cmd);
2860
+ }
2861
+ },
2862
+ { isActive: showPopup && !isDisabled }
2863
+ );
2864
+ useInput2(
2865
+ (_input, key) => {
2866
+ if (getPendingPromptInputAction(key) === "cancelQueue" && pendingPrompt) {
2867
+ onCancelQueue?.();
2868
+ }
2869
+ },
2870
+ { isActive: !!pendingPrompt }
2871
+ );
2872
+ const borderColor = isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green";
2873
+ const innerWidth = Math.max(1, terminalColumns - BORDER_HORIZONTAL);
2874
+ const topBorder = (() => {
2875
+ if (sessionName) {
2876
+ const label = ` "${sessionName}" `;
2877
+ const rightPad = 2;
2878
+ const leftLen = Math.max(0, innerWidth - label.length - rightPad);
2879
+ return { left: "\u250C" + "\u2500".repeat(leftLen), label, right: "\u2500".repeat(rightPad) + "\u2510" };
2880
+ }
2881
+ return { left: "\u250C" + "\u2500".repeat(innerWidth), label: "", right: "\u2510" };
2882
+ })();
2883
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2884
+ showPopup && /* @__PURE__ */ jsx6(
2885
+ SlashAutocomplete,
2886
+ {
2887
+ commands: filteredCommands,
2888
+ selectedIndex,
2889
+ visible: showPopup,
2890
+ isSubcommandMode
2891
+ }
2892
+ ),
2893
+ /* @__PURE__ */ jsxs5(Text7, { color: borderColor, children: [
2894
+ topBorder.left,
2895
+ topBorder.label ? /* @__PURE__ */ jsx6(Text7, { backgroundColor: borderColor, color: "black", bold: true, children: topBorder.label }) : null,
2896
+ topBorder.right
2897
+ ] }),
2898
+ /* @__PURE__ */ jsx6(Box5, { borderStyle: "single", borderTop: false, borderColor, paddingLeft: 1, children: isAborting ? /* @__PURE__ */ jsx6(Text7, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ jsxs5(Text7, { color: "cyan", children: [
2899
+ " ",
2900
+ "Queued: ",
2901
+ pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
2902
+ " ",
2903
+ /* @__PURE__ */ jsx6(Text7, { dimColor: true, children: "(Backspace to cancel)" })
2904
+ ] }) : isDisabled ? /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ jsxs5(Box5, { children: [
2905
+ /* @__PURE__ */ jsx6(Text7, { color: "green", bold: true, children: "> " }),
2906
+ /* @__PURE__ */ jsx6(
2907
+ CjkTextInput,
2908
+ {
2909
+ value,
2910
+ onChange: (v) => {
2911
+ setValue(v);
2912
+ setCursorHint(null);
2913
+ },
2914
+ onSubmit: handleSubmit,
2915
+ onPaste: handlePaste,
2916
+ placeholder: "Type a message or /help",
2917
+ availableWidth,
2918
+ cursorHint
2919
+ }
2920
+ )
2921
+ ] }) })
2922
+ ] });
2923
+ }
2924
+
2925
+ // src/ui/ConfirmPrompt.tsx
2926
+ import { useState as useState7, useRef as useRef5, useCallback as useCallback5 } from "react";
2927
+ import { Box as Box6, Text as Text8, useInput as useInput3 } from "ink";
2928
+
2929
+ // src/ui/flows/selection-flow.ts
2930
+ function createSelectionFlowState() {
2931
+ return { selectedIndex: 0, scrollOffset: 0, resolved: false };
2932
+ }
2933
+ function getVerticalSelectionInputAction(key) {
2934
+ if (key.escape === true) return "cancel";
2935
+ if (key.upArrow === true) return "previous";
2936
+ if (key.downArrow === true) return "next";
2937
+ if (key.return === true) return "select";
2938
+ return void 0;
2939
+ }
2940
+ function getDirectionalSelectionInputAction(key) {
2941
+ if (key.escape === true) return "cancel";
2942
+ if (key.leftArrow === true || key.upArrow === true) return "previous";
2943
+ if (key.rightArrow === true || key.downArrow === true) return "next";
2944
+ if (key.return === true) return "select";
2945
+ return void 0;
2946
+ }
2947
+ function applySelectionInput(state, action, options) {
2948
+ if (state.resolved) {
2949
+ return { state, effect: { type: "none" } };
2950
+ }
2951
+ if (action === "cancel") {
2952
+ return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
2953
+ }
2954
+ if (options.enabled === false || options.itemCount === 0) {
2955
+ return { state, effect: { type: "none" } };
2956
+ }
2957
+ if (action === "select") {
2958
+ const index = clampIndex(state.selectedIndex, options.itemCount);
2959
+ return {
2960
+ state: { ...state, selectedIndex: index, resolved: true },
2961
+ effect: { type: "select", index }
2962
+ };
2963
+ }
2964
+ const selectedIndex = moveSelection(state.selectedIndex, action, options);
2965
+ const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
2966
+ return { state: { ...state, selectedIndex, scrollOffset }, effect: { type: "none" } };
2967
+ }
2968
+ function normalizeSelectionState(state, options) {
2969
+ if (options.itemCount === 0) {
2970
+ return { ...state, selectedIndex: 0, scrollOffset: 0 };
2971
+ }
2972
+ const selectedIndex = clampIndex(state.selectedIndex, options.itemCount);
2973
+ const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
2974
+ if (selectedIndex === state.selectedIndex && scrollOffset === state.scrollOffset) {
2975
+ return state;
2976
+ }
2977
+ return {
2978
+ ...state,
2979
+ selectedIndex,
2980
+ scrollOffset
2981
+ };
2982
+ }
2983
+ function moveSelection(selectedIndex, action, options) {
2984
+ if (action === "previous") {
2985
+ if (options.wrap === true && selectedIndex === 0) return options.itemCount - 1;
2986
+ return Math.max(0, selectedIndex - 1);
2987
+ }
2988
+ if (options.wrap === true && selectedIndex === options.itemCount - 1) return 0;
2989
+ return Math.min(options.itemCount - 1, selectedIndex + 1);
2990
+ }
2991
+ function resolveScrollOffset(selectedIndex, scrollOffset, options) {
2992
+ const maxVisible = options.maxVisible ?? options.itemCount;
2993
+ if (maxVisible <= 0) return 0;
2994
+ if (selectedIndex < scrollOffset) return selectedIndex;
2995
+ if (selectedIndex >= scrollOffset + maxVisible) return selectedIndex - maxVisible + 1;
2996
+ return Math.max(0, scrollOffset);
2997
+ }
2998
+ function clampIndex(index, itemCount) {
2999
+ return Math.min(Math.max(index, 0), itemCount - 1);
3000
+ }
3001
+
3002
+ // src/ui/flows/confirm-prompt-flow.ts
3003
+ function getConfirmPromptInputAction(input, key, optionCount) {
3004
+ const action = getDirectionalSelectionInputAction({ ...key, escape: false });
3005
+ if (action !== void 0) {
3006
+ return action;
3007
+ }
3008
+ if (optionCount === 2 && input === "y") {
3009
+ return { type: "shortcut", index: 0 };
3010
+ }
3011
+ if (optionCount === 2 && input === "n") {
3012
+ return { type: "shortcut", index: 1 };
3013
+ }
3014
+ return void 0;
3015
+ }
3016
+ function applyConfirmPromptInput(state, action, optionCount) {
3017
+ if (state.resolved) {
3018
+ return { state, effect: { type: "none" } };
3019
+ }
3020
+ if (typeof action !== "string") {
3021
+ return {
3022
+ state: { ...state, selectedIndex: action.index, resolved: true },
3023
+ effect: { type: "select", index: action.index }
3024
+ };
3025
+ }
3026
+ return applySelectionInput(state, action, { itemCount: optionCount });
3027
+ }
3028
+
3029
+ // src/ui/ConfirmPrompt.tsx
3030
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3031
+ function ConfirmPrompt({
3032
+ message,
3033
+ options = ["Yes", "No"],
3034
+ onSelect
3035
+ }) {
3036
+ const [state, setState] = useState7(() => createSelectionFlowState());
3037
+ const stateRef = useRef5(state);
3038
+ const applyAction = useCallback5(
3039
+ (action) => {
3040
+ const result = applyConfirmPromptInput(stateRef.current, action, options.length);
3041
+ stateRef.current = result.state;
3042
+ setState(result.state);
3043
+ if (result.effect.type === "select") {
3044
+ onSelect(result.effect.index);
3045
+ }
3046
+ },
3047
+ [onSelect, options.length]
3048
+ );
3049
+ useInput3((input, key) => {
3050
+ const action = getConfirmPromptInputAction(input, key, options.length);
3051
+ if (action !== void 0) {
3052
+ applyAction(action);
3053
+ }
3054
+ });
3055
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3056
+ /* @__PURE__ */ jsx7(Text8, { color: "yellow", children: message }),
3057
+ /* @__PURE__ */ jsx7(Box6, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsx7(Box6, { marginRight: 2, children: /* @__PURE__ */ jsxs6(
3058
+ Text8,
3059
+ {
3060
+ color: i === state.selectedIndex ? "cyan" : void 0,
3061
+ bold: i === state.selectedIndex,
3062
+ children: [
3063
+ i === state.selectedIndex ? "> " : " ",
3064
+ opt
3065
+ ]
3066
+ }
3067
+ ) }, opt)) }),
3068
+ /* @__PURE__ */ jsx7(Text8, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
3069
+ ] });
3070
+ }
3071
+
3072
+ // src/ui/ProviderSetupPrompt.tsx
3073
+ import { useState as useState9 } from "react";
3074
+
3075
+ // src/ui/TextPrompt.tsx
3076
+ import { useState as useState8, useRef as useRef6, useCallback as useCallback6 } from "react";
3077
+ import { Box as Box7, Text as Text9, useInput as useInput4 } from "ink";
3078
+
3079
+ // src/ui/flows/text-prompt-flow.ts
3080
+ function createTextPromptFlowState() {
3081
+ return { value: "", resolved: false };
3082
+ }
3083
+ function getTextPromptInputAction(input, key) {
3084
+ if (key.escape === true) {
3085
+ return { type: "cancel" };
3086
+ }
3087
+ if (key.return === true) {
3088
+ return { type: "submit" };
3089
+ }
3090
+ if (key.backspace === true || key.delete === true) {
3091
+ return { type: "delete" };
3092
+ }
3093
+ if (input && key.ctrl !== true && key.meta !== true) {
3094
+ return { type: "insert", value: input };
3095
+ }
3096
+ return void 0;
3097
+ }
3098
+ function applyTextPromptInput(state, action, options) {
3099
+ if (state.resolved) {
3100
+ return { state, effect: { type: "none" } };
3101
+ }
3102
+ if (action.type === "cancel") {
3103
+ return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
3104
+ }
3105
+ if (action.type === "delete") {
3106
+ return {
3107
+ state: { ...state, value: state.value.slice(0, -1), error: void 0 },
3108
+ effect: { type: "none" }
3109
+ };
3110
+ }
3111
+ if (action.type === "insert") {
3112
+ return {
3113
+ state: { ...state, value: state.value + action.value, error: void 0 },
3114
+ effect: { type: "none" }
3115
+ };
3116
+ }
3117
+ return submitTextPromptValue(state, options);
3118
+ }
3119
+ function submitTextPromptValue(state, options) {
3120
+ const trimmed = state.value.trim();
3121
+ if (!trimmed && !options.allowEmpty) {
3122
+ const emptyError = options.validate?.(trimmed);
3123
+ return {
3124
+ state: emptyError ? { ...state, error: emptyError } : state,
3125
+ effect: { type: "none" }
3126
+ };
3127
+ }
3128
+ const error = options.validate?.(trimmed);
3129
+ if (error !== void 0) {
3130
+ return { state: { ...state, error }, effect: { type: "none" } };
3131
+ }
3132
+ return { state: { ...state, resolved: true }, effect: { type: "submit", value: trimmed } };
3133
+ }
3134
+
3135
+ // src/ui/TextPrompt.tsx
3136
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3137
+ function TextPrompt({
3138
+ title,
3139
+ placeholder,
3140
+ onSubmit,
3141
+ onCancel,
3142
+ validate,
3143
+ allowEmpty = false,
3144
+ masked = false
3145
+ }) {
3146
+ const [state, setState] = useState8(() => createTextPromptFlowState());
3147
+ const stateRef = useRef6(state);
3148
+ const applyAction = useCallback6(
3149
+ (action) => {
3150
+ const result = applyTextPromptInput(stateRef.current, action, { allowEmpty, validate });
3151
+ stateRef.current = result.state;
3152
+ setState(result.state);
3153
+ if (result.effect.type === "cancel") {
3154
+ onCancel();
3155
+ } else if (result.effect.type === "submit") {
3156
+ onSubmit(result.effect.value);
3157
+ }
3158
+ },
3159
+ [allowEmpty, validate, onCancel, onSubmit]
3160
+ );
3161
+ useInput4((input, key) => {
3162
+ const action = getTextPromptInputAction(input, key);
3163
+ if (action !== void 0) {
3164
+ applyAction(action);
3165
+ }
3166
+ });
3167
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3168
+ /* @__PURE__ */ jsx8(Text9, { color: "yellow", bold: true, children: title }),
3169
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
3170
+ /* @__PURE__ */ jsx8(Text9, { color: "cyan", children: "> " }),
3171
+ state.value ? /* @__PURE__ */ jsx8(Text9, { children: masked ? "*".repeat(state.value.length) : state.value }) : placeholder ? /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: placeholder }) : null,
3172
+ /* @__PURE__ */ jsx8(Text9, { color: "cyan", children: "\u2588" })
3173
+ ] }),
3174
+ state.error && /* @__PURE__ */ jsx8(Text9, { color: "red", children: state.error }),
3175
+ /* @__PURE__ */ jsx8(Text9, { dimColor: true, children: " Enter Submit Esc Cancel" })
3176
+ ] });
3177
+ }
3178
+
3179
+ // src/ui/ProviderSetupPrompt.tsx
3180
+ import { jsx as jsx9 } from "react/jsx-runtime";
3181
+ function ProviderSetupPrompt({
3182
+ type,
3183
+ providerDefinitions,
3184
+ onSubmit,
3185
+ onCancel
3186
+ }) {
3187
+ const [state, setState] = useState9(
3188
+ () => createProviderSetupFlow(type, providerDefinitions)
3189
+ );
3190
+ const step = getProviderSetupStep(state);
3191
+ const handleStepSubmit = (rawValue) => {
3192
+ const result = submitProviderSetupValue(state, rawValue);
3193
+ if (result.status === "next") {
3194
+ setState(result.state);
3195
+ return;
3196
+ }
3197
+ if (result.status === "complete") {
3198
+ onSubmit(result.input);
3199
+ }
3200
+ };
3201
+ return /* @__PURE__ */ jsx9(
3202
+ TextPrompt,
3203
+ {
3204
+ title: step.title,
3205
+ placeholder: step.defaultValue,
3206
+ allowEmpty: step.defaultValue !== void 0,
3207
+ masked: step.masked,
3208
+ validate: (value) => validateProviderSetupValue(step, value),
3209
+ onSubmit: handleStepSubmit,
3210
+ onCancel
3211
+ },
3212
+ `${type}-${step.key}`
3213
+ );
3214
+ }
3215
+
3216
+ // src/ui/PermissionPrompt.tsx
3217
+ import React9 from "react";
3218
+ import { Box as Box8, Text as Text10, useInput as useInput5 } from "ink";
3219
+
3220
+ // src/ui/flows/permission-prompt-flow.ts
3221
+ var PERMISSION_PROMPT_OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
3222
+ function getPermissionPromptInputAction(input, key) {
3223
+ const action = getDirectionalSelectionInputAction({ ...key, escape: false });
3224
+ if (action !== void 0) {
3225
+ return action;
3226
+ }
3227
+ if (input === "y" || input === "1") {
3228
+ return { type: "shortcut", index: 0 };
3229
+ }
3230
+ if (input === "a" || input === "2") {
3231
+ return { type: "shortcut", index: 1 };
3232
+ }
3233
+ if (input === "n" || input === "d" || input === "3") {
3234
+ return { type: "shortcut", index: 2 };
3235
+ }
3236
+ return void 0;
3237
+ }
3238
+ function applyPermissionPromptInput(state, action) {
3239
+ if (state.resolved) {
3240
+ return { state, effect: { type: "none" } };
3241
+ }
3242
+ if (typeof action !== "string") {
3243
+ return resolvePermissionIndex(state, action.index);
3244
+ }
3245
+ const result = applySelectionInput(state, action, {
3246
+ itemCount: PERMISSION_PROMPT_OPTIONS.length
3247
+ });
3248
+ if (result.effect.type !== "select") {
3249
+ return { state: result.state, effect: { type: "none" } };
3250
+ }
3251
+ return {
3252
+ state: result.state,
3253
+ effect: { type: "resolve", decision: getPermissionDecision(result.effect.index) }
3254
+ };
3255
+ }
3256
+ function getPermissionDecision(index) {
3257
+ if (index === 0) return true;
3258
+ if (index === 1) return "allow-session";
3259
+ return false;
3260
+ }
3261
+ function resolvePermissionIndex(state, index) {
3262
+ return {
3263
+ state: { ...state, selectedIndex: index, resolved: true },
3264
+ effect: { type: "resolve", decision: getPermissionDecision(index) }
3265
+ };
3266
+ }
3267
+
3268
+ // src/ui/PermissionPrompt.tsx
3269
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
3270
+ function formatArgs(args) {
3271
+ const entries = Object.entries(args);
3272
+ if (entries.length === 0) return "(no arguments)";
3273
+ return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
3274
+ }
3275
+ function PermissionPrompt({ request }) {
3276
+ const [state, setState] = React9.useState(() => createSelectionFlowState());
3277
+ const stateRef = React9.useRef(state);
3278
+ const prevRequestRef = React9.useRef(request);
3279
+ if (prevRequestRef.current !== request) {
3280
+ prevRequestRef.current = request;
3281
+ const nextState = createSelectionFlowState();
3282
+ stateRef.current = nextState;
3283
+ setState(nextState);
3284
+ }
3285
+ const applyAction = React9.useCallback(
3286
+ (action) => {
3287
+ const result = applyPermissionPromptInput(stateRef.current, action);
3288
+ stateRef.current = result.state;
3289
+ setState(result.state);
3290
+ if (result.effect.type === "resolve") {
3291
+ request.resolve(result.effect.decision);
3292
+ }
3293
+ },
3294
+ [request]
3295
+ );
3296
+ useInput5((input, key) => {
3297
+ const action = getPermissionPromptInputAction(input, key);
3298
+ if (action !== void 0) {
3299
+ applyAction(action);
3300
+ }
3301
+ });
3302
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3303
+ /* @__PURE__ */ jsx10(Text10, { color: "yellow", bold: true, children: "[Permission Required]" }),
3304
+ /* @__PURE__ */ jsxs8(Text10, { children: [
3305
+ "Tool:",
3306
+ " ",
3307
+ /* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: request.toolName })
3308
+ ] }),
3309
+ /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
3310
+ " ",
3311
+ formatArgs(request.toolArgs)
3312
+ ] }),
3313
+ /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: PERMISSION_PROMPT_OPTIONS.map((opt, i) => /* @__PURE__ */ jsx10(Box8, { marginRight: 2, children: /* @__PURE__ */ jsxs8(
3314
+ Text10,
3315
+ {
3316
+ color: i === state.selectedIndex ? "cyan" : void 0,
3317
+ bold: i === state.selectedIndex,
3318
+ children: [
3319
+ i === state.selectedIndex ? "> " : " ",
3320
+ opt
3321
+ ]
3322
+ }
3323
+ ) }, opt)) }),
3324
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " left/right to select, Enter to confirm" })
3325
+ ] });
3326
+ }
3327
+
3328
+ // src/ui/StreamingIndicator.tsx
3329
+ import { Box as Box9, Text as Text11 } from "ink";
3330
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3331
+ function getToolStyle(t) {
3332
+ if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
3333
+ if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
3334
+ if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
3335
+ return { color: "green", icon: "\u2713", strikethrough: false };
3336
+ }
3337
+ function StreamingIndicator({ text, activeTools }) {
3338
+ const hasTools = activeTools.length > 0;
3339
+ const hasText = text.length > 0;
3340
+ if (!hasTools && !hasText) {
3341
+ return /* @__PURE__ */ jsx11(Fragment3, {});
3342
+ }
3343
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3344
+ hasTools && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3345
+ /* @__PURE__ */ jsx11(Text11, { color: "white", bold: true, children: "Tools:" }),
3346
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3347
+ activeTools.map((t, i) => {
3348
+ const { color, icon, strikethrough } = getToolStyle(t);
3349
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3350
+ /* @__PURE__ */ jsxs9(Text11, { color, strikethrough, children: [
3351
+ " ",
3352
+ icon,
3353
+ " ",
3354
+ t.toolName,
3355
+ "(",
3356
+ t.firstArg,
3357
+ ")"
3358
+ ] }),
3359
+ t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ jsx11(DiffBlock, { file: t.diffFile, lines: t.diffLines })
3360
+ ] }, `${t.toolName}-${i}`);
3361
+ })
3362
+ ] }),
3363
+ hasText && /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3364
+ /* @__PURE__ */ jsx11(Text11, { color: "cyan", bold: true, children: "Robota:" }),
3365
+ /* @__PURE__ */ jsx11(Text11, { children: " " }),
3366
+ /* @__PURE__ */ jsx11(Box9, { marginLeft: 2, children: /* @__PURE__ */ jsx11(Text11, { wrap: "wrap", children: renderMarkdown(text) }) })
3367
+ ] })
3368
+ ] });
3369
+ }
3370
+
3371
+ // src/ui/PluginTUI.tsx
3372
+ import { useState as useState12, useCallback as useCallback8 } from "react";
3373
+
3374
+ // src/ui/MenuSelect.tsx
3375
+ import { useState as useState10, useCallback as useCallback7, useRef as useRef7 } from "react";
3376
+ import { Box as Box10, Text as Text12, useInput as useInput6 } from "ink";
3377
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3378
+ function MenuSelect({
3379
+ title,
3380
+ items,
3381
+ onSelect,
3382
+ onBack,
3383
+ loading,
3384
+ error
3385
+ }) {
3386
+ const [state, setState] = useState10(() => createSelectionFlowState());
3387
+ const stateRef = useRef7(state);
3388
+ const isEnabled = !loading && !error;
3389
+ const applyAction = useCallback7(
3390
+ (action) => {
3391
+ const result = applySelectionInput(stateRef.current, action, {
3392
+ itemCount: items.length,
3393
+ enabled: isEnabled
3394
+ });
3395
+ stateRef.current = result.state;
3396
+ setState(result.state);
3397
+ if (result.effect.type === "cancel") {
3398
+ onBack();
3399
+ } else if (result.effect.type === "select") {
3400
+ const item = items[result.effect.index];
3401
+ if (item !== void 0) {
3402
+ onSelect(item.value);
3403
+ }
3404
+ }
3405
+ },
3406
+ [isEnabled, items, onBack, onSelect]
3407
+ );
3408
+ useInput6((input, key) => {
3409
+ const action = getVerticalSelectionInputAction(key);
3410
+ if (action !== void 0) {
3411
+ applyAction(action);
3412
+ }
3413
+ });
3414
+ const normalizedState = normalizeSelectionState(state, { itemCount: items.length });
3415
+ if (normalizedState !== state) {
3416
+ stateRef.current = normalizedState;
3417
+ }
3418
+ const selected = normalizedState.selectedIndex;
3419
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3420
+ /* @__PURE__ */ jsx12(Text12, { color: "yellow", bold: true, children: title }),
3421
+ loading && /* @__PURE__ */ jsx12(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Loading..." }) }),
3422
+ error && /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
3423
+ /* @__PURE__ */ jsx12(Text12, { color: "red", children: error }),
3424
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: "Press Esc to go back" })
3425
+ ] }),
3426
+ !loading && !error && /* @__PURE__ */ jsx12(Box10, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ jsxs10(Box10, { children: [
3427
+ /* @__PURE__ */ jsxs10(Text12, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
3428
+ i === selected ? "> " : " ",
3429
+ item.label
3430
+ ] }),
3431
+ item.hint && /* @__PURE__ */ jsxs10(Text12, { dimColor: true, children: [
3432
+ " ",
3433
+ item.hint
3434
+ ] })
3435
+ ] }, item.value)) }),
3436
+ /* @__PURE__ */ jsx12(Text12, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
3437
+ ] });
3438
+ }
3439
+
3440
+ // src/ui/plugin-tui-handlers.ts
3441
+ function handleMainSelect(value, nav) {
3442
+ if (value === "marketplace") {
3443
+ nav.push({ screen: "marketplace-list" });
3444
+ } else if (value === "installed") {
3445
+ nav.push({ screen: "installed-list" });
3446
+ }
3447
+ }
3448
+ function handleMarketplaceListSelect(value, nav) {
3449
+ if (value === "__add__") {
3450
+ nav.push({ screen: "marketplace-add" });
3451
+ } else {
3452
+ nav.push({ screen: "marketplace-action", context: { marketplace: value } });
3453
+ }
3454
+ }
3455
+ function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
3456
+ if (value === "browse") {
3457
+ nav.push({ screen: "marketplace-browse", context: { marketplace } });
3458
+ } else if (value === "update") {
3459
+ callbacks.marketplaceUpdate(marketplace).then(() => {
3460
+ nav.notify(`Updated marketplace "${marketplace}".`);
3461
+ nav.pop();
3462
+ }).catch((err) => {
3463
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3464
+ });
3465
+ } else if (value === "remove") {
3466
+ nav.setConfirm({
3467
+ message: `Remove marketplace "${marketplace}" and all its plugins?`,
3468
+ onConfirm: () => {
3469
+ nav.setConfirm(void 0);
3470
+ callbacks.marketplaceRemove(marketplace).then(() => {
3471
+ nav.notify(`Removed marketplace "${marketplace}".`);
3472
+ nav.popN(2);
3473
+ }).catch((err) => {
3474
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3475
+ });
3476
+ },
3477
+ onCancel: () => nav.setConfirm(void 0)
3478
+ });
3479
+ }
3480
+ }
3481
+ function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
3482
+ const fullId = `${value}@${marketplace}`;
3483
+ const item = items.find((i) => i.value === value);
3484
+ if (item?.hint === "installed") {
3485
+ nav.push({ screen: "installed-action", context: { pluginId: fullId } });
3486
+ } else {
3487
+ nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
3488
+ }
3489
+ }
3490
+ function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
3491
+ const scope = value;
3492
+ callbacks.install(pluginId, scope).then(() => {
3493
+ nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
3494
+ nav.popN(2);
3495
+ }).catch((err) => {
3496
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3497
+ });
3498
+ }
3499
+ function handleInstalledListSelect(value, callbacks, nav) {
3500
+ nav.setConfirm({
3501
+ message: `Uninstall plugin "${value}"?`,
3502
+ onConfirm: () => {
3503
+ nav.setConfirm(void 0);
3504
+ callbacks.uninstall(value).then(() => {
3505
+ nav.notify(`Uninstalled plugin "${value}".`);
3506
+ nav.refresh();
3507
+ }).catch((err) => {
3508
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3509
+ });
3510
+ },
3511
+ onCancel: () => nav.setConfirm(void 0)
3512
+ });
3513
+ }
3514
+ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
3515
+ if (value === "uninstall") {
3516
+ nav.setConfirm({
3517
+ message: `Uninstall plugin "${pluginId}"?`,
3518
+ onConfirm: () => {
3519
+ nav.setConfirm(void 0);
3520
+ callbacks.uninstall(pluginId).then(() => {
3521
+ nav.notify(`Uninstalled plugin "${pluginId}".`);
3522
+ nav.popN(2);
3523
+ }).catch((err) => {
3524
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3525
+ });
3526
+ },
3527
+ onCancel: () => nav.setConfirm(void 0)
3528
+ });
3529
+ }
3530
+ }
3531
+
3532
+ // src/ui/hooks/usePluginScreenData.ts
3533
+ import { useState as useState11, useEffect as useEffect3 } from "react";
3534
+ function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, stackLength) {
3535
+ const [items, setItems] = useState11([]);
3536
+ const [loading, setLoading] = useState11(false);
3537
+ const [error, setError] = useState11();
3538
+ useEffect3(() => {
3539
+ setItems([]);
3540
+ setError(void 0);
3541
+ if (screen === "marketplace-list") {
3542
+ setLoading(true);
3543
+ callbacks.marketplaceList().then((sources) => {
3544
+ const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
3545
+ const sourceItems = sources.map((s) => ({
3546
+ label: s.name,
3547
+ value: s.name,
3548
+ hint: s.type
3549
+ }));
3550
+ setItems([...baseItems, ...sourceItems]);
3551
+ setLoading(false);
3552
+ }).catch((err) => {
3553
+ setError(err instanceof Error ? err.message : String(err));
3554
+ setLoading(false);
3555
+ });
3556
+ } else if (screen === "marketplace-browse") {
3557
+ const mp = marketplace ?? "";
3558
+ setLoading(true);
3559
+ callbacks.listAvailablePlugins(mp).then((plugins) => {
3560
+ setItems(
3561
+ plugins.map((p) => ({
3562
+ label: p.name,
3563
+ value: p.name,
3564
+ hint: p.installed ? "installed" : p.description
3565
+ }))
3566
+ );
3567
+ setLoading(false);
3568
+ }).catch((err) => {
3569
+ setError(err instanceof Error ? err.message : String(err));
3570
+ setLoading(false);
3571
+ });
3572
+ } else if (screen === "installed-list") {
3573
+ setLoading(true);
3574
+ callbacks.listInstalled().then((plugins) => {
3575
+ setItems(
3576
+ plugins.map((p) => ({
3577
+ label: p.name,
3578
+ value: p.name,
3579
+ hint: p.description
3580
+ }))
3581
+ );
3582
+ setLoading(false);
3583
+ }).catch((err) => {
3584
+ setError(err instanceof Error ? err.message : String(err));
3585
+ setLoading(false);
3586
+ });
3587
+ }
3588
+ }, [stackLength, screen, marketplace, callbacks, refreshCounter]);
3589
+ return { items, loading, error };
3590
+ }
3591
+
3592
+ // src/ui/PluginTUI.tsx
3593
+ import { jsx as jsx13 } from "react/jsx-runtime";
3594
+ function PluginTUI({ callbacks, onClose, addMessage }) {
3595
+ const [stack, setStack] = useState12([{ screen: "main" }]);
3596
+ const [confirm, setConfirm] = useState12();
3597
+ const [refreshCounter, setRefreshCounter] = useState12(0);
3598
+ const current = stack[stack.length - 1] ?? { screen: "main" };
3599
+ const push = useCallback8((state) => {
3600
+ setStack((prev) => [...prev, state]);
3601
+ }, []);
3602
+ const pop = useCallback8(() => {
3603
+ setStack((prev) => {
3604
+ if (prev.length <= 1) {
3605
+ onClose();
3606
+ return prev;
3607
+ }
3608
+ return prev.slice(0, -1);
3609
+ });
3610
+ }, [onClose]);
3611
+ const popN = useCallback8(
3612
+ (n) => {
3613
+ setStack((prev) => {
3614
+ const next = prev.slice(0, Math.max(1, prev.length - n));
3615
+ if (next.length === 0) {
3616
+ onClose();
3617
+ return prev;
3618
+ }
3619
+ return next;
3620
+ });
3621
+ },
3622
+ [onClose]
3623
+ );
3624
+ const notify = useCallback8(
3625
+ (content) => {
3626
+ addMessage?.({ role: "system", content });
3627
+ },
3628
+ [addMessage]
3629
+ );
3630
+ const refresh = useCallback8(() => {
3631
+ setRefreshCounter((c) => c + 1);
3632
+ }, []);
3633
+ const setConfirmNav = useCallback8(
3634
+ (state) => setConfirm(state),
3635
+ [setConfirm]
3636
+ );
3637
+ const pushNav = useCallback8(
3638
+ (state) => push({ screen: state.screen, context: state.context }),
3639
+ [push]
3640
+ );
3641
+ const nav = { push: pushNav, pop, popN, notify, setConfirm: setConfirmNav, refresh };
3642
+ const { items, loading, error } = usePluginScreenData(
3643
+ current.screen,
3644
+ current.context?.marketplace,
3645
+ callbacks,
3646
+ refreshCounter,
3647
+ stack.length
3648
+ );
3649
+ const handleSelect = useCallback8(
3650
+ (value) => {
3651
+ const screen2 = current.screen;
3652
+ const ctx = current.context;
3653
+ if (screen2 === "main") handleMainSelect(value, nav);
3654
+ else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
3655
+ else if (screen2 === "marketplace-action")
3656
+ handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
3657
+ else if (screen2 === "marketplace-browse")
3658
+ handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
3659
+ else if (screen2 === "marketplace-install-scope")
3660
+ handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
3661
+ else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
3662
+ else if (screen2 === "installed-action")
3663
+ handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
3664
+ },
3665
+ [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
3666
+ );
3667
+ const handleTextSubmit = useCallback8(
3668
+ (value) => {
3669
+ if (current.screen === "marketplace-add") {
3670
+ callbacks.marketplaceAdd(value).then((name) => {
3671
+ notify(`Added marketplace "${name}" from ${value}.`);
3672
+ pop();
3673
+ }).catch((err) => {
3674
+ notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
3675
+ pop();
3676
+ });
3677
+ }
3678
+ },
3679
+ [current.screen, callbacks, notify, pop]
3680
+ );
3681
+ if (confirm) {
3682
+ return /* @__PURE__ */ jsx13(
3683
+ ConfirmPrompt,
3684
+ {
3685
+ message: confirm.message,
3686
+ onSelect: (index) => {
3687
+ if (index === 0) confirm.onConfirm();
3688
+ else confirm.onCancel();
3689
+ }
3690
+ }
3691
+ );
3692
+ }
3693
+ const screen = current.screen;
3694
+ if (screen === "marketplace-add") {
3695
+ return /* @__PURE__ */ jsx13(
3696
+ TextPrompt,
3697
+ {
3698
+ title: "Add Marketplace Source",
3699
+ placeholder: "owner/repo or git URL",
3700
+ onSubmit: handleTextSubmit,
3701
+ onCancel: pop,
3702
+ validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
3703
+ }
3704
+ );
3705
+ }
3706
+ if (screen === "marketplace-action") {
3707
+ return /* @__PURE__ */ jsx13(
3708
+ MenuSelect,
3709
+ {
3710
+ title: `Marketplace: ${current.context?.marketplace ?? ""}`,
3711
+ items: [
3712
+ { label: "Browse plugins", value: "browse" },
3713
+ { label: "Update", value: "update" },
3714
+ { label: "Remove", value: "remove" }
3715
+ ],
3716
+ onSelect: handleSelect,
3717
+ onBack: pop
3718
+ },
3719
+ stack.length
3720
+ );
3721
+ }
3722
+ if (screen === "marketplace-install-scope") {
3723
+ return /* @__PURE__ */ jsx13(
3724
+ MenuSelect,
3725
+ {
3726
+ title: `Install scope for "${current.context?.pluginId ?? ""}"`,
3727
+ items: [
3728
+ { label: "User scope", value: "user" },
3729
+ { label: "Project scope", value: "project" }
3730
+ ],
3731
+ onSelect: handleSelect,
3732
+ onBack: pop
3733
+ },
3734
+ stack.length
3735
+ );
3736
+ }
3737
+ if (screen === "installed-action") {
3738
+ return /* @__PURE__ */ jsx13(
3739
+ MenuSelect,
3740
+ {
3741
+ title: `Plugin: ${current.context?.pluginId ?? ""}`,
3742
+ items: [{ label: "Uninstall", value: "uninstall" }],
3743
+ onSelect: handleSelect,
3744
+ onBack: pop
3745
+ },
3746
+ stack.length
3747
+ );
3748
+ }
3749
+ const titleMap = {
3750
+ main: "Plugin Management",
3751
+ "marketplace-list": "Marketplace",
3752
+ "marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
3753
+ "installed-list": "Installed Plugins"
3754
+ };
3755
+ const staticItemsMap = {
3756
+ main: [
3757
+ { label: "Marketplace", value: "marketplace" },
3758
+ { label: "Installed Plugins", value: "installed" }
3759
+ ]
3760
+ };
3761
+ return /* @__PURE__ */ jsx13(
3762
+ MenuSelect,
3763
+ {
3764
+ title: titleMap[screen] ?? "Plugin Management",
3765
+ items: staticItemsMap[screen] ?? items,
3766
+ onSelect: handleSelect,
3767
+ onBack: pop,
3768
+ loading,
3769
+ error
3770
+ },
3771
+ `${screen}-${stack.length}-${refreshCounter}`
3772
+ );
3773
+ }
3774
+
3775
+ // src/ui/SessionPicker.tsx
3776
+ import { Box as Box12, Text as Text14 } from "ink";
3777
+
3778
+ // src/ui/ListPicker.tsx
3779
+ import { useState as useState13, useRef as useRef8, useCallback as useCallback9 } from "react";
3780
+ import { Box as Box11, Text as Text13, useInput as useInput7 } from "ink";
3781
+ import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
3782
+ var DEFAULT_MAX_VISIBLE = 3;
3783
+ function ListPicker({
3784
+ items,
3785
+ renderItem,
3786
+ onSelect,
3787
+ onCancel,
3788
+ maxVisible = DEFAULT_MAX_VISIBLE
3789
+ }) {
3790
+ const [state, setState] = useState13(() => createSelectionFlowState());
3791
+ const stateRef = useRef8(state);
3792
+ const applyAction = useCallback9(
3793
+ (action) => {
3794
+ const result = applySelectionInput(stateRef.current, action, {
3795
+ itemCount: items.length,
3796
+ maxVisible
3797
+ });
3798
+ stateRef.current = result.state;
3799
+ setState(result.state);
3800
+ if (result.effect.type === "cancel") {
3801
+ onCancel();
3802
+ } else if (result.effect.type === "select") {
3803
+ const item = items[result.effect.index];
3804
+ if (item !== void 0) {
3805
+ onSelect(item);
3806
+ }
3807
+ }
3808
+ },
3809
+ [items, maxVisible, onCancel, onSelect]
3810
+ );
3811
+ useInput7((_input, key) => {
3812
+ const action = getVerticalSelectionInputAction(key);
3813
+ if (action !== void 0) {
3814
+ applyAction(action);
3815
+ }
3816
+ });
3817
+ if (items.length === 0) {
3818
+ return /* @__PURE__ */ jsx14(Box11, {});
3819
+ }
3820
+ const normalizedState = normalizeSelectionState(state, { itemCount: items.length, maxVisible });
3821
+ if (normalizedState !== state) {
3822
+ stateRef.current = normalizedState;
3823
+ }
3824
+ const { selectedIndex, scrollOffset } = normalizedState;
3825
+ const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
3826
+ const hasMore = scrollOffset + maxVisible < items.length;
3827
+ const hasLess = scrollOffset > 0;
3828
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
3829
+ hasLess && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
3830
+ " \u2191 ",
3831
+ scrollOffset,
3832
+ " more above"
3833
+ ] }),
3834
+ visibleItems.map((item, index) => /* @__PURE__ */ jsx14(Box11, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
3835
+ hasMore && /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
3836
+ " \u2193 ",
3837
+ items.length - scrollOffset - maxVisible,
3838
+ " more below"
3839
+ ] })
3840
+ ] });
3841
+ }
3842
+
3843
+ // src/ui/SessionPicker.tsx
3844
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
3845
+ var SESSION_ID_DISPLAY_LENGTH = 8;
3846
+ function SessionPicker({
3847
+ sessionStore,
3848
+ cwd,
3849
+ onSelect,
3850
+ onCancel
3851
+ }) {
3852
+ const sessions = (sessionStore?.list() ?? []).filter((s) => s.cwd === cwd);
3853
+ return /* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
3854
+ /* @__PURE__ */ jsx15(Text14, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
3855
+ /* @__PURE__ */ jsx15(
3856
+ ListPicker,
3857
+ {
3858
+ items: sessions,
3859
+ renderItem: (session, isSelected) => {
3860
+ const lastMsg = session.messages.slice().reverse().find((m) => {
3861
+ const msg = m;
3862
+ return msg.role === "assistant" && msg.content;
3863
+ });
3864
+ const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
3865
+ const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
3866
+ return /* @__PURE__ */ jsxs12(Text14, { children: [
3867
+ isSelected ? "> " : " ",
3868
+ /* @__PURE__ */ jsx15(Text14, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
3869
+ " ",
3870
+ /* @__PURE__ */ jsx15(Text14, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
3871
+ month: "short",
3872
+ day: "numeric",
3873
+ hour: "2-digit",
3874
+ minute: "2-digit"
3875
+ }) }),
3876
+ " ",
3877
+ /* @__PURE__ */ jsxs12(Text14, { dimColor: true, children: [
3878
+ "msgs: ",
3879
+ session.messages.length
3880
+ ] }),
3881
+ preview ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
3882
+ "\n ",
3883
+ /* @__PURE__ */ jsx15(Text14, { color: "gray", children: preview })
3884
+ ] }) : null
3885
+ ] });
3886
+ },
3887
+ onSelect: (session) => onSelect(session.id),
3888
+ onCancel
3889
+ }
3890
+ )
3891
+ ] });
3892
+ }
3893
+
3894
+ // src/ui/BackgroundTaskPanel.tsx
3895
+ import { Box as Box13, Text as Text15 } from "ink";
3896
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
3897
+ var MS_PER_SECOND = 1e3;
3898
+ var SECONDS_PER_MINUTE = 60;
3899
+ var MINUTES_PER_HOUR = 60;
3900
+ function getStatusColor(status) {
3901
+ if (status === "completed") return "green";
3902
+ if (status === "failed") return "red";
3903
+ if (status === "cancelled") return "yellow";
3904
+ return "cyan";
3905
+ }
3906
+ function getStatusMarker(status) {
3907
+ if (status === "queued" || status === "running") return "\u25A1";
3908
+ return "\u25A0";
3909
+ }
3910
+ function getTaskPreview(task) {
3911
+ return task.errorPreview ?? task.resultPreview ?? task.currentAction ?? task.preview;
3912
+ }
3913
+ function formatAge(iso) {
3914
+ if (!iso) return void 0;
3915
+ const timestamp = Date.parse(iso);
3916
+ if (Number.isNaN(timestamp)) return void 0;
3917
+ const seconds = Math.max(0, Math.floor((Date.now() - timestamp) / MS_PER_SECOND));
3918
+ if (seconds < SECONDS_PER_MINUTE) return `${seconds}s`;
3919
+ const minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
3920
+ if (minutes < MINUTES_PER_HOUR) return `${minutes}m`;
3921
+ return `${Math.floor(minutes / MINUTES_PER_HOUR)}h`;
3922
+ }
3923
+ function BackgroundTaskPanel({ tasks }) {
3924
+ if (tasks.length === 0) return null;
3925
+ return /* @__PURE__ */ jsxs13(Box13, { flexDirection: "column", marginBottom: 1, children: [
3926
+ /* @__PURE__ */ jsx16(Text15, { color: "cyan", bold: true, children: "Background" }),
3927
+ tasks.map((task) => /* @__PURE__ */ jsxs13(Text15, { children: [
3928
+ "- ",
3929
+ /* @__PURE__ */ jsx16(Text15, { color: getStatusColor(task.status), children: getStatusMarker(task.status) }),
3930
+ ` ${task.kind}:${task.label} ${task.id}`,
3931
+ task.status === "running" && formatAge(task.lastActivityAt) ? /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: ` idle ${formatAge(task.lastActivityAt)}` }) : null,
3932
+ task.timeoutReason ? /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: ` (${task.timeoutReason})` }) : null,
3933
+ getTaskPreview(task) ? /* @__PURE__ */ jsx16(Text15, { dimColor: true, children: ` - ${getTaskPreview(task)}` }) : null
3934
+ ] }, task.id))
3935
+ ] });
3936
+ }
3937
+
3938
+ // src/ui/App.tsx
3939
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
3940
+ function App(props) {
3941
+ const [activeSessionId, setActiveSessionId] = useState14(props.resumeSessionId);
3942
+ return /* @__PURE__ */ jsx17(
3943
+ AppInner,
3944
+ {
3945
+ ...props,
3946
+ resumeSessionId: activeSessionId,
3947
+ onSessionSwitch: (sessionId) => setActiveSessionId(sessionId)
3948
+ },
3949
+ activeSessionId ?? "__new__"
3950
+ );
3951
+ }
3952
+ function AppInner(props) {
3953
+ const cwd = props.cwd;
3954
+ const providerDefinitions = props.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
3955
+ const {
3956
+ interactiveSession,
3957
+ registry,
3958
+ history,
3959
+ addEntry,
3960
+ streamingText,
3961
+ activeTools,
3962
+ isThinking,
3963
+ isAborting,
3964
+ isShuttingDown,
3965
+ pendingPrompt,
3966
+ backgroundTasks,
3967
+ permissionRequest,
3968
+ contextState,
3969
+ handleSubmit: baseHandleSubmit,
3970
+ handleAbort,
3971
+ handleCancelQueue,
3972
+ handleShutdown
3973
+ } = useInteractiveSession({
3974
+ cwd,
3975
+ provider: props.provider,
3976
+ permissionMode: props.permissionMode,
3977
+ maxTurns: props.maxTurns,
3978
+ sessionStore: props.sessionStore,
3979
+ resumeSessionId: props.resumeSessionId,
3980
+ forkSession: props.forkSession,
3981
+ sessionName: props.sessionName,
3982
+ backgroundTaskRunners: props.backgroundTaskRunners,
3983
+ subagentRunnerFactory: props.subagentRunnerFactory,
3984
+ commandModules: props.commandModules,
3985
+ providerDefinitions
3986
+ });
3987
+ const pluginCallbacks = usePluginCallbacks(cwd);
3988
+ const { exit } = useApp2();
3989
+ const [sessionName, setSessionName] = useState14(props.sessionName);
3990
+ const {
3991
+ handleSubmit,
3992
+ pendingModelId,
3993
+ pendingProviderProfile,
3994
+ pendingProviderSetupType,
3995
+ showPluginTUI,
3996
+ showSessionPicker,
3997
+ setShowPluginTUI,
3998
+ setShowSessionPicker,
3999
+ handleModelConfirm,
4000
+ handleProviderConfirm,
4001
+ handleProviderSetupSubmit,
4002
+ handleProviderSetupCancel
4003
+ } = useSideEffects({
4004
+ cwd,
4005
+ interactiveSession,
4006
+ addEntry,
4007
+ baseHandleSubmit,
4008
+ setSessionName,
4009
+ providerDefinitions
4010
+ });
4011
+ useEffect4(() => {
4012
+ const name = interactiveSession?.getName?.();
4013
+ if (name && !sessionName) setSessionName(name);
4014
+ }, [interactiveSession, sessionName]);
4015
+ useEffect4(() => {
4016
+ const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
4017
+ process.stdout.write(`\x1B]0;${title}\x07`);
4018
+ }, [sessionName]);
4019
+ useInput8((_input, key) => {
4020
+ if (!key.escape || !isThinking) return;
4021
+ if (permissionRequest || showPluginTUI || showSessionPicker) return;
4022
+ handleAbort();
4023
+ });
4024
+ useInput8((input, key) => {
4025
+ if (!key.ctrl || input !== "c" || isShuttingDown) return;
4026
+ void handleShutdown("prompt_input_exit").finally(() => exit());
4027
+ });
4028
+ useEffect4(() => {
4029
+ const onSigterm = () => {
4030
+ if (isShuttingDown) return;
4031
+ void handleShutdown("other").finally(() => exit());
4032
+ };
4033
+ process.once("SIGINT", onSigterm);
4034
+ process.once("SIGTERM", onSigterm);
4035
+ return () => {
4036
+ process.off("SIGINT", onSigterm);
4037
+ process.off("SIGTERM", onSigterm);
4038
+ };
4039
+ }, [handleShutdown, exit, isShuttingDown]);
4040
+ let permissionMode = props.permissionMode ?? "default";
4041
+ let sessionId = "";
4042
+ try {
4043
+ const session = interactiveSession.getSession();
4044
+ permissionMode = session.getPermissionMode();
4045
+ sessionId = session.getSessionId();
4046
+ } catch {
4047
+ }
4048
+ return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", children: [
4049
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
4050
+ /* @__PURE__ */ jsx17(Text16, { color: "cyan", bold: true, children: `
4051
+ ____ ___ ____ ___ _____ _
4052
+ | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
4053
+ | |_) | | | | _ \\| | | || | / _ \\
4054
+ | _ <| |_| | |_) | |_| || |/ ___ \\
4055
+ |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
4056
+ ` }),
4057
+ /* @__PURE__ */ jsxs14(Text16, { dimColor: true, children: [
4058
+ " v",
4059
+ props.version ?? "0.0.0"
4060
+ ] })
4061
+ ] }),
4062
+ /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
4063
+ /* @__PURE__ */ jsx17(MessageList, { history }),
4064
+ isShuttingDown && /* @__PURE__ */ jsx17(Box14, { marginBottom: 1, children: /* @__PURE__ */ jsx17(Text16, { color: "yellow", children: "Shutting down..." }) }),
4065
+ (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx17(Box14, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx17(StreamingIndicator, { text: streamingText, activeTools }) }),
4066
+ /* @__PURE__ */ jsx17(BackgroundTaskPanel, { tasks: backgroundTasks })
4067
+ ] }),
4068
+ permissionRequest && /* @__PURE__ */ jsx17(PermissionPrompt, { request: permissionRequest }),
4069
+ pendingModelId && /* @__PURE__ */ jsx17(
4070
+ ConfirmPrompt,
4071
+ {
4072
+ message: `Change model to ${getModelName2(pendingModelId)}? This will restart the session.`,
4073
+ onSelect: handleModelConfirm
4074
+ }
4075
+ ),
4076
+ pendingProviderProfile && /* @__PURE__ */ jsx17(
4077
+ ConfirmPrompt,
4078
+ {
4079
+ message: `Change provider to ${pendingProviderProfile}? This will restart the session.`,
4080
+ onSelect: handleProviderConfirm
4081
+ }
4082
+ ),
4083
+ pendingProviderSetupType && /* @__PURE__ */ jsx17(
4084
+ ProviderSetupPrompt,
4085
+ {
4086
+ type: pendingProviderSetupType,
4087
+ providerDefinitions,
4088
+ onSubmit: handleProviderSetupSubmit,
4089
+ onCancel: handleProviderSetupCancel
4090
+ }
4091
+ ),
4092
+ showPluginTUI && /* @__PURE__ */ jsx17(
4093
+ PluginTUI,
4094
+ {
4095
+ callbacks: pluginCallbacks,
4096
+ onClose: () => setShowPluginTUI(false),
4097
+ addMessage: (msg) => addEntry(messageToHistoryEntry4(createSystemMessage4(msg.content)))
4098
+ }
4099
+ ),
4100
+ showSessionPicker && /* @__PURE__ */ jsx17(
4101
+ SessionPicker,
4102
+ {
4103
+ sessionStore: props.sessionStore,
4104
+ cwd: props.cwd,
4105
+ onSelect: (id) => {
4106
+ setShowSessionPicker(false);
4107
+ props.onSessionSwitch(id);
4108
+ },
4109
+ onCancel: () => {
4110
+ setShowSessionPicker(false);
4111
+ addEntry(messageToHistoryEntry4(createSystemMessage4("Session resume cancelled.")));
4112
+ }
4113
+ }
4114
+ ),
4115
+ /* @__PURE__ */ jsx17(
4116
+ StatusBar,
4117
+ {
4118
+ permissionMode,
4119
+ modelName: props.modelId ? getModelName2(props.modelId) : "",
4120
+ sessionId,
4121
+ messageCount: history.length,
4122
+ isThinking,
4123
+ contextPercentage: contextState.percentage,
4124
+ contextUsedTokens: contextState.usedTokens,
4125
+ contextMaxTokens: contextState.maxTokens,
4126
+ sessionName
4127
+ }
4128
+ ),
4129
+ /* @__PURE__ */ jsx17(
4130
+ InputArea,
4131
+ {
4132
+ onSubmit: handleSubmit,
4133
+ onCancelQueue: handleCancelQueue,
4134
+ isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown || !!pendingProviderSetupType || isThinking && !!pendingPrompt,
4135
+ isAborting,
4136
+ pendingPrompt,
4137
+ registry,
4138
+ sessionName
4139
+ }
4140
+ ),
4141
+ /* @__PURE__ */ jsx17(Text16, { children: " " })
4142
+ ] });
4143
+ }
4144
+
4145
+ // src/ui/render.tsx
4146
+ import { jsx as jsx18 } from "react/jsx-runtime";
4147
+ function renderApp(options) {
4148
+ process.on("unhandledRejection", (reason) => {
4149
+ process.stderr.write(`
4150
+ [UNHANDLED REJECTION] ${reason}
4151
+ `);
4152
+ if (reason instanceof Error) {
4153
+ process.stderr.write(`${reason.stack}
4154
+ `);
4155
+ }
4156
+ });
4157
+ if (process.stdin.isTTY && process.stdout.isTTY) {
4158
+ process.stdout.write("\x1B[?2004h");
4159
+ }
4160
+ const instance = render(/* @__PURE__ */ jsx18(App, { ...options }), {
4161
+ exitOnCtrlC: false
4162
+ });
4163
+ instance.waitUntilExit().then(() => {
4164
+ if (process.stdout.isTTY) {
4165
+ process.stdout.write("\x1B[?2004l");
4166
+ }
4167
+ process.exit(0);
4168
+ }).catch((err) => {
4169
+ if (err) {
4170
+ process.stderr.write(`
4171
+ [EXIT ERROR] ${err}
4172
+ `);
4173
+ }
4174
+ process.exit(1);
4175
+ });
4176
+ }
4177
+
4178
+ // src/cli.ts
4179
+ function readVersion() {
4180
+ try {
4181
+ const thisFile = fileURLToPath(import.meta.url);
4182
+ const dir = dirname3(thisFile);
4183
+ const candidates = [join7(dir, "..", "..", "package.json"), join7(dir, "..", "package.json")];
4184
+ for (const pkgPath of candidates) {
4185
+ try {
4186
+ const raw = readFileSync4(pkgPath, "utf-8");
4187
+ const pkg = JSON.parse(raw);
4188
+ if (pkg.version !== void 0 && pkg.name !== void 0) {
4189
+ return pkg.version;
4190
+ }
4191
+ } catch {
4192
+ }
4193
+ }
4194
+ return "0.0.0";
4195
+ } catch {
4196
+ return "0.0.0";
4197
+ }
4198
+ }
4199
+ function promptInput(label, masked = false) {
4200
+ return new Promise((resolve) => {
4201
+ process.stdout.write(label);
4202
+ let input = "";
4203
+ const stdin = process.stdin;
4204
+ const wasRaw = stdin.isRaw;
4205
+ stdin.setRawMode(true);
4206
+ stdin.resume();
4207
+ stdin.setEncoding("utf8");
4208
+ const onData = (data) => {
4209
+ for (const ch of data) {
4210
+ if (ch === "\r" || ch === "\n") {
4211
+ stdin.removeListener("data", onData);
4212
+ stdin.setRawMode(wasRaw ?? false);
4213
+ stdin.pause();
4214
+ process.stdout.write("\n");
4215
+ resolve(input.trim());
4216
+ return;
4217
+ } else if (ch === "\x7F" || ch === "\b") {
4218
+ if (input.length > 0) {
4219
+ input = input.slice(0, -1);
4220
+ process.stdout.write("\b \b");
4221
+ }
4222
+ } else if (ch === "") {
4223
+ process.stdout.write("\n");
4224
+ process.exit(0);
4225
+ } else if (ch.charCodeAt(0) >= 32) {
4226
+ input += ch;
4227
+ process.stdout.write(masked ? "*" : ch);
4228
+ }
4229
+ }
4230
+ };
4231
+ stdin.on("data", onData);
4232
+ });
4233
+ }
4234
+ function resetConfig() {
4235
+ const userPath = getUserSettingsPath();
4236
+ if (deleteSettings(userPath)) {
4237
+ process.stdout.write(`Deleted ${userPath}
4238
+ `);
4239
+ } else {
4240
+ process.stdout.write("No user settings found.\n");
4241
+ }
4242
+ }
4243
+ async function startCli(options = {}) {
4244
+ const args = parseCliArgs();
4245
+ if (args.version) {
4246
+ process.stdout.write(`robota ${readVersion()}
4247
+ `);
4248
+ return;
4249
+ }
4250
+ if (args.reset) {
4251
+ resetConfig();
4252
+ return;
4253
+ }
4254
+ const cwd = process.cwd();
4255
+ const providerDefinitions = options.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
4256
+ if (args.configure) {
4257
+ await runInteractiveProviderSetup(cwd, args, promptInput, providerDefinitions);
4258
+ return;
4259
+ }
4260
+ if (handleProviderConfigurationArgs(cwd, args, providerDefinitions)) {
4261
+ return;
4262
+ }
4263
+ try {
4264
+ await ensureConfig(cwd, args, promptInput, providerDefinitions);
4265
+ } catch (error) {
4266
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4267
+ `);
4268
+ process.exit(1);
4269
+ }
4270
+ const providerOptions = args.provider ? { providerOverride: args.provider, providerDefinitions } : { providerDefinitions };
4271
+ const providerSettings = readProviderSettings(cwd, providerOptions);
4272
+ const modelId = args.model ?? providerSettings.model;
4273
+ const provider = createProviderFromSettings(cwd, args.model, providerOptions);
4274
+ const backgroundTaskRunners = [createManagedShellProcessRunner()];
4275
+ const paths = projectPaths(cwd);
4276
+ const subagentRunnerFactory = createChildProcessSubagentRunnerFactory({
4277
+ providerConfig: { ...providerSettings, model: modelId },
4278
+ logsDir: paths.logs
4279
+ });
4280
+ const sessionStore = new SessionStore(paths.sessions);
4281
+ let resumeSessionId;
4282
+ if (args.continueMode) {
4283
+ const sessions = sessionStore.list().filter((s) => s.cwd === cwd);
4284
+ if (sessions.length > 0) {
4285
+ resumeSessionId = sessions[0].id;
4286
+ }
4287
+ } else if (args.resumeId !== void 0) {
4288
+ if (args.resumeId === "") {
4289
+ resumeSessionId = "__picker__";
4290
+ } else {
4291
+ const sessions = sessionStore.list();
4292
+ const match = sessions.find((s) => s.id === args.resumeId || s.name === args.resumeId);
4293
+ if (match) {
4294
+ resumeSessionId = match.id;
4295
+ } else {
4296
+ process.stderr.write(`Session not found: ${args.resumeId}
4297
+ `);
4298
+ process.exit(1);
4299
+ }
4300
+ }
4301
+ }
4302
+ if (args.printMode) {
4303
+ let prompt = args.positional.join(" ").trim();
4304
+ if (!prompt && !process.stdin.isTTY) {
4305
+ const chunks = [];
4306
+ for await (const chunk of process.stdin) {
4307
+ chunks.push(chunk);
4308
+ }
4309
+ prompt = Buffer.concat(chunks).toString("utf-8").trim();
4310
+ }
4311
+ if (!prompt) {
4312
+ process.stderr.write("Print mode (-p) requires a prompt argument.\n");
4313
+ process.exit(1);
4314
+ }
4315
+ const appendParts = [];
4316
+ if (args.appendSystemPrompt) appendParts.push(args.appendSystemPrompt);
4317
+ if (args.jsonSchema)
4318
+ appendParts.push(
4319
+ `Respond with valid JSON only, matching this JSON schema:
4320
+ ${args.jsonSchema}`
4321
+ );
4322
+ const appendSystemPrompt = appendParts.length > 0 ? appendParts.join("\n\n") : void 0;
4323
+ const session = new InteractiveSession2({
4324
+ cwd,
4325
+ provider,
4326
+ permissionMode: args.permissionMode ?? "bypassPermissions",
4327
+ maxTurns: args.maxTurns,
4328
+ sessionStore: args.noSessionPersistence ? void 0 : sessionStore,
4329
+ sessionName: args.sessionName,
4330
+ bare: args.bare || void 0,
4331
+ allowedTools: args.allowedTools ? args.allowedTools.split(",").map((t) => t.trim()).filter((t) => t.length > 0) : void 0,
4332
+ appendSystemPrompt,
4333
+ backgroundTaskRunners,
4334
+ subagentRunnerFactory,
4335
+ commandModules: options.commandModules
4336
+ });
4337
+ const transport = createHeadlessTransport({
4338
+ outputFormat: args.outputFormat ?? "text",
4339
+ prompt
4340
+ });
4341
+ session.attachTransport(transport);
4342
+ await transport.start();
4343
+ await session.shutdown({ reason: "prompt_input_exit", message: "Headless transport complete" });
4344
+ process.exit(transport.getExitCode());
4345
+ }
4346
+ renderApp({
4347
+ cwd,
4348
+ provider,
4349
+ modelId,
4350
+ language: args.language,
4351
+ permissionMode: args.permissionMode,
4352
+ maxTurns: args.maxTurns,
4353
+ version: readVersion(),
4354
+ sessionStore,
4355
+ resumeSessionId,
4356
+ forkSession: args.forkSession,
4357
+ sessionName: args.sessionName,
4358
+ backgroundTaskRunners,
4359
+ subagentRunnerFactory,
4360
+ commandModules: options.commandModules,
4361
+ providerDefinitions
4362
+ });
4363
+ }
4364
+
4365
+ export {
4366
+ createManagedShellProcessRunner,
4367
+ createGitWorktreeIsolationAdapter,
4368
+ GitWorktreeIsolationAdapter,
4369
+ createChildProcessSubagentRunnerFactory,
4370
+ ChildProcessSubagentRunner,
4371
+ startCli
4372
+ };