@robota-sdk/agent-cli 3.0.0-beta.53 → 3.0.0-beta.55

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.
@@ -30,15 +30,20 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ChildProcessSubagentRunner: () => ChildProcessSubagentRunner,
34
+ GitWorktreeIsolationAdapter: () => GitWorktreeIsolationAdapter,
35
+ createChildProcessSubagentRunnerFactory: () => createChildProcessSubagentRunnerFactory,
36
+ createGitWorktreeIsolationAdapter: () => createGitWorktreeIsolationAdapter,
37
+ createManagedShellProcessRunner: () => createManagedShellProcessRunner,
33
38
  startCli: () => startCli
34
39
  });
35
40
  module.exports = __toCommonJS(index_exports);
36
41
 
37
42
  // src/cli.ts
38
- var import_node_fs3 = require("fs");
39
- var import_node_path5 = require("path");
43
+ var import_node_fs6 = require("fs");
44
+ var import_node_path8 = require("path");
40
45
  var import_node_url = require("url");
41
- var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
46
+ var import_agent_sdk8 = require("@robota-sdk/agent-sdk");
42
47
  var import_agent_sessions = require("@robota-sdk/agent-sessions");
43
48
 
44
49
  // src/utils/cli-args.ts
@@ -80,7 +85,20 @@ function parseCliArgs() {
80
85
  "system-prompt": { type: "string" },
81
86
  "append-system-prompt": { type: "string" },
82
87
  version: { type: "boolean", default: false },
83
- reset: { type: "boolean", default: false }
88
+ reset: { type: "boolean", default: false },
89
+ bare: { type: "boolean", default: false },
90
+ "allowed-tools": { type: "string" },
91
+ "no-session-persistence": { type: "boolean", default: false },
92
+ "json-schema": { type: "string" },
93
+ configure: { type: "boolean", default: false },
94
+ "configure-provider": { type: "string" },
95
+ provider: { type: "string" },
96
+ type: { type: "string" },
97
+ "base-url": { type: "string" },
98
+ "api-key": { type: "string" },
99
+ "api-key-env": { type: "string" },
100
+ "set-current": { type: "boolean", default: false },
101
+ "settings-scope": { type: "string" }
84
102
  }
85
103
  });
86
104
  return {
@@ -98,7 +116,20 @@ function parseCliArgs() {
98
116
  systemPrompt: values["system-prompt"],
99
117
  appendSystemPrompt: values["append-system-prompt"],
100
118
  version: values["version"] ?? false,
101
- reset: values["reset"] ?? false
119
+ reset: values["reset"] ?? false,
120
+ bare: values["bare"] ?? false,
121
+ allowedTools: values["allowed-tools"],
122
+ noSessionPersistence: values["no-session-persistence"] ?? false,
123
+ jsonSchema: values["json-schema"],
124
+ configure: values["configure"] ?? false,
125
+ configureProvider: values["configure-provider"],
126
+ provider: values["provider"],
127
+ providerType: values["type"],
128
+ baseURL: values["base-url"],
129
+ apiKey: values["api-key"],
130
+ apiKeyEnv: values["api-key-env"],
131
+ setCurrent: values["set-current"] ?? false,
132
+ settingsScope: values["settings-scope"]
102
133
  };
103
134
  }
104
135
 
@@ -126,11 +157,26 @@ function writeSettings(path, settings) {
126
157
  }
127
158
  function updateModelInSettings(settingsPath, modelId) {
128
159
  const settings = readSettings(settingsPath);
129
- const provider = settings.provider ?? {};
130
- provider.model = modelId;
131
- settings.provider = provider;
160
+ const currentProvider = settings.currentProvider;
161
+ const providers = settings.providers;
162
+ if (typeof currentProvider === "string" && isSettingsData(providers)) {
163
+ const providerMap = providers;
164
+ providerMap[currentProvider] = {
165
+ ...isSettingsData(providerMap[currentProvider]) ? providerMap[currentProvider] : {},
166
+ model: modelId
167
+ };
168
+ settings.providers = providerMap;
169
+ } else {
170
+ settings.provider = {
171
+ ...isSettingsData(settings.provider) ? settings.provider : {},
172
+ model: modelId
173
+ };
174
+ }
132
175
  writeSettings(settingsPath, settings);
133
176
  }
177
+ function isSettingsData(value) {
178
+ return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
179
+ }
134
180
  function deleteSettings(path) {
135
181
  if ((0, import_node_fs.existsSync)(path)) {
136
182
  (0, import_node_fs.unlinkSync)(path);
@@ -143,62 +189,556 @@ function deleteSettings(path) {
143
189
  var import_node_fs2 = require("fs");
144
190
  var import_node_path2 = require("path");
145
191
  var import_node_os = require("os");
192
+
193
+ // src/utils/provider-default-definitions.ts
146
194
  var import_agent_provider_anthropic = require("@robota-sdk/agent-provider-anthropic");
147
- function readProviderSettings(cwd) {
195
+ var import_agent_provider_gemma = require("@robota-sdk/agent-provider-gemma");
196
+ var import_agent_provider_openai = require("@robota-sdk/agent-provider-openai");
197
+ var DEFAULT_PROVIDER_DEFINITIONS = [
198
+ (0, import_agent_provider_anthropic.createAnthropicProviderDefinition)(),
199
+ (0, import_agent_provider_openai.createOpenAIProviderDefinition)(),
200
+ (0, import_agent_provider_gemma.createGemmaProviderDefinition)()
201
+ ];
202
+
203
+ // src/utils/provider-definition.ts
204
+ var import_agent_core = require("@robota-sdk/agent-core");
205
+
206
+ // src/utils/provider-factory.ts
207
+ function readProviderSettings(cwd, options = {}) {
208
+ const merged = readMergedProviderSettings(cwd);
209
+ const providerConfig = resolveActiveProvider(
210
+ merged,
211
+ options.providerOverride,
212
+ getProviderDefinitions(options)
213
+ );
214
+ if (providerConfig !== void 0) {
215
+ return providerConfig;
216
+ }
217
+ throw new Error("No provider configuration found. Run `robota` to set up.");
218
+ }
219
+ function readMergedProviderSettings(cwd) {
148
220
  const paths = [
149
- (0, import_node_path2.join)(cwd, ".robota", "settings.local.json"),
221
+ (0, import_node_path2.join)((0, import_node_os.homedir)(), ".robota", "settings.json"),
222
+ (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "settings.json"),
150
223
  (0, import_node_path2.join)(cwd, ".robota", "settings.json"),
151
- (0, import_node_path2.join)(cwd, ".claude", "settings.local.json"),
224
+ (0, import_node_path2.join)(cwd, ".robota", "settings.local.json"),
152
225
  (0, import_node_path2.join)(cwd, ".claude", "settings.json"),
153
- (0, import_node_path2.join)((0, import_node_os.homedir)(), ".robota", "settings.json"),
154
- (0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "settings.json")
226
+ (0, import_node_path2.join)(cwd, ".claude", "settings.local.json")
155
227
  ];
156
- for (const filePath of paths) {
157
- if (!(0, import_node_fs2.existsSync)(filePath)) continue;
158
- try {
159
- const raw = (0, import_node_fs2.readFileSync)(filePath, "utf8");
160
- const parsed = JSON.parse(raw);
161
- const provider = parsed.provider;
162
- if (provider?.apiKey && provider?.name) {
163
- return {
164
- name: provider.name,
165
- model: provider.model ?? "claude-sonnet-4-6",
166
- apiKey: provider.apiKey
167
- };
168
- }
169
- } catch {
170
- continue;
228
+ return paths.reduce((settings, filePath) => {
229
+ const parsed = readSettingsFile(filePath);
230
+ if (parsed === void 0) {
231
+ return settings;
171
232
  }
233
+ return mergeSettings(settings, parsed);
234
+ }, {});
235
+ }
236
+ function readSettingsFile(filePath) {
237
+ if (!(0, import_node_fs2.existsSync)(filePath)) {
238
+ return void 0;
172
239
  }
173
- throw new Error("No provider configuration found. Run `robota` to set up.");
240
+ try {
241
+ const raw = (0, import_node_fs2.readFileSync)(filePath, "utf8");
242
+ return JSON.parse(raw);
243
+ } catch {
244
+ return void 0;
245
+ }
246
+ }
247
+ function mergeSettings(base, override) {
248
+ return {
249
+ ...base,
250
+ ...override,
251
+ provider: base.provider !== void 0 || override.provider !== void 0 ? { ...base.provider, ...override.provider } : void 0,
252
+ providers: base.providers !== void 0 || override.providers !== void 0 ? mergeProviders(base.providers, override.providers) : void 0
253
+ };
254
+ }
255
+ function mergeProviders(base, override) {
256
+ const result = { ...base ?? {} };
257
+ for (const [name, profile] of Object.entries(override ?? {})) {
258
+ result[name] = { ...result[name], ...profile };
259
+ }
260
+ return result;
261
+ }
262
+ function resolveActiveProvider(settings, providerOverride, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
263
+ const activeProvider = providerOverride ?? settings.currentProvider;
264
+ if (activeProvider !== void 0) {
265
+ const profile = settings.providers?.[activeProvider];
266
+ if (profile === void 0) {
267
+ throw new Error(`Provider profile "${activeProvider}" was not found in providers`);
268
+ }
269
+ if (!profile.type) {
270
+ throw new Error(`Provider profile "${activeProvider}" is missing type`);
271
+ }
272
+ return normalizeProviderConfig(
273
+ {
274
+ name: profile.type,
275
+ model: profile.model,
276
+ apiKey: profile.apiKey,
277
+ baseURL: profile.baseURL,
278
+ timeout: profile.timeout
279
+ },
280
+ providerDefinitions
281
+ );
282
+ }
283
+ const provider = settings.provider;
284
+ if (provider?.name) {
285
+ return normalizeProviderConfig(
286
+ {
287
+ name: provider.name,
288
+ model: provider.model,
289
+ apiKey: provider.apiKey,
290
+ baseURL: provider.baseURL,
291
+ timeout: provider.timeout
292
+ },
293
+ providerDefinitions
294
+ );
295
+ }
296
+ return void 0;
174
297
  }
175
- function createProviderFromSettings(cwd, modelOverride) {
176
- const settings = readProviderSettings(cwd);
298
+ function normalizeProviderConfig(settings, providerDefinitions) {
299
+ const defaults = (0, import_agent_core.findProviderDefinition)(providerDefinitions, settings.name)?.defaults ?? {};
300
+ const model = settings.model ?? defaults.model;
301
+ if (!model) {
302
+ throw new Error(`Provider ${settings.name} requires model`);
303
+ }
304
+ return {
305
+ name: settings.name,
306
+ model,
307
+ apiKey: settings.apiKey !== void 0 ? resolveEnvRef(settings.apiKey) : defaults.apiKey,
308
+ baseURL: settings.baseURL ?? defaults.baseURL,
309
+ timeout: settings.timeout
310
+ };
311
+ }
312
+ function resolveEnvRef(value) {
313
+ const envPrefix = "$ENV:";
314
+ if (!value.startsWith(envPrefix)) {
315
+ return value;
316
+ }
317
+ const envName = value.slice(envPrefix.length);
318
+ return process.env[envName] ?? value;
319
+ }
320
+ function createProviderFromConfig(settings, providerDefinitions) {
321
+ const definition = (0, import_agent_core.findProviderDefinition)(providerDefinitions, settings.name);
322
+ if (definition === void 0) {
323
+ throw new Error(
324
+ `Unknown provider: ${settings.name}. Currently supported: ${(0, import_agent_core.formatSupportedProviderTypes)(providerDefinitions)}`
325
+ );
326
+ }
327
+ if (definition.requiresApiKey === true && !settings.apiKey) {
328
+ throw new Error(`Provider ${settings.name} requires apiKey`);
329
+ }
330
+ return definition.createProvider(settings);
331
+ }
332
+ function createProviderFromSettings(cwd, modelOverride, options = {}) {
333
+ const providerDefinitions = getProviderDefinitions(options);
334
+ const settings = readProviderSettings(cwd, { ...options, providerDefinitions });
177
335
  const model = modelOverride ?? settings.model;
178
- switch (settings.name) {
179
- case "anthropic":
180
- return new import_agent_provider_anthropic.AnthropicProvider({ apiKey: settings.apiKey, defaultModel: model });
181
- default:
182
- throw new Error(`Unknown provider: ${settings.name}. Currently supported: anthropic`);
336
+ return createProviderFromConfig({ ...settings, model }, providerDefinitions);
337
+ }
338
+ function getProviderDefinitions(options) {
339
+ return options.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
340
+ }
341
+
342
+ // src/utils/provider-setup.ts
343
+ var import_node_path3 = require("path");
344
+ var import_node_os2 = require("os");
345
+
346
+ // src/utils/settings-check.ts
347
+ var import_node_fs3 = require("fs");
348
+ function checkSettingsFile(filePath, providerDefinitions = []) {
349
+ if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
350
+ try {
351
+ const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
352
+ if (raw.length === 0) return "incomplete";
353
+ const parsed = JSON.parse(raw);
354
+ if (!hasUsableProviderConfig(parsed, providerDefinitions)) return "incomplete";
355
+ return "valid";
356
+ } catch {
357
+ return "corrupt";
358
+ }
359
+ }
360
+ function hasUsableProviderConfig(settings, providerDefinitions) {
361
+ if (settings.provider && isUsableProviderProfile(settings.provider.name, settings.provider, providerDefinitions)) {
362
+ return true;
363
+ }
364
+ if (typeof settings.currentProvider !== "string") {
365
+ return false;
366
+ }
367
+ const profile = settings.providers?.[settings.currentProvider];
368
+ return isUsableProviderProfile(profile?.type, profile, providerDefinitions);
369
+ }
370
+ function isUsableProviderProfile(type, profile, providerDefinitions) {
371
+ if (!profile) {
372
+ return false;
373
+ }
374
+ if (profile.apiKey) {
375
+ return true;
376
+ }
377
+ if (!type) {
378
+ return false;
379
+ }
380
+ const definition = (0, import_agent_core.findProviderDefinition)(providerDefinitions, type);
381
+ if (definition === void 0) {
382
+ return false;
383
+ }
384
+ return definition.requiresApiKey !== true || definition.defaults?.apiKey !== void 0;
385
+ }
386
+
387
+ // src/utils/provider-settings.ts
388
+ function upsertProviderProfile(settings, profileName, profile) {
389
+ return {
390
+ ...settings,
391
+ providers: {
392
+ ...settings.providers ?? {},
393
+ [profileName]: profile
394
+ }
395
+ };
396
+ }
397
+ function setCurrentProvider(settings, profileName) {
398
+ if (!settings.providers?.[profileName]) {
399
+ throw new Error(`Provider profile "${profileName}" was not found`);
400
+ }
401
+ return {
402
+ ...settings,
403
+ currentProvider: profileName
404
+ };
405
+ }
406
+ function validateProviderProfile(profileName, profile, options = {}) {
407
+ if (!profile.type) {
408
+ throw new Error(`Provider profile "${profileName}" is missing type`);
409
+ }
410
+ if (!profile.model) {
411
+ throw new Error(`Provider profile "${profileName}" is missing model`);
412
+ }
413
+ const definition = (0, import_agent_core.findProviderDefinition)(options.providerDefinitions ?? [], profile.type);
414
+ if (definition?.requiresApiKey === true && !profile.apiKey && definition.defaults?.apiKey === void 0) {
415
+ throw new Error(`Provider profile "${profileName}" is missing apiKey`);
183
416
  }
184
417
  }
418
+ function buildProviderSetupPatch(input, options = {}) {
419
+ const profile = buildProviderProfile(input, options);
420
+ validateProviderProfile(input.profile, profile, options);
421
+ return {
422
+ ...input.setCurrent && { currentProvider: input.profile },
423
+ providers: {
424
+ [input.profile]: profile
425
+ }
426
+ };
427
+ }
428
+ function buildProviderProfile(input, options = {}) {
429
+ const defaults = getProviderDefaults(input.type, options.providerDefinitions ?? []);
430
+ const apiKey = input.apiKeyEnv !== void 0 ? `$ENV:${input.apiKeyEnv}` : input.apiKey ?? defaults.apiKey;
431
+ const baseURL = input.baseURL ?? defaults.baseURL;
432
+ return {
433
+ type: input.type,
434
+ model: input.model ?? defaults.model,
435
+ ...apiKey !== void 0 && { apiKey },
436
+ ...baseURL !== void 0 && { baseURL },
437
+ ...input.timeout !== void 0 && { timeout: input.timeout }
438
+ };
439
+ }
440
+ function getProviderDefaults(type, providerDefinitions) {
441
+ return (0, import_agent_core.findProviderDefinition)(providerDefinitions, type)?.defaults ?? {};
442
+ }
443
+ function mergeProviderPatch(settings, patch) {
444
+ const [profileName, profile] = Object.entries(patch.providers)[0] ?? [];
445
+ if (!profileName || !profile) {
446
+ return settings;
447
+ }
448
+ const withProfile = upsertProviderProfile(settings, profileName, profile);
449
+ return patch.currentProvider ? setCurrentProvider(withProfile, patch.currentProvider) : withProfile;
450
+ }
451
+
452
+ // src/utils/provider-configuration.ts
453
+ function readProviderDocument(settingsPath) {
454
+ return readSettings(settingsPath);
455
+ }
456
+ function applyProviderConfiguration(settingsPath, input, options = {}) {
457
+ const settings = readProviderDocument(settingsPath);
458
+ const patch = buildProviderSetupPatch(input, options);
459
+ const next = mergeProviderPatch(settings, patch);
460
+ writeSettings(settingsPath, next);
461
+ return next;
462
+ }
463
+ function applyProviderSwitch(settingsPath, profileName, options = {}) {
464
+ const settings = readProviderDocument(settingsPath);
465
+ const hasLocalProfile = settings.providers?.[profileName] !== void 0;
466
+ const hasKnownProfile = options.knownProviders?.[profileName] !== void 0;
467
+ const next = hasLocalProfile || hasKnownProfile ? { ...settings, currentProvider: profileName } : setCurrentProvider(settings, profileName);
468
+ writeSettings(settingsPath, next);
469
+ return next;
470
+ }
471
+
472
+ // src/utils/provider-setup-flow.ts
473
+ function createProviderSetupFlow(type, providerDefinitions) {
474
+ return {
475
+ type,
476
+ steps: getProviderSetupSteps(type, providerDefinitions),
477
+ stepIndex: 0,
478
+ values: {}
479
+ };
480
+ }
481
+ function getProviderSetupStep(state) {
482
+ const step = state.steps[state.stepIndex];
483
+ if (step === void 0) {
484
+ throw new Error(`Provider setup step ${state.stepIndex} is out of range`);
485
+ }
486
+ return step;
487
+ }
488
+ function submitProviderSetupValue(state, rawValue) {
489
+ const step = getProviderSetupStep(state);
490
+ const value = rawValue.trim() || step.defaultValue || "";
491
+ const validationMessage = validateProviderSetupValue(step, value);
492
+ if (validationMessage !== void 0) {
493
+ return { status: "error", state, message: validationMessage };
494
+ }
495
+ const nextState = {
496
+ ...state,
497
+ stepIndex: state.stepIndex + 1,
498
+ values: { ...state.values, [step.key]: value }
499
+ };
500
+ if (nextState.stepIndex < state.steps.length) {
501
+ return { status: "next", state: nextState };
502
+ }
503
+ return { status: "complete", input: buildProviderSetupInput(nextState) };
504
+ }
505
+ async function runProviderSetupPromptFlow(type, promptInput2, providerDefinitions) {
506
+ let state = createProviderSetupFlow(type, providerDefinitions);
507
+ const stepCount = state.steps.length;
508
+ while (state.stepIndex < stepCount) {
509
+ const step = getProviderSetupStep(state);
510
+ const value = await promptInput2(formatProviderSetupPromptLabel(step), step.masked === true);
511
+ const result = submitProviderSetupValue(state, value);
512
+ if (result.status === "complete") {
513
+ return result.input;
514
+ }
515
+ if (result.status === "error") {
516
+ throw new Error(result.message);
517
+ }
518
+ state = result.state;
519
+ }
520
+ throw new Error("Provider setup flow ended without completion");
521
+ }
522
+ function formatProviderSetupPromptLabel(step) {
523
+ const suffix = step.defaultValue !== void 0 ? ` (default: ${step.defaultValue})` : "";
524
+ return ` ${step.title}${suffix}: `;
525
+ }
526
+ function validateProviderSetupValue(step, value) {
527
+ if (step.required === true && value.length === 0) {
528
+ return "Required";
529
+ }
530
+ return void 0;
531
+ }
532
+ function getProviderSetupSteps(type, providerDefinitions) {
533
+ const definition = (0, import_agent_core.findProviderDefinition)(providerDefinitions, type);
534
+ if (definition === void 0) {
535
+ throw new Error(
536
+ `Unknown provider: ${type}. Currently supported: ${(0, import_agent_core.formatSupportedProviderTypes)(providerDefinitions)}`
537
+ );
538
+ }
539
+ if (definition.setupSteps !== void 0) {
540
+ return [...definition.setupSteps];
541
+ }
542
+ const steps = [
543
+ {
544
+ key: "model",
545
+ title: `${definition.type} model`,
546
+ defaultValue: definition.defaults?.model,
547
+ required: definition.defaults?.model === void 0
548
+ }
549
+ ];
550
+ if (definition.defaults?.baseURL !== void 0) {
551
+ steps.unshift({
552
+ key: "baseURL",
553
+ title: `${definition.type} base URL`,
554
+ defaultValue: definition.defaults.baseURL
555
+ });
556
+ }
557
+ if (definition.requiresApiKey === true) {
558
+ steps.push({
559
+ key: "apiKey",
560
+ title: `${definition.type} API key`,
561
+ defaultValue: definition.defaults?.apiKey,
562
+ required: definition.defaults?.apiKey === void 0,
563
+ masked: true
564
+ });
565
+ }
566
+ return steps;
567
+ }
568
+ function buildProviderSetupInput(state) {
569
+ return {
570
+ profile: state.type,
571
+ type: state.type,
572
+ model: state.values.model,
573
+ apiKey: state.values.apiKey,
574
+ ...state.values.baseURL !== void 0 && { baseURL: state.values.baseURL },
575
+ setCurrent: true
576
+ };
577
+ }
578
+
579
+ // src/utils/provider-setup.ts
580
+ function getSettingsPathForScope(cwd, scope) {
581
+ if (scope === void 0 || scope === "user") {
582
+ return getUserSettingsPath();
583
+ }
584
+ if (scope === "project-local") {
585
+ return (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
586
+ }
587
+ throw new Error(`Invalid --settings-scope "${scope}". Valid: user | project-local`);
588
+ }
589
+ function handleProviderConfigurationArgs(cwd, args, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
590
+ const settingsPath = getSettingsPathForScope(cwd, args.settingsScope);
591
+ if (args.configureProvider) {
592
+ applyProviderConfiguration(settingsPath, buildSetupInputFromArgs(args), {
593
+ providerDefinitions
594
+ });
595
+ process.stdout.write(`Provider profile saved to ${settingsPath}
596
+ `);
597
+ return !args.printMode && args.positional.length === 0;
598
+ }
599
+ if (args.provider && args.setCurrent) {
600
+ applyProviderSwitch(settingsPath, args.provider, {
601
+ knownProviders: readMergedProviderSettings(cwd).providers
602
+ });
603
+ process.stdout.write(`Current provider set to ${args.provider}
604
+ `);
605
+ return !args.printMode && args.positional.length === 0;
606
+ }
607
+ return false;
608
+ }
609
+ async function ensureConfig(cwd, args, promptInput2, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
610
+ const checks = getSettingsCheckPaths(cwd).map((path) => ({
611
+ path,
612
+ status: checkSettingsFile(path, providerDefinitions)
613
+ }));
614
+ if (checks.some((check) => check.status === "valid")) {
615
+ return;
616
+ }
617
+ if (!isInteractiveTerminal()) {
618
+ throw new Error(formatMissingProviderConfigMessage());
619
+ }
620
+ await runInteractiveProviderSetup(cwd, args, promptInput2, providerDefinitions);
621
+ }
622
+ async function runInteractiveProviderSetup(cwd, args, promptInput2, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
623
+ const defaultProviderType = providerDefinitions[0]?.type ?? "";
624
+ const supportedTypes = providerDefinitions.map((definition) => definition.type).join("/");
625
+ const providerChoice = await promptInput2(` Provider (${supportedTypes}, default: ${defaultProviderType}): `) || defaultProviderType;
626
+ const type = parseProviderSetupType(providerChoice);
627
+ const settingsPath = getSettingsPathForScope(cwd, args.settingsScope);
628
+ const input = await runProviderSetupPromptFlow(type, promptInput2, providerDefinitions);
629
+ applyProviderConfiguration(settingsPath, input, {
630
+ providerDefinitions
631
+ });
632
+ const language = await promptInput2(" Response language (ko/en/ja/zh, default: en): ");
633
+ if (language) {
634
+ const settings = readSettings(settingsPath);
635
+ settings.language = language;
636
+ writeSettings(settingsPath, settings);
637
+ }
638
+ process.stdout.write(`
639
+ Config saved to ${settingsPath}
640
+
641
+ `);
642
+ }
643
+ function parseProviderSetupType(value) {
644
+ return value.trim();
645
+ }
646
+ function buildSetupInputFromArgs(args) {
647
+ const type = args.providerType ?? args.configureProvider;
648
+ if (!args.configureProvider || !type) {
649
+ throw new Error("--configure-provider requires a provider profile and --type");
650
+ }
651
+ return {
652
+ profile: args.configureProvider,
653
+ type,
654
+ ...args.model !== void 0 && { model: args.model },
655
+ ...args.apiKey !== void 0 && { apiKey: args.apiKey },
656
+ ...args.apiKeyEnv !== void 0 && { apiKeyEnv: args.apiKeyEnv },
657
+ ...args.baseURL !== void 0 && { baseURL: args.baseURL },
658
+ setCurrent: args.setCurrent
659
+ };
660
+ }
661
+ function getSettingsCheckPaths(cwd) {
662
+ return [
663
+ getUserSettingsPath(),
664
+ (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".claude", "settings.json"),
665
+ (0, import_node_path3.join)(cwd, ".robota", "settings.json"),
666
+ (0, import_node_path3.join)(cwd, ".robota", "settings.local.json"),
667
+ (0, import_node_path3.join)(cwd, ".claude", "settings.json"),
668
+ (0, import_node_path3.join)(cwd, ".claude", "settings.local.json")
669
+ ];
670
+ }
671
+ function isInteractiveTerminal() {
672
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
673
+ }
674
+ function formatMissingProviderConfigMessage() {
675
+ return [
676
+ "No provider configuration found.",
677
+ "Run `robota --configure` in an interactive terminal, or configure a provider:",
678
+ " robota --configure-provider gemma --type gemma --base-url http://localhost:1234/v1 --model supergemma4-26b-uncensored-v2 --api-key lm-studio --set-current",
679
+ " robota --configure-provider openai --type openai --model <openai-compatible-model> --api-key-env OPENAI_API_KEY --set-current"
680
+ ].join("\n");
681
+ }
185
682
 
186
683
  // src/cli.ts
187
684
  var import_agent_transport_headless = require("@robota-sdk/agent-transport-headless");
188
685
 
189
686
  // src/ui/render.tsx
190
- var import_ink17 = require("ink");
687
+ var import_ink18 = require("ink");
191
688
 
192
689
  // src/ui/App.tsx
193
- var import_react17 = require("react");
194
- var import_ink16 = require("ink");
195
- var import_agent_core5 = require("@robota-sdk/agent-core");
690
+ var import_react18 = require("react");
691
+ var import_ink17 = require("ink");
692
+ var import_agent_core7 = require("@robota-sdk/agent-core");
196
693
 
197
694
  // src/ui/hooks/useInteractiveSession.ts
198
695
  var import_react2 = require("react");
199
- var import_node_os2 = require("os");
200
- var import_node_path3 = require("path");
201
- var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
696
+ var import_node_os3 = require("os");
697
+ var import_node_path4 = require("path");
698
+ var import_agent_sdk = require("@robota-sdk/agent-sdk");
699
+ var import_agent_core3 = require("@robota-sdk/agent-core");
700
+
701
+ // src/ui/background-task-view-model.ts
702
+ var BACKGROUND_PREVIEW_LENGTH = 120;
703
+ var BACKGROUND_PREVIEW_WHITESPACE = /\s+/g;
704
+ var BACKGROUND_PREVIEW_SEPARATOR = " ";
705
+ var SUCCESS_EXIT_CODE = 0;
706
+ function toBackgroundTaskViewModel(state, partialText) {
707
+ return {
708
+ id: state.id,
709
+ kind: state.kind,
710
+ label: state.label,
711
+ status: state.status,
712
+ statusLabel: getBackgroundTaskStatusLabel(state),
713
+ mode: state.mode,
714
+ currentAction: state.currentAction,
715
+ unread: state.unread,
716
+ preview: trimBackgroundPreview(state.promptPreview ?? state.commandPreview) ?? "",
717
+ resultPreview: trimBackgroundPreview(state.result?.output ?? partialText),
718
+ errorPreview: trimBackgroundPreview(state.error?.message),
719
+ startedAt: state.startedAt,
720
+ lastActivityAt: state.lastActivityAt,
721
+ timeoutReason: state.timeoutReason
722
+ };
723
+ }
724
+ function getBackgroundTaskStatusLabel(state) {
725
+ if (state.status === "failed" && state.timeoutReason) {
726
+ if (state.timeoutReason === "idle" || state.timeoutReason === "max_runtime") {
727
+ return "timed out";
728
+ }
729
+ return state.timeoutReason.replace(/_/g, " ");
730
+ }
731
+ return state.status;
732
+ }
733
+ function shouldHideAtNextUserTurn(state) {
734
+ return state.status === "completed" && !state.error && (state.result?.exitCode === void 0 || state.result.exitCode === SUCCESS_EXIT_CODE) && !state.result?.signalCode && !state.worktreePath && !state.branchName;
735
+ }
736
+ function trimBackgroundPreview(value) {
737
+ if (!value) return void 0;
738
+ const preview = value.trim().replace(BACKGROUND_PREVIEW_WHITESPACE, BACKGROUND_PREVIEW_SEPARATOR);
739
+ if (!preview) return void 0;
740
+ return preview.length > BACKGROUND_PREVIEW_LENGTH ? `${preview.slice(0, BACKGROUND_PREVIEW_LENGTH)}...` : preview;
741
+ }
202
742
 
203
743
  // src/ui/tui-state-manager.ts
204
744
  var MAX_RENDERED_MESSAGES = 100;
@@ -231,10 +771,13 @@ var TuiStateManager = class {
231
771
  isAborting = false;
232
772
  pendingPrompt = null;
233
773
  contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
774
+ backgroundTasks = [];
234
775
  /** Called after any state change. React hook sets this to trigger re-render. */
235
776
  onChange = null;
236
777
  // ── Internal ──────────────────────────────────────────────────
237
778
  streamBuf = "";
779
+ backgroundTextBuffers = /* @__PURE__ */ new Map();
780
+ backgroundTasksHiddenOnNextTurn = /* @__PURE__ */ new Set();
238
781
  debouncedStreamNotify = createDebouncedNotify(() => this.notify(), STREAMING_DEBOUNCE_MS);
239
782
  notify() {
240
783
  this.onChange?.();
@@ -296,6 +839,30 @@ var TuiStateManager = class {
296
839
  this.activeTools = [];
297
840
  this.notify();
298
841
  };
842
+ onBackgroundTaskEvent = (event) => {
843
+ if ("task" in event) {
844
+ this.upsertBackgroundTask(event.task);
845
+ return;
846
+ }
847
+ if (event.type === "background_task_closed") {
848
+ this.backgroundTextBuffers.delete(event.taskId);
849
+ this.backgroundTasksHiddenOnNextTurn.delete(event.taskId);
850
+ this.backgroundTasks = this.backgroundTasks.filter((task) => task.id !== event.taskId);
851
+ this.notify();
852
+ return;
853
+ }
854
+ if (event.type === "background_task_text_delta") {
855
+ this.appendBackgroundTaskText(event.taskId, event.delta);
856
+ return;
857
+ }
858
+ if (event.type === "background_task_tool_start") {
859
+ this.updateBackgroundTaskAction(event.taskId, event.firstArg ?? event.toolName);
860
+ return;
861
+ }
862
+ if (event.type === "background_task_tool_end") {
863
+ this.updateBackgroundTaskAction(event.taskId, event.success ? void 0 : event.error);
864
+ }
865
+ };
299
866
  // ── State updates from external sources ───────────────────────
300
867
  /** Sync history from InteractiveSession */
301
868
  syncHistory(entries) {
@@ -324,16 +891,176 @@ var TuiStateManager = class {
324
891
  this.contextState = state;
325
892
  this.notify();
326
893
  }
894
+ onUserTurnAccepted() {
895
+ if (this.backgroundTasksHiddenOnNextTurn.size === 0) return;
896
+ const visible = this.backgroundTasks.filter(
897
+ (task) => !this.backgroundTasksHiddenOnNextTurn.has(task.id)
898
+ );
899
+ this.backgroundTasksHiddenOnNextTurn.clear();
900
+ if (visible.length === this.backgroundTasks.length) return;
901
+ this.backgroundTasks = visible;
902
+ this.notify();
903
+ }
904
+ upsertBackgroundTask(state) {
905
+ const partialText = state.result ? void 0 : this.backgroundTextBuffers.get(state.id);
906
+ const viewModel = toBackgroundTaskViewModel(state, partialText);
907
+ const index = this.backgroundTasks.findIndex((task) => task.id === state.id);
908
+ if (index === -1) {
909
+ this.backgroundTasks = [...this.backgroundTasks, viewModel];
910
+ } else {
911
+ const updated = [...this.backgroundTasks];
912
+ updated[index] = viewModel;
913
+ this.backgroundTasks = updated;
914
+ }
915
+ if (state.status === "completed" || state.status === "failed" || state.status === "cancelled") {
916
+ this.backgroundTextBuffers.delete(state.id);
917
+ }
918
+ if (shouldHideAtNextUserTurn(state)) {
919
+ this.backgroundTasksHiddenOnNextTurn.add(state.id);
920
+ } else {
921
+ this.backgroundTasksHiddenOnNextTurn.delete(state.id);
922
+ }
923
+ this.notify();
924
+ }
925
+ appendBackgroundTaskText(taskId, delta) {
926
+ const nextText = `${this.backgroundTextBuffers.get(taskId) ?? ""}${delta}`;
927
+ this.backgroundTextBuffers.set(taskId, nextText);
928
+ this.backgroundTasks = this.backgroundTasks.map(
929
+ (task) => task.id === taskId ? { ...task, resultPreview: trimBackgroundPreview(nextText) } : task
930
+ );
931
+ this.notify();
932
+ }
933
+ updateBackgroundTaskAction(taskId, currentAction) {
934
+ this.backgroundTasks = this.backgroundTasks.map(
935
+ (task) => task.id === taskId ? { ...task, currentAction } : task
936
+ );
937
+ this.notify();
938
+ }
327
939
  };
328
940
 
329
941
  // src/ui/hooks/useSlashRouting.ts
330
942
  var import_react = require("react");
331
943
  var import_node_crypto = require("crypto");
332
- var import_agent_sdk = require("@robota-sdk/agent-sdk");
333
- var import_agent_core = require("@robota-sdk/agent-core");
334
- function useSlashRouting(interactiveSession, registry, manager) {
944
+ var import_agent_core2 = require("@robota-sdk/agent-core");
945
+
946
+ // src/utils/provider-command.ts
947
+ async function handleProviderCommand(cwd, args, deps = {}) {
948
+ const settings = readMergedProviderSettings(cwd);
949
+ const providerDefinitions = deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
950
+ const [subcommand = "current", profileArg] = args.trim().split(/\s+/);
951
+ if (subcommand === "list") {
952
+ return {
953
+ message: formatProviderList(settings.currentProvider, settings.providers),
954
+ success: true
955
+ };
956
+ }
957
+ if (subcommand === "current" || subcommand === "") {
958
+ return {
959
+ message: formatCurrentProvider(settings.currentProvider, settings.providers),
960
+ success: true
961
+ };
962
+ }
963
+ if (subcommand === "use") {
964
+ return buildProviderSwitch(settings.providers, profileArg);
965
+ }
966
+ if (subcommand === "test") {
967
+ return await testProvider(settings.currentProvider, settings.providers, profileArg, deps);
968
+ }
969
+ if (subcommand === "add") {
970
+ return buildProviderSetup(profileArg, providerDefinitions);
971
+ }
972
+ return {
973
+ message: "Usage: provider [current|list|use <profile>|add <type>|test [profile]]",
974
+ success: false
975
+ };
976
+ }
977
+ function formatProviderList(currentProvider, providers) {
978
+ const entries = Object.entries(providers ?? {});
979
+ if (entries.length === 0) {
980
+ return "No provider profiles configured.";
981
+ }
982
+ return entries.map(([name, profile]) => {
983
+ const marker = name === currentProvider ? "*" : "-";
984
+ return `${marker} ${name}: ${profile.type ?? "unknown"} ${profile.model ?? "(no model)"}`;
985
+ }).join("\n");
986
+ }
987
+ function formatCurrentProvider(currentProvider, providers) {
988
+ if (!currentProvider) {
989
+ return "No current provider configured.";
990
+ }
991
+ const profile = providers?.[currentProvider];
992
+ if (!profile) {
993
+ return `Current provider "${currentProvider}" was not found in providers.`;
994
+ }
995
+ return [
996
+ `Current provider: ${currentProvider}`,
997
+ `Type: ${profile.type ?? "unknown"}`,
998
+ `Model: ${profile.model ?? "(no model)"}`,
999
+ ...profile.baseURL ? [`Base URL: ${profile.baseURL}`] : []
1000
+ ].join("\n");
1001
+ }
1002
+ function buildProviderSwitch(providers, profileName) {
1003
+ if (!profileName) {
1004
+ return { message: "Usage: provider use <profile>", success: false };
1005
+ }
1006
+ if (!providers?.[profileName]) {
1007
+ return { message: `Provider profile "${profileName}" was not found.`, success: false };
1008
+ }
1009
+ return {
1010
+ message: `Provider change requested: ${profileName}`,
1011
+ success: true,
1012
+ data: { providerSwitch: { profile: profileName } }
1013
+ };
1014
+ }
1015
+ function buildProviderSetup(type, providerDefinitions) {
1016
+ if (!type || (0, import_agent_core.findProviderDefinition)(providerDefinitions, type) === void 0) {
1017
+ return {
1018
+ message: `Usage: provider add <type>. Supported: ${(0, import_agent_core.formatSupportedProviderTypes)(providerDefinitions)}`,
1019
+ success: false
1020
+ };
1021
+ }
1022
+ return {
1023
+ message: `Provider setup requested: ${type}`,
1024
+ success: true,
1025
+ data: { providerSetup: { type } }
1026
+ };
1027
+ }
1028
+ async function testProvider(currentProvider, providers, profileArg, deps) {
1029
+ const profileName = profileArg ?? currentProvider;
1030
+ if (!profileName) {
1031
+ return { message: "No provider profile selected.", success: false };
1032
+ }
1033
+ const profile = providers?.[profileName];
1034
+ if (!profile) {
1035
+ return { message: `Provider profile "${profileName}" was not found.`, success: false };
1036
+ }
1037
+ try {
1038
+ validateProviderProfile(profileName, profile, {
1039
+ providerDefinitions: deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS
1040
+ });
1041
+ } catch (error) {
1042
+ return { message: error instanceof Error ? error.message : String(error), success: false };
1043
+ }
1044
+ const providerDefinitions = deps.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
1045
+ const definition = profile.type ? (0, import_agent_core.findProviderDefinition)(providerDefinitions, profile.type) : void 0;
1046
+ const probe = deps.probe ?? definition?.probeProfile ?? probeProviderProfile;
1047
+ const result = await probe(profile);
1048
+ return {
1049
+ message: result.ok ? `Provider "${profileName}" test passed: ${result.message}` : `Provider "${profileName}" test failed: ${result.message}; manual configuration can continue.`,
1050
+ success: true,
1051
+ data: { providerTest: { profile: profileName } }
1052
+ };
1053
+ }
1054
+ async function probeProviderProfile(profile) {
1055
+ void profile;
1056
+ return { ok: true, message: "Profile fields are valid; no endpoint probe configured." };
1057
+ }
1058
+
1059
+ // src/ui/hooks/useSlashRouting.ts
1060
+ function useSlashRouting(cwd, interactiveSession, registry, manager, providerDefinitions) {
335
1061
  return (0, import_react.useCallback)(
336
1062
  async (input) => {
1063
+ manager.onUserTurnAccepted();
337
1064
  if (!input.startsWith("/")) {
338
1065
  await interactiveSession.submit(input);
339
1066
  manager.setPendingPrompt(interactiveSession.getPendingPrompt());
@@ -342,81 +1069,114 @@ function useSlashRouting(interactiveSession, registry, manager) {
342
1069
  const parts = input.slice(1).split(/\s+/);
343
1070
  const cmd = parts[0]?.toLowerCase() ?? "";
344
1071
  const args = parts.slice(1).join(" ");
1072
+ if (cmd === "provider") {
1073
+ await routeProviderCommand(cwd, args, interactiveSession, manager, providerDefinitions);
1074
+ return;
1075
+ }
345
1076
  const result = await interactiveSession.executeCommand(cmd, args);
346
1077
  if (result) {
347
- manager.addEntry((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)(result.message)));
348
- const effects = interactiveSession;
349
- if (result.data?.modelId) {
350
- effects._pendingModelId = result.data.modelId;
351
- return;
352
- }
353
- if (result.data?.language) {
354
- effects._pendingLanguage = result.data.language;
355
- return;
356
- }
357
- if (result.data?.resetRequested) {
358
- effects._resetRequested = true;
359
- return;
360
- }
361
- if (result.data?.triggerResumePicker) {
362
- effects._triggerResumePicker = true;
363
- return;
364
- }
365
- if (result.data?.name) {
366
- effects._sessionName = result.data.name;
367
- return;
368
- }
369
- const ctx = interactiveSession.getContextState();
370
- manager.setContextState({
371
- percentage: ctx.usedPercentage,
372
- usedTokens: ctx.usedTokens,
373
- maxTokens: ctx.maxTokens
374
- });
1078
+ applySystemCommandResult(result, interactiveSession, manager);
375
1079
  return;
376
1080
  }
377
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
378
- if (skillCmd) {
379
- manager.addEntry({
380
- id: (0, import_node_crypto.randomUUID)(),
381
- timestamp: /* @__PURE__ */ new Date(),
382
- category: "event",
383
- type: "skill-invocation",
384
- data: {
385
- skillName: cmd,
386
- source: skillCmd.source,
387
- message: `Invoking ${skillCmd.source}: ${cmd}`
388
- }
389
- });
390
- const prompt = await (0, import_agent_sdk.buildSkillPrompt)(input, registry);
391
- if (prompt) {
392
- const qualifiedName = registry.resolveQualifiedName(cmd);
393
- const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
394
- await interactiveSession.submit(prompt, input, hookInput);
395
- manager.setPendingPrompt(interactiveSession.getPendingPrompt());
396
- return;
397
- }
398
- }
399
- if (cmd === "exit") {
400
- interactiveSession._exitRequested = true;
1081
+ if (await routeSkillCommand(input, cmd, registry, interactiveSession, manager)) {
401
1082
  return;
402
1083
  }
403
- if (cmd === "plugin") {
404
- interactiveSession._triggerPluginTUI = true;
1084
+ if (routeTuiCommand(cmd, interactiveSession)) {
405
1085
  return;
406
1086
  }
407
1087
  manager.addEntry(
408
- (0, import_agent_core.messageToHistoryEntry)(
409
- (0, import_agent_core.createSystemMessage)(`Unknown command "/${cmd}". Type /help for help.`)
1088
+ (0, import_agent_core2.messageToHistoryEntry)(
1089
+ (0, import_agent_core2.createSystemMessage)(`Unknown command "/${cmd}". Type /help for help.`)
410
1090
  )
411
1091
  );
412
1092
  },
413
- [interactiveSession, registry, manager]
1093
+ [cwd, interactiveSession, registry, manager, providerDefinitions]
414
1094
  );
415
1095
  }
1096
+ async function routeProviderCommand(cwd, args, interactiveSession, manager, providerDefinitions) {
1097
+ const result = await handleProviderCommand(cwd, args, { providerDefinitions });
1098
+ manager.addEntry((0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)(result.message)));
1099
+ const providerSwitch = result.data?.providerSwitch;
1100
+ if (providerSwitch?.profile) {
1101
+ getEffects(interactiveSession)._pendingProviderProfile = providerSwitch.profile;
1102
+ }
1103
+ const providerSetup = result.data?.providerSetup;
1104
+ if (providerSetup?.type) {
1105
+ getEffects(interactiveSession)._pendingProviderSetupType = providerSetup.type;
1106
+ }
1107
+ }
1108
+ function applySystemCommandResult(result, interactiveSession, manager) {
1109
+ manager.addEntry((0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)(result.message)));
1110
+ const data = result.data;
1111
+ const effects = getEffects(interactiveSession);
1112
+ if (typeof data?.modelId === "string") {
1113
+ effects._pendingModelId = data.modelId;
1114
+ return;
1115
+ }
1116
+ if (typeof data?.language === "string") {
1117
+ effects._pendingLanguage = data.language;
1118
+ return;
1119
+ }
1120
+ if (data?.resetRequested === true) {
1121
+ effects._resetRequested = true;
1122
+ return;
1123
+ }
1124
+ if (data?.triggerResumePicker === true) {
1125
+ effects._triggerResumePicker = true;
1126
+ return;
1127
+ }
1128
+ if (typeof data?.name === "string") {
1129
+ effects._sessionName = data.name;
1130
+ return;
1131
+ }
1132
+ const ctx = interactiveSession.getContextState();
1133
+ manager.setContextState({
1134
+ percentage: ctx.usedPercentage,
1135
+ usedTokens: ctx.usedTokens,
1136
+ maxTokens: ctx.maxTokens
1137
+ });
1138
+ }
1139
+ async function routeSkillCommand(input, cmd, registry, interactiveSession, manager) {
1140
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
1141
+ if (!skillCmd) {
1142
+ return false;
1143
+ }
1144
+ manager.addEntry({
1145
+ id: (0, import_node_crypto.randomUUID)(),
1146
+ timestamp: /* @__PURE__ */ new Date(),
1147
+ category: "event",
1148
+ type: "skill-invocation",
1149
+ data: {
1150
+ skillName: cmd,
1151
+ source: skillCmd.source,
1152
+ message: `Invoking ${skillCmd.source}: ${cmd}`
1153
+ }
1154
+ });
1155
+ const args = input.slice(1 + cmd.length).trimStart();
1156
+ const qualifiedName = registry.resolveQualifiedName(cmd);
1157
+ const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
1158
+ await interactiveSession.executeSkillCommand(skillCmd, args, input, hookInput);
1159
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
1160
+ return true;
1161
+ }
1162
+ function routeTuiCommand(cmd, interactiveSession) {
1163
+ if (cmd === "exit") {
1164
+ getEffects(interactiveSession)._exitRequested = true;
1165
+ return true;
1166
+ }
1167
+ if (cmd === "plugin") {
1168
+ getEffects(interactiveSession)._triggerPluginTUI = true;
1169
+ return true;
1170
+ }
1171
+ return false;
1172
+ }
1173
+ function getEffects(interactiveSession) {
1174
+ return interactiveSession;
1175
+ }
416
1176
 
417
1177
  // src/ui/hooks/useInteractiveSession.ts
418
1178
  function initializeSession(props, permissionHandler) {
419
- const interactiveSession = new import_agent_sdk2.InteractiveSession({
1179
+ const interactiveSession = new import_agent_sdk.InteractiveSession({
420
1180
  cwd: props.cwd,
421
1181
  provider: props.provider,
422
1182
  permissionMode: props.permissionMode,
@@ -425,17 +1185,23 @@ function initializeSession(props, permissionHandler) {
425
1185
  sessionStore: props.sessionStore,
426
1186
  resumeSessionId: props.resumeSessionId,
427
1187
  forkSession: props.forkSession,
428
- sessionName: props.sessionName
1188
+ sessionName: props.sessionName,
1189
+ backgroundTaskRunners: props.backgroundTaskRunners,
1190
+ subagentRunnerFactory: props.subagentRunnerFactory,
1191
+ commandModules: props.commandModules
429
1192
  });
430
- const registry = new import_agent_sdk2.CommandRegistry();
431
- registry.addSource(new import_agent_sdk2.BuiltinCommandSource());
432
- registry.addSource(new import_agent_sdk2.SkillCommandSource(props.cwd));
433
- const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
434
- const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
1193
+ const registry = new import_agent_sdk.CommandRegistry();
1194
+ registry.addSource(new import_agent_sdk.BuiltinCommandSource());
1195
+ for (const module2 of props.commandModules ?? []) {
1196
+ registry.addModule(module2);
1197
+ }
1198
+ registry.addSource(new import_agent_sdk.SkillCommandSource(props.cwd));
1199
+ const pluginsDir = (0, import_node_path4.join)((0, import_node_os3.homedir)(), ".robota", "plugins");
1200
+ const loader = new import_agent_sdk.BundlePluginLoader(pluginsDir);
435
1201
  try {
436
1202
  const plugins = loader.loadPluginsSync();
437
1203
  if (plugins.length > 0) {
438
- registry.addSource(new import_agent_sdk2.PluginCommandSource(plugins));
1204
+ registry.addSource(new import_agent_sdk.PluginCommandSource(plugins));
439
1205
  }
440
1206
  } catch {
441
1207
  }
@@ -445,6 +1211,7 @@ function initializeSession(props, permissionHandler) {
445
1211
  function useInteractiveSession(props) {
446
1212
  const [, forceRender] = (0, import_react2.useState)(0);
447
1213
  const [permissionRequest, setPermissionRequest] = (0, import_react2.useState)(null);
1214
+ const [isShuttingDown, setIsShuttingDown] = (0, import_react2.useState)(false);
448
1215
  const permissionQueueRef = (0, import_react2.useRef)([]);
449
1216
  const processingRef = (0, import_react2.useRef)(false);
450
1217
  const processNextPermission = (0, import_react2.useCallback)(() => {
@@ -494,6 +1261,7 @@ function useInteractiveSession(props) {
494
1261
  interactiveSession.on("complete", manager.onComplete);
495
1262
  interactiveSession.on("interrupted", manager.onInterrupted);
496
1263
  interactiveSession.on("error", manager.onError);
1264
+ interactiveSession.on("background_task_event", manager.onBackgroundTaskEvent);
497
1265
  const initCheck = setInterval(() => {
498
1266
  try {
499
1267
  const ctx = interactiveSession.getContextState();
@@ -519,6 +1287,7 @@ function useInteractiveSession(props) {
519
1287
  interactiveSession.off("complete", manager.onComplete);
520
1288
  interactiveSession.off("interrupted", manager.onInterrupted);
521
1289
  interactiveSession.off("error", manager.onError);
1290
+ interactiveSession.off("background_task_event", manager.onBackgroundTaskEvent);
522
1291
  };
523
1292
  }, [interactiveSession, manager]);
524
1293
  (0, import_react2.useEffect)(() => {
@@ -527,7 +1296,13 @@ function useInteractiveSession(props) {
527
1296
  manager.setPendingPrompt(interactiveSession.getPendingPrompt());
528
1297
  }
529
1298
  }, [manager.isThinking, interactiveSession, manager]);
530
- const handleSubmit = useSlashRouting(interactiveSession, registry, manager);
1299
+ const handleSubmit = useSlashRouting(
1300
+ props.cwd,
1301
+ interactiveSession,
1302
+ registry,
1303
+ manager,
1304
+ props.providerDefinitions ?? []
1305
+ );
531
1306
  const handleAbort = (0, import_react2.useCallback)(() => {
532
1307
  manager.setAborting(true);
533
1308
  interactiveSession.abort();
@@ -536,6 +1311,15 @@ function useInteractiveSession(props) {
536
1311
  interactiveSession.cancelQueue();
537
1312
  manager.setPendingPrompt(null);
538
1313
  }, [interactiveSession, manager]);
1314
+ const handleShutdown = (0, import_react2.useCallback)(
1315
+ async (reason = "prompt_input_exit") => {
1316
+ if (isShuttingDown) return;
1317
+ setIsShuttingDown(true);
1318
+ manager.addEntry((0, import_agent_core3.messageToHistoryEntry)((0, import_agent_core3.createSystemMessage)("Shutting down...")));
1319
+ await interactiveSession.shutdown({ reason, message: "CLI shutdown" });
1320
+ },
1321
+ [interactiveSession, manager, isShuttingDown]
1322
+ );
539
1323
  return {
540
1324
  interactiveSession,
541
1325
  registry,
@@ -545,33 +1329,36 @@ function useInteractiveSession(props) {
545
1329
  activeTools: manager.activeTools,
546
1330
  isThinking: manager.isThinking,
547
1331
  isAborting: manager.isAborting,
1332
+ isShuttingDown,
548
1333
  pendingPrompt: manager.pendingPrompt,
1334
+ backgroundTasks: manager.backgroundTasks,
549
1335
  permissionRequest,
550
1336
  contextState: manager.contextState,
551
1337
  handleSubmit,
552
1338
  handleAbort,
553
- handleCancelQueue
1339
+ handleCancelQueue,
1340
+ handleShutdown
554
1341
  };
555
1342
  }
556
1343
 
557
1344
  // src/ui/hooks/usePluginCallbacks.ts
558
1345
  var import_react3 = require("react");
559
- var import_node_os3 = require("os");
560
- var import_node_path4 = require("path");
561
- var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
1346
+ var import_node_os4 = require("os");
1347
+ var import_node_path5 = require("path");
1348
+ var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
562
1349
  function usePluginCallbacks(cwd) {
563
1350
  return (0, import_react3.useMemo)(() => {
564
- const home = (0, import_node_os3.homedir)();
565
- const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
566
- const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
567
- const settingsStore = new import_agent_sdk3.PluginSettingsStore(userSettingsPath);
568
- const marketplace = new import_agent_sdk3.MarketplaceClient({ pluginsDir });
569
- const installer = new import_agent_sdk3.BundlePluginInstaller({
1351
+ const home = (0, import_node_os4.homedir)();
1352
+ const pluginsDir = (0, import_node_path5.join)(home, ".robota", "plugins");
1353
+ const userSettingsPath = (0, import_node_path5.join)(home, ".robota", "settings.json");
1354
+ const settingsStore = new import_agent_sdk2.PluginSettingsStore(userSettingsPath);
1355
+ const marketplace = new import_agent_sdk2.MarketplaceClient({ pluginsDir });
1356
+ const installer = new import_agent_sdk2.BundlePluginInstaller({
570
1357
  pluginsDir,
571
1358
  settingsStore,
572
1359
  marketplaceClient: marketplace
573
1360
  });
574
- const loader = new import_agent_sdk3.BundlePluginLoader(pluginsDir);
1361
+ const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
575
1362
  return {
576
1363
  listInstalled: async () => {
577
1364
  const plugins = await loader.loadAll();
@@ -609,8 +1396,8 @@ function usePluginCallbacks(cwd) {
609
1396
  throw new Error("Plugin ID must be in format: name@marketplace");
610
1397
  }
611
1398
  if (scope === "project") {
612
- const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
613
- const projectInstaller = new import_agent_sdk3.BundlePluginInstaller({
1399
+ const projectPluginsDir = (0, import_node_path5.join)(cwd, ".robota", "plugins");
1400
+ const projectInstaller = new import_agent_sdk2.BundlePluginInstaller({
614
1401
  pluginsDir: projectPluginsDir,
615
1402
  settingsStore,
616
1403
  marketplaceClient: marketplace
@@ -661,19 +1448,33 @@ function usePluginCallbacks(cwd) {
661
1448
  // src/ui/hooks/useSideEffects.ts
662
1449
  var import_react4 = require("react");
663
1450
  var import_ink = require("ink");
664
- var import_agent_core2 = require("@robota-sdk/agent-core");
1451
+ var import_agent_core4 = require("@robota-sdk/agent-core");
665
1452
  var EXIT_DELAY_MS = 500;
666
1453
  function useSideEffects({
1454
+ cwd,
667
1455
  interactiveSession,
668
1456
  addEntry,
669
1457
  baseHandleSubmit,
670
- setSessionName
1458
+ setSessionName,
1459
+ providerDefinitions
671
1460
  }) {
672
1461
  const { exit } = (0, import_ink.useApp)();
673
1462
  const [pendingModelId, setPendingModelId] = (0, import_react4.useState)(null);
674
1463
  const pendingModelChangeRef = (0, import_react4.useRef)(null);
1464
+ const [pendingProviderProfile, setPendingProviderProfile] = (0, import_react4.useState)(null);
1465
+ const pendingProviderProfileRef = (0, import_react4.useRef)(null);
1466
+ const [pendingProviderSetupType, setPendingProviderSetupType] = (0, import_react4.useState)(null);
675
1467
  const [showPluginTUI, setShowPluginTUI] = (0, import_react4.useState)(false);
676
1468
  const [showSessionPicker, setShowSessionPicker] = (0, import_react4.useState)(false);
1469
+ const requestShutdown = (0, import_react4.useCallback)(
1470
+ (reason, message) => {
1471
+ addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Shutting down...")));
1472
+ setTimeout(() => {
1473
+ void interactiveSession.shutdown({ reason, message }).finally(() => exit());
1474
+ }, EXIT_DELAY_MS);
1475
+ },
1476
+ [interactiveSession, addEntry, exit]
1477
+ );
677
1478
  const handleSubmit = (0, import_react4.useCallback)(
678
1479
  async (input) => {
679
1480
  await baseHandleSubmit(input);
@@ -693,9 +1494,22 @@ function useSideEffects({
693
1494
  settings.language = lang;
694
1495
  writeSettings(settingsPath, settings);
695
1496
  addEntry(
696
- (0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)(`Language set to "${lang}". Restarting...`))
1497
+ (0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(`Language set to "${lang}". Restarting...`))
697
1498
  );
698
- setTimeout(() => exit(), EXIT_DELAY_MS);
1499
+ requestShutdown("other", "Language change restart");
1500
+ return;
1501
+ }
1502
+ if (sideEffects._pendingProviderProfile) {
1503
+ const profile = sideEffects._pendingProviderProfile;
1504
+ delete sideEffects._pendingProviderProfile;
1505
+ pendingProviderProfileRef.current = profile;
1506
+ setPendingProviderProfile(profile);
1507
+ return;
1508
+ }
1509
+ if (sideEffects._pendingProviderSetupType) {
1510
+ const type = sideEffects._pendingProviderSetupType;
1511
+ delete sideEffects._pendingProviderSetupType;
1512
+ setPendingProviderSetupType(type);
699
1513
  return;
700
1514
  }
701
1515
  if (sideEffects._resetRequested) {
@@ -703,17 +1517,17 @@ function useSideEffects({
703
1517
  const settingsPath = getUserSettingsPath();
704
1518
  if (deleteSettings(settingsPath)) {
705
1519
  addEntry(
706
- (0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)(`Deleted ${settingsPath}. Exiting...`))
1520
+ (0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(`Deleted ${settingsPath}. Exiting...`))
707
1521
  );
708
1522
  } else {
709
- addEntry((0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)("No user settings found.")));
1523
+ addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("No user settings found.")));
710
1524
  }
711
- setTimeout(() => exit(), EXIT_DELAY_MS);
1525
+ requestShutdown("other", "Reset settings restart");
712
1526
  return;
713
1527
  }
714
1528
  if (sideEffects._exitRequested) {
715
1529
  delete sideEffects._exitRequested;
716
- setTimeout(() => exit(), EXIT_DELAY_MS);
1530
+ requestShutdown("prompt_input_exit", "User requested exit");
717
1531
  return;
718
1532
  }
719
1533
  if (sideEffects._triggerPluginTUI) {
@@ -734,7 +1548,7 @@ function useSideEffects({
734
1548
  return;
735
1549
  }
736
1550
  },
737
- [interactiveSession, baseHandleSubmit, addEntry, exit, setSessionName]
1551
+ [interactiveSession, baseHandleSubmit, addEntry, requestShutdown, setSessionName]
738
1552
  );
739
1553
  const handleModelConfirm = (0, import_react4.useCallback)(
740
1554
  (index) => {
@@ -746,40 +1560,101 @@ function useSideEffects({
746
1560
  const settingsPath = getUserSettingsPath();
747
1561
  updateModelInSettings(settingsPath, modelId);
748
1562
  addEntry(
749
- (0, import_agent_core2.messageToHistoryEntry)(
750
- (0, import_agent_core2.createSystemMessage)(`Model changed to ${(0, import_agent_core2.getModelName)(modelId)}. Restarting...`)
1563
+ (0, import_agent_core4.messageToHistoryEntry)(
1564
+ (0, import_agent_core4.createSystemMessage)(`Model changed to ${(0, import_agent_core4.getModelName)(modelId)}. Restarting...`)
1565
+ )
1566
+ );
1567
+ requestShutdown("other", "Model change restart");
1568
+ } catch (err) {
1569
+ addEntry(
1570
+ (0, import_agent_core4.messageToHistoryEntry)(
1571
+ (0, import_agent_core4.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
1572
+ )
1573
+ );
1574
+ }
1575
+ } else {
1576
+ addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Model change cancelled.")));
1577
+ }
1578
+ },
1579
+ [addEntry, requestShutdown]
1580
+ );
1581
+ const handleProviderConfirm = (0, import_react4.useCallback)(
1582
+ (index) => {
1583
+ const profile = pendingProviderProfileRef.current;
1584
+ setPendingProviderProfile(null);
1585
+ pendingProviderProfileRef.current = null;
1586
+ if (index === 0 && profile) {
1587
+ try {
1588
+ const settingsPath = getUserSettingsPath();
1589
+ applyProviderSwitch(settingsPath, profile, {
1590
+ knownProviders: readMergedProviderSettings(cwd).providers
1591
+ });
1592
+ addEntry(
1593
+ (0, import_agent_core4.messageToHistoryEntry)(
1594
+ (0, import_agent_core4.createSystemMessage)(`Provider changed to ${profile}. Restarting...`)
751
1595
  )
752
1596
  );
753
- setTimeout(() => exit(), EXIT_DELAY_MS);
1597
+ requestShutdown("other", "Provider change restart");
754
1598
  } catch (err) {
755
1599
  addEntry(
756
- (0, import_agent_core2.messageToHistoryEntry)(
757
- (0, import_agent_core2.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
1600
+ (0, import_agent_core4.messageToHistoryEntry)(
1601
+ (0, import_agent_core4.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
758
1602
  )
759
1603
  );
760
1604
  }
761
1605
  } else {
762
- addEntry((0, import_agent_core2.messageToHistoryEntry)((0, import_agent_core2.createSystemMessage)("Model change cancelled.")));
1606
+ addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Provider change cancelled.")));
1607
+ }
1608
+ },
1609
+ [cwd, addEntry, requestShutdown]
1610
+ );
1611
+ const handleProviderSetupSubmit = (0, import_react4.useCallback)(
1612
+ (input) => {
1613
+ setPendingProviderSetupType(null);
1614
+ try {
1615
+ const settingsPath = getUserSettingsPath();
1616
+ applyProviderConfiguration(settingsPath, input, { providerDefinitions });
1617
+ addEntry(
1618
+ (0, import_agent_core4.messageToHistoryEntry)(
1619
+ (0, import_agent_core4.createSystemMessage)(`Provider ${input.profile} configured. Restarting...`)
1620
+ )
1621
+ );
1622
+ requestShutdown("other", "Provider setup restart");
1623
+ } catch (err) {
1624
+ addEntry(
1625
+ (0, import_agent_core4.messageToHistoryEntry)(
1626
+ (0, import_agent_core4.createSystemMessage)(`Failed: ${err instanceof Error ? err.message : String(err)}`)
1627
+ )
1628
+ );
763
1629
  }
764
1630
  },
765
- [addEntry, exit]
1631
+ [addEntry, requestShutdown, providerDefinitions]
766
1632
  );
1633
+ const handleProviderSetupCancel = (0, import_react4.useCallback)(() => {
1634
+ setPendingProviderSetupType(null);
1635
+ addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Provider setup cancelled.")));
1636
+ }, [addEntry]);
767
1637
  return {
768
1638
  handleSubmit,
769
1639
  pendingModelId,
1640
+ pendingProviderProfile,
1641
+ pendingProviderSetupType,
770
1642
  showPluginTUI,
771
1643
  showSessionPicker,
772
1644
  setPendingModelId,
773
1645
  setShowPluginTUI,
774
1646
  setShowSessionPicker,
775
- handleModelConfirm
1647
+ handleModelConfirm,
1648
+ handleProviderConfirm,
1649
+ handleProviderSetupSubmit,
1650
+ handleProviderSetupCancel
776
1651
  };
777
1652
  }
778
1653
 
779
1654
  // src/ui/MessageList.tsx
780
1655
  var import_react5 = __toESM(require("react"), 1);
781
1656
  var import_ink3 = require("ink");
782
- var import_agent_core3 = require("@robota-sdk/agent-core");
1657
+ var import_agent_core5 = require("@robota-sdk/agent-core");
783
1658
 
784
1659
  // src/ui/render-markdown.ts
785
1660
  var import_marked = require("marked");
@@ -865,7 +1740,7 @@ function RoleLabel({ role }) {
865
1740
  }
866
1741
  }
867
1742
  function ToolMessage({ message }) {
868
- if (!(0, import_agent_core3.isToolMessage)(message)) {
1743
+ if (!(0, import_agent_core5.isToolMessage)(message)) {
869
1744
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
870
1745
  }
871
1746
  const toolName = message.name;
@@ -928,7 +1803,7 @@ function ToolMessage({ message }) {
928
1803
  var MessageItem = import_react5.default.memo(function MessageItem2({
929
1804
  message
930
1805
  }) {
931
- if ((0, import_agent_core3.isToolMessage)(message)) {
1806
+ if ((0, import_agent_core5.isToolMessage)(message)) {
932
1807
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
933
1808
  }
934
1809
  const content = message.content ?? "";
@@ -936,7 +1811,7 @@ var MessageItem = import_react5.default.memo(function MessageItem2({
936
1811
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink3.Box, { flexDirection: "column", marginBottom: 1, children: [
937
1812
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
938
1813
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Text, { children: " " }),
939
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Text, { wrap: "wrap", children: (0, import_agent_core3.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
1814
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink3.Text, { wrap: "wrap", children: (0, import_agent_core5.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
940
1815
  ] });
941
1816
  });
942
1817
  function ToolSummaryEntry({ entry }) {
@@ -985,7 +1860,7 @@ function MessageList({ history }) {
985
1860
 
986
1861
  // src/ui/StatusBar.tsx
987
1862
  var import_ink4 = require("ink");
988
- var import_agent_core4 = require("@robota-sdk/agent-core");
1863
+ var import_agent_core6 = require("@robota-sdk/agent-core");
989
1864
  var import_jsx_runtime3 = require("react/jsx-runtime");
990
1865
  var CONTEXT_YELLOW_THRESHOLD = 70;
991
1866
  var CONTEXT_RED_THRESHOLD = 90;
@@ -1030,9 +1905,9 @@ function StatusBar({
1030
1905
  "Context: ",
1031
1906
  Math.round(contextPercentage),
1032
1907
  "% (",
1033
- (0, import_agent_core4.formatTokenCount)(contextUsedTokens),
1908
+ (0, import_agent_core6.formatTokenCount)(contextUsedTokens),
1034
1909
  "/",
1035
- (0, import_agent_core4.formatTokenCount)(contextMaxTokens),
1910
+ (0, import_agent_core6.formatTokenCount)(contextMaxTokens),
1036
1911
  ")"
1037
1912
  ] })
1038
1913
  ] }),
@@ -1056,39 +1931,183 @@ var import_ink8 = require("ink");
1056
1931
  var import_react6 = require("react");
1057
1932
  var import_ink5 = require("ink");
1058
1933
  var import_chalk = __toESM(require("chalk"), 1);
1934
+
1935
+ // src/ui/flows/cjk-text-input-flow.ts
1059
1936
  var import_string_width = __toESM(require("string-width"), 1);
1060
- var import_jsx_runtime4 = require("react/jsx-runtime");
1061
1937
  var PASTE_START = "[200~";
1062
1938
  var PASTE_END = "[201~";
1939
+ var LAST_ASCII_CONTROL_CODE = 31;
1940
+ var DELETE_CONTROL_CODE = 127;
1941
+ function createCjkTextInputFlowState(value) {
1942
+ return { value, cursor: value.length, isPasting: false, pasteBuffer: "" };
1943
+ }
1944
+ function syncCjkTextInputFlowState(state, value, cursorHint) {
1945
+ if (value === state.value) {
1946
+ return state;
1947
+ }
1948
+ return {
1949
+ ...state,
1950
+ value,
1951
+ cursor: cursorHint != null ? Math.min(cursorHint, value.length) : value.length
1952
+ };
1953
+ }
1954
+ function applyCjkTextInput(state, input, key, options) {
1955
+ const pasteResult = applyPasteBoundaryInput(state, input, options);
1956
+ if (pasteResult !== void 0) return pasteResult;
1957
+ const controlResult = applyControlInput(state, input, key, options);
1958
+ if (controlResult !== void 0) return controlResult;
1959
+ const cursorResult = applyCursorInput(state, key, options.availableWidth);
1960
+ if (cursorResult !== void 0) return cursorResult;
1961
+ return insertPrintableInput(state, input);
1962
+ }
1963
+ function applyPasteBoundaryInput(state, input, options) {
1964
+ if (input === PASTE_START || input.startsWith(PASTE_START)) {
1965
+ return startBracketedPaste(state, input);
1966
+ }
1967
+ if (state.isPasting) {
1968
+ return continueBracketedPaste(state, input, options);
1969
+ }
1970
+ return void 0;
1971
+ }
1972
+ function applyControlInput(state, input, key, options) {
1973
+ if (key.ctrl === true && input === "c" || key.tab === true) {
1974
+ return { state, effect: { type: "none" } };
1975
+ }
1976
+ if (key.return === true) {
1977
+ return { state, effect: { type: "submit", value: state.value } };
1978
+ }
1979
+ if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && options.canPaste) {
1980
+ return {
1981
+ state,
1982
+ effect: { type: "paste", text: input.replace(/\r\n?/g, "\n"), cursor: state.cursor }
1983
+ };
1984
+ }
1985
+ return void 0;
1986
+ }
1987
+ function applyCursorInput(state, key, availableWidth) {
1988
+ if (key.upArrow === true || key.downArrow === true) {
1989
+ return moveCursorVertically(state, key.upArrow === true ? "up" : "down", availableWidth);
1990
+ }
1991
+ if (key.leftArrow === true) {
1992
+ return moveCursorHorizontally(state, "left");
1993
+ }
1994
+ if (key.rightArrow === true) {
1995
+ return moveCursorHorizontally(state, "right");
1996
+ }
1997
+ if (key.backspace === true || key.delete === true) {
1998
+ return deleteBeforeCursor(state);
1999
+ }
2000
+ return void 0;
2001
+ }
1063
2002
  function filterPrintable(input) {
1064
2003
  if (!input || input.length === 0) return "";
1065
- return input.replace(/[\x00-\x1f\x7f]/g, "");
2004
+ let output = "";
2005
+ for (const char of input) {
2006
+ const code = char.charCodeAt(0);
2007
+ if (code > LAST_ASCII_CONTROL_CODE && code !== DELETE_CONTROL_CODE) {
2008
+ output += char;
2009
+ }
2010
+ }
2011
+ return output;
1066
2012
  }
1067
2013
  function insertAtCursor(value, cursor, input) {
1068
2014
  const next = value.slice(0, cursor) + input + value.slice(cursor);
1069
2015
  return { value: next, cursor: cursor + input.length };
1070
2016
  }
1071
- function displayOffset(chars, charIndex, width) {
1072
- let offset = 0;
1073
- for (let i = 0; i < charIndex && i < chars.length; i++) {
1074
- const w = (0, import_string_width.default)(chars[i]);
1075
- const col = offset % width;
1076
- if (col + w > width) offset += width - col;
1077
- offset += w;
2017
+ function displayOffset(chars, charIndex, width) {
2018
+ let offset = 0;
2019
+ for (let i = 0; i < charIndex && i < chars.length; i++) {
2020
+ const w = (0, import_string_width.default)(chars[i]);
2021
+ const col = offset % width;
2022
+ if (col + w > width) offset += width - col;
2023
+ offset += w;
2024
+ }
2025
+ return offset;
2026
+ }
2027
+ function charIndexAtDisplayOffset(chars, target, width) {
2028
+ let offset = 0;
2029
+ for (let i = 0; i < chars.length; i++) {
2030
+ if (offset >= target) return i;
2031
+ const w = (0, import_string_width.default)(chars[i]);
2032
+ const col = offset % width;
2033
+ if (col + w > width) offset += width - col;
2034
+ offset += w;
2035
+ }
2036
+ return chars.length;
2037
+ }
2038
+ function startBracketedPaste(state, input) {
2039
+ return {
2040
+ state: { ...state, isPasting: true, pasteBuffer: input.slice(PASTE_START.length) },
2041
+ effect: { type: "none" }
2042
+ };
2043
+ }
2044
+ function continueBracketedPaste(state, input, options) {
2045
+ if (input !== PASTE_END && !input.includes(PASTE_END)) {
2046
+ return {
2047
+ state: { ...state, pasteBuffer: state.pasteBuffer + input },
2048
+ effect: { type: "none" }
2049
+ };
2050
+ }
2051
+ const beforeMarker = input.split(PASTE_END)[0] ?? "";
2052
+ const text = (state.pasteBuffer + beforeMarker).replace(/\r\n?/g, "\n");
2053
+ const nextState = { ...state, isPasting: false, pasteBuffer: "" };
2054
+ if (text.length === 0) {
2055
+ return { state: nextState, effect: { type: "none" } };
2056
+ }
2057
+ if (text.includes("\n") && options.canPaste) {
2058
+ return { state: nextState, effect: { type: "paste", text, cursor: state.cursor } };
2059
+ }
2060
+ return insertPrintableInput(nextState, text);
2061
+ }
2062
+ function moveCursorVertically(state, direction, availableWidth) {
2063
+ if (!availableWidth || availableWidth <= 0) {
2064
+ return { state, effect: { type: "none" } };
2065
+ }
2066
+ const chars = [...state.value];
2067
+ const offset = displayOffset(chars, state.cursor, availableWidth);
2068
+ const target = direction === "up" ? offset - availableWidth : offset + availableWidth;
2069
+ if (target < 0) {
2070
+ return { state, effect: { type: "none" } };
2071
+ }
2072
+ const cursor = charIndexAtDisplayOffset(chars, target, availableWidth);
2073
+ if (cursor === state.cursor) {
2074
+ return { state, effect: { type: "none" } };
2075
+ }
2076
+ return { state: { ...state, cursor }, effect: { type: "render" } };
2077
+ }
2078
+ function moveCursorHorizontally(state, direction) {
2079
+ if (direction === "left" && state.cursor > 0) {
2080
+ return { state: { ...state, cursor: state.cursor - 1 }, effect: { type: "render" } };
2081
+ }
2082
+ if (direction === "right" && state.cursor < state.value.length) {
2083
+ return { state: { ...state, cursor: state.cursor + 1 }, effect: { type: "render" } };
2084
+ }
2085
+ return { state, effect: { type: "none" } };
2086
+ }
2087
+ function deleteBeforeCursor(state) {
2088
+ if (state.cursor === 0) {
2089
+ return { state, effect: { type: "none" } };
1078
2090
  }
1079
- return offset;
2091
+ const value = state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor);
2092
+ return {
2093
+ state: { ...state, value, cursor: state.cursor - 1 },
2094
+ effect: { type: "change", value }
2095
+ };
1080
2096
  }
1081
- function charIndexAtDisplayOffset(chars, target, width) {
1082
- let offset = 0;
1083
- for (let i = 0; i < chars.length; i++) {
1084
- if (offset >= target) return i;
1085
- const w = (0, import_string_width.default)(chars[i]);
1086
- const col = offset % width;
1087
- if (col + w > width) offset += width - col;
1088
- offset += w;
2097
+ function insertPrintableInput(state, input) {
2098
+ const printable = filterPrintable(input);
2099
+ if (printable.length === 0) {
2100
+ return { state, effect: { type: "none" } };
1089
2101
  }
1090
- return chars.length;
2102
+ const result = insertAtCursor(state.value, state.cursor, printable);
2103
+ return {
2104
+ state: { ...state, value: result.value, cursor: result.cursor },
2105
+ effect: { type: "change", value: result.value }
2106
+ };
1091
2107
  }
2108
+
2109
+ // src/ui/CjkTextInput.tsx
2110
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1092
2111
  function CjkTextInput({
1093
2112
  value,
1094
2113
  onChange,
@@ -1100,113 +2119,40 @@ function CjkTextInput({
1100
2119
  availableWidth,
1101
2120
  cursorHint = null
1102
2121
  }) {
1103
- const valueRef = (0, import_react6.useRef)(value);
1104
- const cursorRef = (0, import_react6.useRef)(value.length);
2122
+ const stateRef = (0, import_react6.useRef)(createCjkTextInputFlowState(value));
1105
2123
  const [, forceRender] = (0, import_react6.useState)(0);
1106
- const isPastingRef = (0, import_react6.useRef)(false);
1107
- const pasteBufferRef = (0, import_react6.useRef)("");
1108
- if (value !== valueRef.current) {
1109
- valueRef.current = value;
1110
- cursorRef.current = cursorHint != null ? Math.min(cursorHint, value.length) : value.length;
1111
- }
2124
+ stateRef.current = syncCjkTextInputFlowState(stateRef.current, value, cursorHint);
1112
2125
  (0, import_ink5.useInput)(
1113
2126
  (input, key) => {
1114
2127
  try {
1115
- if (input === PASTE_START || input.startsWith(PASTE_START)) {
1116
- isPastingRef.current = true;
1117
- const afterMarker = input.slice(PASTE_START.length);
1118
- if (afterMarker.length > 0) {
1119
- pasteBufferRef.current += afterMarker;
1120
- }
1121
- return;
1122
- }
1123
- if (isPastingRef.current) {
1124
- if (input === PASTE_END || input.includes(PASTE_END)) {
1125
- const beforeMarker = input.split(PASTE_END)[0] ?? "";
1126
- pasteBufferRef.current += beforeMarker;
1127
- const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
1128
- pasteBufferRef.current = "";
1129
- isPastingRef.current = false;
1130
- if (text.length > 0) {
1131
- if (text.includes("\n") && onPaste) {
1132
- onPaste(text, cursorRef.current);
1133
- } else {
1134
- const printable2 = filterPrintable(text);
1135
- if (printable2.length > 0) {
1136
- const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
1137
- cursorRef.current = result2.cursor;
1138
- valueRef.current = result2.value;
1139
- onChange(result2.value);
1140
- }
1141
- }
1142
- }
1143
- } else {
1144
- pasteBufferRef.current += input;
1145
- }
1146
- return;
1147
- }
1148
- if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
1149
- return;
1150
- }
1151
- if (key.upArrow || key.downArrow) {
1152
- if (availableWidth && availableWidth > 0) {
1153
- const chars = [...valueRef.current];
1154
- const offset = displayOffset(chars, cursorRef.current, availableWidth);
1155
- const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
1156
- if (target >= 0) {
1157
- const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
1158
- if (newCursor !== cursorRef.current) {
1159
- cursorRef.current = newCursor;
1160
- forceRender((n) => n + 1);
1161
- }
1162
- }
1163
- }
1164
- return;
1165
- }
1166
- if (key.return) {
1167
- onSubmit?.(valueRef.current);
1168
- return;
1169
- }
1170
- if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
1171
- onPaste(input.replace(/\r\n?/g, "\n"), cursorRef.current);
1172
- return;
1173
- }
1174
- if (key.leftArrow) {
1175
- if (cursorRef.current > 0) {
1176
- cursorRef.current -= 1;
1177
- forceRender((n) => n + 1);
1178
- }
1179
- return;
1180
- }
1181
- if (key.rightArrow) {
1182
- if (cursorRef.current < valueRef.current.length) {
1183
- cursorRef.current += 1;
1184
- forceRender((n) => n + 1);
1185
- }
1186
- return;
1187
- }
1188
- if (key.backspace || key.delete) {
1189
- if (cursorRef.current > 0) {
1190
- const v = valueRef.current;
1191
- const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
1192
- cursorRef.current -= 1;
1193
- valueRef.current = next;
1194
- onChange(next);
1195
- }
1196
- return;
1197
- }
1198
- const printable = filterPrintable(input);
1199
- if (printable.length === 0) return;
1200
- const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
1201
- cursorRef.current = result.cursor;
1202
- valueRef.current = result.value;
1203
- onChange(result.value);
2128
+ const result = applyCjkTextInput(stateRef.current, input, key, {
2129
+ availableWidth,
2130
+ canPaste: onPaste !== void 0
2131
+ });
2132
+ stateRef.current = result.state;
2133
+ applyCjkTextInputEffect(result.effect, onChange, onSubmit, onPaste, forceRender);
1204
2134
  } catch {
1205
2135
  }
1206
2136
  },
1207
2137
  { isActive: focus }
1208
2138
  );
1209
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
2139
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink5.Text, { children: renderWithCursor(
2140
+ stateRef.current.value,
2141
+ stateRef.current.cursor,
2142
+ placeholder,
2143
+ showCursor && focus
2144
+ ) });
2145
+ }
2146
+ function applyCjkTextInputEffect(effect, onChange, onSubmit, onPaste, forceRender) {
2147
+ if (effect.type === "change") {
2148
+ onChange(effect.value);
2149
+ } else if (effect.type === "submit") {
2150
+ onSubmit?.(effect.value);
2151
+ } else if (effect.type === "paste") {
2152
+ onPaste?.(effect.text, effect.cursor);
2153
+ } else if (effect.type === "render") {
2154
+ forceRender((n) => n + 1);
2155
+ }
1210
2156
  }
1211
2157
  function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
1212
2158
  if (!showCursor) {
@@ -1354,6 +2300,61 @@ function useAutocomplete(value, registry) {
1354
2300
  };
1355
2301
  }
1356
2302
 
2303
+ // src/ui/flows/input-area-flow.ts
2304
+ function getAutocompletePopupAction(key) {
2305
+ if (key.upArrow === true) return "previous";
2306
+ if (key.downArrow === true) return "next";
2307
+ if (key.escape === true) return "close";
2308
+ if (key.tab === true) return "complete";
2309
+ return void 0;
2310
+ }
2311
+ function getPendingPromptInputAction(key) {
2312
+ if (key.backspace === true || key.delete === true) {
2313
+ return "cancelQueue";
2314
+ }
2315
+ return void 0;
2316
+ }
2317
+ function moveAutocompleteSelection(selectedIndex, commandCount, direction) {
2318
+ if (commandCount === 0) return 0;
2319
+ if (direction === "previous") {
2320
+ return selectedIndex > 0 ? selectedIndex - 1 : commandCount - 1;
2321
+ }
2322
+ return selectedIndex < commandCount - 1 ? selectedIndex + 1 : 0;
2323
+ }
2324
+ function resolveTabCompletion(value, command) {
2325
+ const parsed = parseSlashInput(value);
2326
+ if (parsed.parentCommand) {
2327
+ return { type: "insert", value: `/${parsed.parentCommand} ${command.name} ` };
2328
+ }
2329
+ if (command.subcommands && command.subcommands.length > 0) {
2330
+ return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
2331
+ }
2332
+ return { type: "insert", value: `/${command.name} ` };
2333
+ }
2334
+ function resolveEnterCommandSelection(value, command) {
2335
+ const parsed = parseSlashInput(value);
2336
+ if (parsed.parentCommand) {
2337
+ return { type: "submit", value: `/${parsed.parentCommand} ${command.name}` };
2338
+ }
2339
+ if (command.subcommands && command.subcommands.length > 0) {
2340
+ return { type: "insert", value: `/${command.name} `, selectedIndex: 0 };
2341
+ }
2342
+ return { type: "submit", value: `/${command.name}` };
2343
+ }
2344
+ function createPasteLabelChange(value, cursorPosition, pasteId, text) {
2345
+ const lineCount = text.split("\n").length;
2346
+ const label = `[Pasted text #${pasteId} +${lineCount} lines]`;
2347
+ return {
2348
+ value: value.slice(0, cursorPosition) + label + value.slice(cursorPosition),
2349
+ cursorHint: cursorPosition + label.length,
2350
+ label,
2351
+ lineCount
2352
+ };
2353
+ }
2354
+ function shouldSubmitInput(text) {
2355
+ return text.trim().length > 0;
2356
+ }
2357
+
1357
2358
  // src/ui/InputArea.tsx
1358
2359
  var import_jsx_runtime7 = require("react/jsx-runtime");
1359
2360
  var BORDER_HORIZONTAL = 2;
@@ -1388,56 +2389,47 @@ function InputArea({
1388
2389
  pasteIdRef.current += 1;
1389
2390
  const id = pasteIdRef.current;
1390
2391
  pasteStore.current.set(id, text);
1391
- const lineCount = text.split("\n").length;
1392
- const label = `[Pasted text #${id} +${lineCount} lines]`;
1393
- const newCursorPos = cursorPosition + label.length;
1394
- setCursorHint(newCursorPos);
1395
- setValue((prev) => prev.slice(0, cursorPosition) + label + prev.slice(cursorPosition));
2392
+ setValue((prev) => {
2393
+ const change = createPasteLabelChange(prev, cursorPosition, id, text);
2394
+ setCursorHint(change.cursorHint);
2395
+ return change.value;
2396
+ });
1396
2397
  }, []);
1397
2398
  const tabCompleteCommand = (0, import_react9.useCallback)(
1398
2399
  (cmd) => {
1399
- const parsed = parseSlashInput(value);
1400
- if (parsed.parentCommand) {
1401
- setValue(`/${parsed.parentCommand} ${cmd.name} `);
1402
- return;
1403
- }
1404
- if (cmd.subcommands && cmd.subcommands.length > 0) {
1405
- setValue(`/${cmd.name} `);
1406
- setSelectedIndex(0);
1407
- return;
2400
+ const result = resolveTabCompletion(value, cmd);
2401
+ if (result.type === "insert") {
2402
+ setValue(result.value);
2403
+ if (result.selectedIndex !== void 0) {
2404
+ setSelectedIndex(result.selectedIndex);
2405
+ }
1408
2406
  }
1409
- setValue(`/${cmd.name} `);
1410
2407
  },
1411
2408
  [value, setSelectedIndex]
1412
2409
  );
1413
2410
  const enterSelectCommand = (0, import_react9.useCallback)(
1414
2411
  (cmd) => {
1415
- const parsed = parseSlashInput(value);
1416
- if (parsed.parentCommand) {
1417
- const fullCommand = `/${parsed.parentCommand} ${cmd.name}`;
1418
- setValue("");
1419
- onSubmit(fullCommand);
1420
- return;
1421
- }
1422
- if (cmd.subcommands && cmd.subcommands.length > 0) {
1423
- setValue(`/${cmd.name} `);
1424
- setSelectedIndex(0);
2412
+ const result = resolveEnterCommandSelection(value, cmd);
2413
+ if (result.type === "insert") {
2414
+ setValue(result.value);
2415
+ if (result.selectedIndex !== void 0) {
2416
+ setSelectedIndex(result.selectedIndex);
2417
+ }
1425
2418
  return;
1426
2419
  }
1427
2420
  setValue("");
1428
- onSubmit(`/${cmd.name}`);
2421
+ onSubmit(result.value);
1429
2422
  },
1430
2423
  [value, onSubmit, setSelectedIndex]
1431
2424
  );
1432
2425
  const handleSubmit = (0, import_react9.useCallback)(
1433
2426
  (text) => {
1434
- const trimmed = text.trim();
1435
- if (trimmed.length === 0) return;
2427
+ if (!shouldSubmitInput(text)) return;
1436
2428
  if (showPopup && filteredCommands[selectedIndex]) {
1437
2429
  enterSelectCommand(filteredCommands[selectedIndex]);
1438
2430
  return;
1439
2431
  }
1440
- const expanded = expandPasteLabels(trimmed, pasteStore.current);
2432
+ const expanded = expandPasteLabels(text.trim(), pasteStore.current);
1441
2433
  setValue("");
1442
2434
  pasteStore.current.clear();
1443
2435
  pasteIdRef.current = 0;
@@ -1448,13 +2440,14 @@ function InputArea({
1448
2440
  (0, import_ink8.useInput)(
1449
2441
  (_input, key) => {
1450
2442
  if (!showPopup) return;
1451
- if (key.upArrow) {
1452
- setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredCommands.length - 1);
1453
- } else if (key.downArrow) {
1454
- setSelectedIndex((prev) => prev < filteredCommands.length - 1 ? prev + 1 : 0);
1455
- } else if (key.escape) {
2443
+ const action = getAutocompletePopupAction(key);
2444
+ if (action === "previous" || action === "next") {
2445
+ setSelectedIndex(
2446
+ (prev) => moveAutocompleteSelection(prev, filteredCommands.length, action)
2447
+ );
2448
+ } else if (action === "close") {
1456
2449
  setShowPopup(false);
1457
- } else if (key.tab) {
2450
+ } else if (action === "complete") {
1458
2451
  const cmd = filteredCommands[selectedIndex];
1459
2452
  if (cmd) tabCompleteCommand(cmd);
1460
2453
  }
@@ -1463,7 +2456,7 @@ function InputArea({
1463
2456
  );
1464
2457
  (0, import_ink8.useInput)(
1465
2458
  (_input, key) => {
1466
- if ((key.backspace || key.delete) && pendingPrompt) {
2459
+ if (getPendingPromptInputAction(key) === "cancelQueue" && pendingPrompt) {
1467
2460
  onCancelQueue?.();
1468
2461
  }
1469
2462
  },
@@ -1525,113 +2518,409 @@ function InputArea({
1525
2518
  // src/ui/ConfirmPrompt.tsx
1526
2519
  var import_react10 = require("react");
1527
2520
  var import_ink9 = require("ink");
2521
+
2522
+ // src/ui/flows/selection-flow.ts
2523
+ function createSelectionFlowState() {
2524
+ return { selectedIndex: 0, scrollOffset: 0, resolved: false };
2525
+ }
2526
+ function getVerticalSelectionInputAction(key) {
2527
+ if (key.escape === true) return "cancel";
2528
+ if (key.upArrow === true) return "previous";
2529
+ if (key.downArrow === true) return "next";
2530
+ if (key.return === true) return "select";
2531
+ return void 0;
2532
+ }
2533
+ function getDirectionalSelectionInputAction(key) {
2534
+ if (key.escape === true) return "cancel";
2535
+ if (key.leftArrow === true || key.upArrow === true) return "previous";
2536
+ if (key.rightArrow === true || key.downArrow === true) return "next";
2537
+ if (key.return === true) return "select";
2538
+ return void 0;
2539
+ }
2540
+ function applySelectionInput(state, action, options) {
2541
+ if (state.resolved) {
2542
+ return { state, effect: { type: "none" } };
2543
+ }
2544
+ if (action === "cancel") {
2545
+ return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
2546
+ }
2547
+ if (options.enabled === false || options.itemCount === 0) {
2548
+ return { state, effect: { type: "none" } };
2549
+ }
2550
+ if (action === "select") {
2551
+ const index = clampIndex(state.selectedIndex, options.itemCount);
2552
+ return {
2553
+ state: { ...state, selectedIndex: index, resolved: true },
2554
+ effect: { type: "select", index }
2555
+ };
2556
+ }
2557
+ const selectedIndex = moveSelection(state.selectedIndex, action, options);
2558
+ const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
2559
+ return { state: { ...state, selectedIndex, scrollOffset }, effect: { type: "none" } };
2560
+ }
2561
+ function normalizeSelectionState(state, options) {
2562
+ if (options.itemCount === 0) {
2563
+ return { ...state, selectedIndex: 0, scrollOffset: 0 };
2564
+ }
2565
+ const selectedIndex = clampIndex(state.selectedIndex, options.itemCount);
2566
+ const scrollOffset = resolveScrollOffset(selectedIndex, state.scrollOffset, options);
2567
+ if (selectedIndex === state.selectedIndex && scrollOffset === state.scrollOffset) {
2568
+ return state;
2569
+ }
2570
+ return {
2571
+ ...state,
2572
+ selectedIndex,
2573
+ scrollOffset
2574
+ };
2575
+ }
2576
+ function moveSelection(selectedIndex, action, options) {
2577
+ if (action === "previous") {
2578
+ if (options.wrap === true && selectedIndex === 0) return options.itemCount - 1;
2579
+ return Math.max(0, selectedIndex - 1);
2580
+ }
2581
+ if (options.wrap === true && selectedIndex === options.itemCount - 1) return 0;
2582
+ return Math.min(options.itemCount - 1, selectedIndex + 1);
2583
+ }
2584
+ function resolveScrollOffset(selectedIndex, scrollOffset, options) {
2585
+ const maxVisible = options.maxVisible ?? options.itemCount;
2586
+ if (maxVisible <= 0) return 0;
2587
+ if (selectedIndex < scrollOffset) return selectedIndex;
2588
+ if (selectedIndex >= scrollOffset + maxVisible) return selectedIndex - maxVisible + 1;
2589
+ return Math.max(0, scrollOffset);
2590
+ }
2591
+ function clampIndex(index, itemCount) {
2592
+ return Math.min(Math.max(index, 0), itemCount - 1);
2593
+ }
2594
+
2595
+ // src/ui/flows/confirm-prompt-flow.ts
2596
+ function getConfirmPromptInputAction(input, key, optionCount) {
2597
+ const action = getDirectionalSelectionInputAction({ ...key, escape: false });
2598
+ if (action !== void 0) {
2599
+ return action;
2600
+ }
2601
+ if (optionCount === 2 && input === "y") {
2602
+ return { type: "shortcut", index: 0 };
2603
+ }
2604
+ if (optionCount === 2 && input === "n") {
2605
+ return { type: "shortcut", index: 1 };
2606
+ }
2607
+ return void 0;
2608
+ }
2609
+ function applyConfirmPromptInput(state, action, optionCount) {
2610
+ if (state.resolved) {
2611
+ return { state, effect: { type: "none" } };
2612
+ }
2613
+ if (typeof action !== "string") {
2614
+ return {
2615
+ state: { ...state, selectedIndex: action.index, resolved: true },
2616
+ effect: { type: "select", index: action.index }
2617
+ };
2618
+ }
2619
+ return applySelectionInput(state, action, { itemCount: optionCount });
2620
+ }
2621
+
2622
+ // src/ui/ConfirmPrompt.tsx
1528
2623
  var import_jsx_runtime8 = require("react/jsx-runtime");
1529
2624
  function ConfirmPrompt({
1530
2625
  message,
1531
2626
  options = ["Yes", "No"],
1532
2627
  onSelect
1533
2628
  }) {
1534
- const [selected, setSelected] = (0, import_react10.useState)(0);
1535
- const resolvedRef = (0, import_react10.useRef)(false);
1536
- const doSelect = (0, import_react10.useCallback)(
1537
- (index) => {
1538
- if (resolvedRef.current) return;
1539
- resolvedRef.current = true;
1540
- onSelect(index);
2629
+ const [state, setState] = (0, import_react10.useState)(() => createSelectionFlowState());
2630
+ const stateRef = (0, import_react10.useRef)(state);
2631
+ const applyAction = (0, import_react10.useCallback)(
2632
+ (action) => {
2633
+ const result = applyConfirmPromptInput(stateRef.current, action, options.length);
2634
+ stateRef.current = result.state;
2635
+ setState(result.state);
2636
+ if (result.effect.type === "select") {
2637
+ onSelect(result.effect.index);
2638
+ }
1541
2639
  },
1542
- [onSelect]
2640
+ [onSelect, options.length]
1543
2641
  );
1544
2642
  (0, import_ink9.useInput)((input, key) => {
1545
- if (resolvedRef.current) return;
1546
- if (key.leftArrow || key.upArrow) {
1547
- setSelected((prev) => prev > 0 ? prev - 1 : prev);
1548
- } else if (key.rightArrow || key.downArrow) {
1549
- setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
1550
- } else if (key.return) {
1551
- doSelect(selected);
1552
- } else if (input === "y" && options.length === 2) {
1553
- doSelect(0);
1554
- } else if (input === "n" && options.length === 2) {
1555
- doSelect(1);
2643
+ const action = getConfirmPromptInputAction(input, key, options.length);
2644
+ if (action !== void 0) {
2645
+ applyAction(action);
1556
2646
  }
1557
2647
  });
1558
2648
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1559
2649
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Text, { color: "yellow", children: message }),
1560
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1561
- i === selected ? "> " : " ",
1562
- opt
1563
- ] }) }, opt)) }),
2650
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2651
+ import_ink9.Text,
2652
+ {
2653
+ color: i === state.selectedIndex ? "cyan" : void 0,
2654
+ bold: i === state.selectedIndex,
2655
+ children: [
2656
+ i === state.selectedIndex ? "> " : " ",
2657
+ opt
2658
+ ]
2659
+ }
2660
+ ) }, opt)) }),
1564
2661
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink9.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
1565
2662
  ] });
1566
2663
  }
1567
2664
 
1568
- // src/ui/PermissionPrompt.tsx
1569
- var import_react11 = __toESM(require("react"), 1);
2665
+ // src/ui/ProviderSetupPrompt.tsx
2666
+ var import_react12 = require("react");
2667
+
2668
+ // src/ui/TextPrompt.tsx
2669
+ var import_react11 = require("react");
1570
2670
  var import_ink10 = require("ink");
2671
+
2672
+ // src/ui/flows/text-prompt-flow.ts
2673
+ function createTextPromptFlowState() {
2674
+ return { value: "", resolved: false };
2675
+ }
2676
+ function getTextPromptInputAction(input, key) {
2677
+ if (key.escape === true) {
2678
+ return { type: "cancel" };
2679
+ }
2680
+ if (key.return === true) {
2681
+ return { type: "submit" };
2682
+ }
2683
+ if (key.backspace === true || key.delete === true) {
2684
+ return { type: "delete" };
2685
+ }
2686
+ if (input && key.ctrl !== true && key.meta !== true) {
2687
+ return { type: "insert", value: input };
2688
+ }
2689
+ return void 0;
2690
+ }
2691
+ function applyTextPromptInput(state, action, options) {
2692
+ if (state.resolved) {
2693
+ return { state, effect: { type: "none" } };
2694
+ }
2695
+ if (action.type === "cancel") {
2696
+ return { state: { ...state, resolved: true }, effect: { type: "cancel" } };
2697
+ }
2698
+ if (action.type === "delete") {
2699
+ return {
2700
+ state: { ...state, value: state.value.slice(0, -1), error: void 0 },
2701
+ effect: { type: "none" }
2702
+ };
2703
+ }
2704
+ if (action.type === "insert") {
2705
+ return {
2706
+ state: { ...state, value: state.value + action.value, error: void 0 },
2707
+ effect: { type: "none" }
2708
+ };
2709
+ }
2710
+ return submitTextPromptValue(state, options);
2711
+ }
2712
+ function submitTextPromptValue(state, options) {
2713
+ const trimmed = state.value.trim();
2714
+ if (!trimmed && !options.allowEmpty) {
2715
+ const emptyError = options.validate?.(trimmed);
2716
+ return {
2717
+ state: emptyError ? { ...state, error: emptyError } : state,
2718
+ effect: { type: "none" }
2719
+ };
2720
+ }
2721
+ const error = options.validate?.(trimmed);
2722
+ if (error !== void 0) {
2723
+ return { state: { ...state, error }, effect: { type: "none" } };
2724
+ }
2725
+ return { state: { ...state, resolved: true }, effect: { type: "submit", value: trimmed } };
2726
+ }
2727
+
2728
+ // src/ui/TextPrompt.tsx
1571
2729
  var import_jsx_runtime9 = require("react/jsx-runtime");
1572
- var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
2730
+ function TextPrompt({
2731
+ title,
2732
+ placeholder,
2733
+ onSubmit,
2734
+ onCancel,
2735
+ validate,
2736
+ allowEmpty = false,
2737
+ masked = false
2738
+ }) {
2739
+ const [state, setState] = (0, import_react11.useState)(() => createTextPromptFlowState());
2740
+ const stateRef = (0, import_react11.useRef)(state);
2741
+ const applyAction = (0, import_react11.useCallback)(
2742
+ (action) => {
2743
+ const result = applyTextPromptInput(stateRef.current, action, { allowEmpty, validate });
2744
+ stateRef.current = result.state;
2745
+ setState(result.state);
2746
+ if (result.effect.type === "cancel") {
2747
+ onCancel();
2748
+ } else if (result.effect.type === "submit") {
2749
+ onSubmit(result.effect.value);
2750
+ }
2751
+ },
2752
+ [allowEmpty, validate, onCancel, onSubmit]
2753
+ );
2754
+ (0, import_ink10.useInput)((input, key) => {
2755
+ const action = getTextPromptInputAction(input, key);
2756
+ if (action !== void 0) {
2757
+ applyAction(action);
2758
+ }
2759
+ });
2760
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2761
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "yellow", bold: true, children: title }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Box, { marginTop: 1, children: [
2763
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "cyan", children: "> " }),
2764
+ state.value ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { children: masked ? "*".repeat(state.value.length) : state.value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { dimColor: true, children: placeholder }) : null,
2765
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "cyan", children: "\u2588" })
2766
+ ] }),
2767
+ state.error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "red", children: state.error }),
2768
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
2769
+ ] });
2770
+ }
2771
+
2772
+ // src/ui/ProviderSetupPrompt.tsx
2773
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2774
+ function ProviderSetupPrompt({
2775
+ type,
2776
+ providerDefinitions,
2777
+ onSubmit,
2778
+ onCancel
2779
+ }) {
2780
+ const [state, setState] = (0, import_react12.useState)(
2781
+ () => createProviderSetupFlow(type, providerDefinitions)
2782
+ );
2783
+ const step = getProviderSetupStep(state);
2784
+ const handleStepSubmit = (rawValue) => {
2785
+ const result = submitProviderSetupValue(state, rawValue);
2786
+ if (result.status === "next") {
2787
+ setState(result.state);
2788
+ return;
2789
+ }
2790
+ if (result.status === "complete") {
2791
+ onSubmit(result.input);
2792
+ }
2793
+ };
2794
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2795
+ TextPrompt,
2796
+ {
2797
+ title: step.title,
2798
+ placeholder: step.defaultValue,
2799
+ allowEmpty: step.defaultValue !== void 0,
2800
+ masked: step.masked,
2801
+ validate: (value) => validateProviderSetupValue(step, value),
2802
+ onSubmit: handleStepSubmit,
2803
+ onCancel
2804
+ },
2805
+ `${type}-${step.key}`
2806
+ );
2807
+ }
2808
+
2809
+ // src/ui/PermissionPrompt.tsx
2810
+ var import_react13 = __toESM(require("react"), 1);
2811
+ var import_ink11 = require("ink");
2812
+
2813
+ // src/ui/flows/permission-prompt-flow.ts
2814
+ var PERMISSION_PROMPT_OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
2815
+ function getPermissionPromptInputAction(input, key) {
2816
+ const action = getDirectionalSelectionInputAction({ ...key, escape: false });
2817
+ if (action !== void 0) {
2818
+ return action;
2819
+ }
2820
+ if (input === "y" || input === "1") {
2821
+ return { type: "shortcut", index: 0 };
2822
+ }
2823
+ if (input === "a" || input === "2") {
2824
+ return { type: "shortcut", index: 1 };
2825
+ }
2826
+ if (input === "n" || input === "d" || input === "3") {
2827
+ return { type: "shortcut", index: 2 };
2828
+ }
2829
+ return void 0;
2830
+ }
2831
+ function applyPermissionPromptInput(state, action) {
2832
+ if (state.resolved) {
2833
+ return { state, effect: { type: "none" } };
2834
+ }
2835
+ if (typeof action !== "string") {
2836
+ return resolvePermissionIndex(state, action.index);
2837
+ }
2838
+ const result = applySelectionInput(state, action, {
2839
+ itemCount: PERMISSION_PROMPT_OPTIONS.length
2840
+ });
2841
+ if (result.effect.type !== "select") {
2842
+ return { state: result.state, effect: { type: "none" } };
2843
+ }
2844
+ return {
2845
+ state: result.state,
2846
+ effect: { type: "resolve", decision: getPermissionDecision(result.effect.index) }
2847
+ };
2848
+ }
2849
+ function getPermissionDecision(index) {
2850
+ if (index === 0) return true;
2851
+ if (index === 1) return "allow-session";
2852
+ return false;
2853
+ }
2854
+ function resolvePermissionIndex(state, index) {
2855
+ return {
2856
+ state: { ...state, selectedIndex: index, resolved: true },
2857
+ effect: { type: "resolve", decision: getPermissionDecision(index) }
2858
+ };
2859
+ }
2860
+
2861
+ // src/ui/PermissionPrompt.tsx
2862
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1573
2863
  function formatArgs(args) {
1574
2864
  const entries = Object.entries(args);
1575
2865
  if (entries.length === 0) return "(no arguments)";
1576
2866
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
1577
2867
  }
1578
2868
  function PermissionPrompt({ request }) {
1579
- const [selected, setSelected] = import_react11.default.useState(0);
1580
- const resolvedRef = import_react11.default.useRef(false);
1581
- const prevRequestRef = import_react11.default.useRef(request);
2869
+ const [state, setState] = import_react13.default.useState(() => createSelectionFlowState());
2870
+ const stateRef = import_react13.default.useRef(state);
2871
+ const prevRequestRef = import_react13.default.useRef(request);
1582
2872
  if (prevRequestRef.current !== request) {
1583
2873
  prevRequestRef.current = request;
1584
- resolvedRef.current = false;
1585
- setSelected(0);
2874
+ const nextState = createSelectionFlowState();
2875
+ stateRef.current = nextState;
2876
+ setState(nextState);
1586
2877
  }
1587
- const doResolve = import_react11.default.useCallback(
1588
- (index) => {
1589
- if (resolvedRef.current) return;
1590
- resolvedRef.current = true;
1591
- if (index === 0) request.resolve(true);
1592
- else if (index === 1) request.resolve("allow-session");
1593
- else request.resolve(false);
2878
+ const applyAction = import_react13.default.useCallback(
2879
+ (action) => {
2880
+ const result = applyPermissionPromptInput(stateRef.current, action);
2881
+ stateRef.current = result.state;
2882
+ setState(result.state);
2883
+ if (result.effect.type === "resolve") {
2884
+ request.resolve(result.effect.decision);
2885
+ }
1594
2886
  },
1595
2887
  [request]
1596
2888
  );
1597
- (0, import_ink10.useInput)((input, key) => {
1598
- if (resolvedRef.current) return;
1599
- if (key.upArrow || key.leftArrow) {
1600
- setSelected((prev) => prev > 0 ? prev - 1 : prev);
1601
- } else if (key.downArrow || key.rightArrow) {
1602
- setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
1603
- } else if (key.return) {
1604
- doResolve(selected);
1605
- } else if (input === "y" || input === "1") {
1606
- doResolve(0);
1607
- } else if (input === "a" || input === "2") {
1608
- doResolve(1);
1609
- } else if (input === "n" || input === "d" || input === "3") {
1610
- doResolve(2);
2889
+ (0, import_ink11.useInput)((input, key) => {
2890
+ const action = getPermissionPromptInputAction(input, key);
2891
+ if (action !== void 0) {
2892
+ applyAction(action);
1611
2893
  }
1612
2894
  });
1613
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1614
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
1615
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Text, { children: [
2895
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2896
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
2897
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { children: [
1616
2898
  "Tool:",
1617
2899
  " ",
1618
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: request.toolName })
2900
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "cyan", bold: true, children: request.toolName })
1619
2901
  ] }),
1620
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Text, { dimColor: true, children: [
2902
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
1621
2903
  " ",
1622
2904
  formatArgs(request.toolArgs)
1623
2905
  ] }),
1624
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink10.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1625
- i === selected ? "> " : " ",
1626
- opt
1627
- ] }) }, opt)) }),
1628
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink10.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
2906
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: PERMISSION_PROMPT_OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2907
+ import_ink11.Text,
2908
+ {
2909
+ color: i === state.selectedIndex ? "cyan" : void 0,
2910
+ bold: i === state.selectedIndex,
2911
+ children: [
2912
+ i === state.selectedIndex ? "> " : " ",
2913
+ opt
2914
+ ]
2915
+ }
2916
+ ) }, opt)) }),
2917
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
1629
2918
  ] });
1630
2919
  }
1631
2920
 
1632
2921
  // src/ui/StreamingIndicator.tsx
1633
- var import_ink11 = require("ink");
1634
- var import_jsx_runtime10 = require("react/jsx-runtime");
2922
+ var import_ink12 = require("ink");
2923
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1635
2924
  function getToolStyle(t) {
1636
2925
  if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
1637
2926
  if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
@@ -1642,16 +2931,16 @@ function StreamingIndicator({ text, activeTools }) {
1642
2931
  const hasTools = activeTools.length > 0;
1643
2932
  const hasText = text.length > 0;
1644
2933
  if (!hasTools && !hasText) {
1645
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
2934
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_jsx_runtime12.Fragment, {});
1646
2935
  }
1647
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink11.Box, { flexDirection: "column", children: [
1648
- hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink11.Box, { flexDirection: "column", marginBottom: 1, children: [
1649
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Text, { color: "white", bold: true, children: "Tools:" }),
1650
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Text, { children: " " }),
2936
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", children: [
2937
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", marginBottom: 1, children: [
2938
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "white", bold: true, children: "Tools:" }),
2939
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: " " }),
1651
2940
  activeTools.map((t, i) => {
1652
2941
  const { color, icon, strikethrough } = getToolStyle(t);
1653
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink11.Box, { flexDirection: "column", children: [
1654
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink11.Text, { color, strikethrough, children: [
2942
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", children: [
2943
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Text, { color, strikethrough, children: [
1655
2944
  " ",
1656
2945
  icon,
1657
2946
  " ",
@@ -1660,25 +2949,25 @@ function StreamingIndicator({ text, activeTools }) {
1660
2949
  t.firstArg,
1661
2950
  ")"
1662
2951
  ] }),
1663
- t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
2952
+ t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
1664
2953
  ] }, `${t.toolName}-${i}`);
1665
2954
  })
1666
2955
  ] }),
1667
- hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink11.Box, { flexDirection: "column", marginBottom: 1, children: [
1668
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Text, { color: "cyan", bold: true, children: "Robota:" }),
1669
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Text, { children: " " }),
1670
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink11.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
2956
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", marginBottom: 1, children: [
2957
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", bold: true, children: "Robota:" }),
2958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: " " }),
2959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
1671
2960
  ] })
1672
2961
  ] });
1673
2962
  }
1674
2963
 
1675
2964
  // src/ui/PluginTUI.tsx
1676
- var import_react15 = require("react");
2965
+ var import_react16 = require("react");
1677
2966
 
1678
2967
  // src/ui/MenuSelect.tsx
1679
- var import_react12 = require("react");
1680
- var import_ink12 = require("ink");
1681
- var import_jsx_runtime11 = require("react/jsx-runtime");
2968
+ var import_react14 = require("react");
2969
+ var import_ink13 = require("ink");
2970
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1682
2971
  function MenuSelect({
1683
2972
  title,
1684
2973
  items,
@@ -1687,119 +2976,57 @@ function MenuSelect({
1687
2976
  loading,
1688
2977
  error
1689
2978
  }) {
1690
- const [selected, setSelected] = (0, import_react12.useState)(0);
1691
- const selectedRef = (0, import_react12.useRef)(0);
1692
- const resolvedRef = (0, import_react12.useRef)(false);
1693
- const doSelect = (0, import_react12.useCallback)(
1694
- (index) => {
1695
- if (resolvedRef.current || items.length === 0) return;
1696
- resolvedRef.current = true;
1697
- onSelect(items[index].value);
2979
+ const [state, setState] = (0, import_react14.useState)(() => createSelectionFlowState());
2980
+ const stateRef = (0, import_react14.useRef)(state);
2981
+ const isEnabled = !loading && !error;
2982
+ const applyAction = (0, import_react14.useCallback)(
2983
+ (action) => {
2984
+ const result = applySelectionInput(stateRef.current, action, {
2985
+ itemCount: items.length,
2986
+ enabled: isEnabled
2987
+ });
2988
+ stateRef.current = result.state;
2989
+ setState(result.state);
2990
+ if (result.effect.type === "cancel") {
2991
+ onBack();
2992
+ } else if (result.effect.type === "select") {
2993
+ const item = items[result.effect.index];
2994
+ if (item !== void 0) {
2995
+ onSelect(item.value);
2996
+ }
2997
+ }
1698
2998
  },
1699
- [items, onSelect]
2999
+ [isEnabled, items, onBack, onSelect]
1700
3000
  );
1701
- (0, import_ink12.useInput)((input, key) => {
1702
- if (resolvedRef.current) return;
1703
- if (key.escape) {
1704
- resolvedRef.current = true;
1705
- onBack();
1706
- return;
1707
- }
1708
- if (loading || error || items.length === 0) return;
1709
- if (key.upArrow) {
1710
- const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
1711
- selectedRef.current = next;
1712
- setSelected(next);
1713
- } else if (key.downArrow) {
1714
- const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
1715
- selectedRef.current = next;
1716
- setSelected(next);
1717
- } else if (key.return) {
1718
- doSelect(selectedRef.current);
1719
- }
1720
- });
1721
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
1723
- loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Text, { dimColor: true, children: "Loading..." }) }),
1724
- error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink12.Box, { marginTop: 1, flexDirection: "column", children: [
1725
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Text, { color: "red", children: error }),
1726
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Text, { dimColor: true, children: "Press Esc to go back" })
1727
- ] }),
1728
- !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink12.Box, { children: [
1729
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink12.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1730
- i === selected ? "> " : " ",
1731
- item.label
1732
- ] }),
1733
- item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink12.Text, { dimColor: true, children: [
1734
- " ",
1735
- item.hint
1736
- ] })
1737
- ] }, item.value)) }),
1738
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink12.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
1739
- ] });
1740
- }
1741
-
1742
- // src/ui/TextPrompt.tsx
1743
- var import_react13 = require("react");
1744
- var import_ink13 = require("ink");
1745
- var import_jsx_runtime12 = require("react/jsx-runtime");
1746
- function TextPrompt({
1747
- title,
1748
- placeholder,
1749
- onSubmit,
1750
- onCancel,
1751
- validate
1752
- }) {
1753
- const [value, setValue] = (0, import_react13.useState)("");
1754
- const [error, setError] = (0, import_react13.useState)();
1755
- const resolvedRef = (0, import_react13.useRef)(false);
1756
- const valueRef = (0, import_react13.useRef)("");
1757
- const handleSubmit = (0, import_react13.useCallback)(() => {
1758
- if (resolvedRef.current) return;
1759
- const trimmed = valueRef.current.trim();
1760
- if (!trimmed) return;
1761
- if (validate) {
1762
- const err = validate(trimmed);
1763
- if (err) {
1764
- setError(err);
1765
- return;
1766
- }
1767
- }
1768
- resolvedRef.current = true;
1769
- onSubmit(trimmed);
1770
- }, [validate, onSubmit]);
1771
- (0, import_ink13.useInput)((input, key) => {
1772
- if (resolvedRef.current) return;
1773
- if (key.escape) {
1774
- resolvedRef.current = true;
1775
- onCancel();
1776
- return;
1777
- }
1778
- if (key.return) {
1779
- handleSubmit();
1780
- return;
1781
- }
1782
- if (key.backspace || key.delete) {
1783
- valueRef.current = valueRef.current.slice(0, -1);
1784
- setValue(valueRef.current);
1785
- setError(void 0);
1786
- return;
1787
- }
1788
- if (input && !key.ctrl && !key.meta) {
1789
- valueRef.current = valueRef.current + input;
1790
- setValue(valueRef.current);
1791
- setError(void 0);
3001
+ (0, import_ink13.useInput)((input, key) => {
3002
+ const action = getVerticalSelectionInputAction(key);
3003
+ if (action !== void 0) {
3004
+ applyAction(action);
1792
3005
  }
1793
3006
  });
1794
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink13.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1795
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { color: "yellow", bold: true, children: title }),
1796
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink13.Box, { marginTop: 1, children: [
1797
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { color: "cyan", children: "> " }),
1798
- value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { dimColor: true, children: placeholder }) : null,
1799
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { color: "cyan", children: "\u2588" })
3007
+ const normalizedState = normalizeSelectionState(state, { itemCount: items.length });
3008
+ if (normalizedState !== state) {
3009
+ stateRef.current = normalizedState;
3010
+ }
3011
+ const selected = normalizedState.selectedIndex;
3012
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
3013
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "yellow", bold: true, children: title }),
3014
+ loading && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: "Loading..." }) }),
3015
+ error && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Box, { marginTop: 1, flexDirection: "column", children: [
3016
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { color: "red", children: error }),
3017
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: "Press Esc to go back" })
1800
3018
  ] }),
1801
- error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { color: "red", children: error }),
1802
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink13.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
3019
+ !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Box, { children: [
3020
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
3021
+ i === selected ? "> " : " ",
3022
+ item.label
3023
+ ] }),
3024
+ item.hint && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_ink13.Text, { dimColor: true, children: [
3025
+ " ",
3026
+ item.hint
3027
+ ] })
3028
+ ] }, item.value)) }),
3029
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_ink13.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
1803
3030
  ] });
1804
3031
  }
1805
3032
 
@@ -1896,12 +3123,12 @@ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
1896
3123
  }
1897
3124
 
1898
3125
  // src/ui/hooks/usePluginScreenData.ts
1899
- var import_react14 = require("react");
3126
+ var import_react15 = require("react");
1900
3127
  function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, stackLength) {
1901
- const [items, setItems] = (0, import_react14.useState)([]);
1902
- const [loading, setLoading] = (0, import_react14.useState)(false);
1903
- const [error, setError] = (0, import_react14.useState)();
1904
- (0, import_react14.useEffect)(() => {
3128
+ const [items, setItems] = (0, import_react15.useState)([]);
3129
+ const [loading, setLoading] = (0, import_react15.useState)(false);
3130
+ const [error, setError] = (0, import_react15.useState)();
3131
+ (0, import_react15.useEffect)(() => {
1905
3132
  setItems([]);
1906
3133
  setError(void 0);
1907
3134
  if (screen === "marketplace-list") {
@@ -1956,16 +3183,16 @@ function usePluginScreenData(screen, marketplace, callbacks, refreshCounter, sta
1956
3183
  }
1957
3184
 
1958
3185
  // src/ui/PluginTUI.tsx
1959
- var import_jsx_runtime13 = require("react/jsx-runtime");
3186
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1960
3187
  function PluginTUI({ callbacks, onClose, addMessage }) {
1961
- const [stack, setStack] = (0, import_react15.useState)([{ screen: "main" }]);
1962
- const [confirm, setConfirm] = (0, import_react15.useState)();
1963
- const [refreshCounter, setRefreshCounter] = (0, import_react15.useState)(0);
3188
+ const [stack, setStack] = (0, import_react16.useState)([{ screen: "main" }]);
3189
+ const [confirm, setConfirm] = (0, import_react16.useState)();
3190
+ const [refreshCounter, setRefreshCounter] = (0, import_react16.useState)(0);
1964
3191
  const current = stack[stack.length - 1] ?? { screen: "main" };
1965
- const push = (0, import_react15.useCallback)((state) => {
3192
+ const push = (0, import_react16.useCallback)((state) => {
1966
3193
  setStack((prev) => [...prev, state]);
1967
3194
  }, []);
1968
- const pop = (0, import_react15.useCallback)(() => {
3195
+ const pop = (0, import_react16.useCallback)(() => {
1969
3196
  setStack((prev) => {
1970
3197
  if (prev.length <= 1) {
1971
3198
  onClose();
@@ -1974,7 +3201,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1974
3201
  return prev.slice(0, -1);
1975
3202
  });
1976
3203
  }, [onClose]);
1977
- const popN = (0, import_react15.useCallback)(
3204
+ const popN = (0, import_react16.useCallback)(
1978
3205
  (n) => {
1979
3206
  setStack((prev) => {
1980
3207
  const next = prev.slice(0, Math.max(1, prev.length - n));
@@ -1987,16 +3214,24 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
1987
3214
  },
1988
3215
  [onClose]
1989
3216
  );
1990
- const notify = (0, import_react15.useCallback)(
3217
+ const notify = (0, import_react16.useCallback)(
1991
3218
  (content) => {
1992
3219
  addMessage?.({ role: "system", content });
1993
3220
  },
1994
3221
  [addMessage]
1995
3222
  );
1996
- const refresh = (0, import_react15.useCallback)(() => {
3223
+ const refresh = (0, import_react16.useCallback)(() => {
1997
3224
  setRefreshCounter((c) => c + 1);
1998
3225
  }, []);
1999
- const nav = { push, pop, popN, notify, setConfirm, refresh };
3226
+ const setConfirmNav = (0, import_react16.useCallback)(
3227
+ (state) => setConfirm(state),
3228
+ [setConfirm]
3229
+ );
3230
+ const pushNav = (0, import_react16.useCallback)(
3231
+ (state) => push({ screen: state.screen, context: state.context }),
3232
+ [push]
3233
+ );
3234
+ const nav = { push: pushNav, pop, popN, notify, setConfirm: setConfirmNav, refresh };
2000
3235
  const { items, loading, error } = usePluginScreenData(
2001
3236
  current.screen,
2002
3237
  current.context?.marketplace,
@@ -2004,7 +3239,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2004
3239
  refreshCounter,
2005
3240
  stack.length
2006
3241
  );
2007
- const handleSelect = (0, import_react15.useCallback)(
3242
+ const handleSelect = (0, import_react16.useCallback)(
2008
3243
  (value) => {
2009
3244
  const screen2 = current.screen;
2010
3245
  const ctx = current.context;
@@ -2022,7 +3257,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2022
3257
  },
2023
3258
  [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
2024
3259
  );
2025
- const handleTextSubmit = (0, import_react15.useCallback)(
3260
+ const handleTextSubmit = (0, import_react16.useCallback)(
2026
3261
  (value) => {
2027
3262
  if (current.screen === "marketplace-add") {
2028
3263
  callbacks.marketplaceAdd(value).then((name) => {
@@ -2037,7 +3272,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2037
3272
  [current.screen, callbacks, notify, pop]
2038
3273
  );
2039
3274
  if (confirm) {
2040
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3275
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2041
3276
  ConfirmPrompt,
2042
3277
  {
2043
3278
  message: confirm.message,
@@ -2050,7 +3285,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2050
3285
  }
2051
3286
  const screen = current.screen;
2052
3287
  if (screen === "marketplace-add") {
2053
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3288
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2054
3289
  TextPrompt,
2055
3290
  {
2056
3291
  title: "Add Marketplace Source",
@@ -2062,7 +3297,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2062
3297
  );
2063
3298
  }
2064
3299
  if (screen === "marketplace-action") {
2065
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3300
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2066
3301
  MenuSelect,
2067
3302
  {
2068
3303
  title: `Marketplace: ${current.context?.marketplace ?? ""}`,
@@ -2078,7 +3313,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2078
3313
  );
2079
3314
  }
2080
3315
  if (screen === "marketplace-install-scope") {
2081
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3316
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2082
3317
  MenuSelect,
2083
3318
  {
2084
3319
  title: `Install scope for "${current.context?.pluginId ?? ""}"`,
@@ -2093,7 +3328,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2093
3328
  );
2094
3329
  }
2095
3330
  if (screen === "installed-action") {
2096
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3331
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2097
3332
  MenuSelect,
2098
3333
  {
2099
3334
  title: `Plugin: ${current.context?.pluginId ?? ""}`,
@@ -2116,7 +3351,7 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2116
3351
  { label: "Installed Plugins", value: "installed" }
2117
3352
  ]
2118
3353
  };
2119
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3354
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2120
3355
  MenuSelect,
2121
3356
  {
2122
3357
  title: titleMap[screen] ?? "Plugin Management",
@@ -2134,9 +3369,9 @@ function PluginTUI({ callbacks, onClose, addMessage }) {
2134
3369
  var import_ink15 = require("ink");
2135
3370
 
2136
3371
  // src/ui/ListPicker.tsx
2137
- var import_react16 = require("react");
3372
+ var import_react17 = require("react");
2138
3373
  var import_ink14 = require("ink");
2139
- var import_jsx_runtime14 = require("react/jsx-runtime");
3374
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2140
3375
  var DEFAULT_MAX_VISIBLE = 3;
2141
3376
  function ListPicker({
2142
3377
  items,
@@ -2145,54 +3380,52 @@ function ListPicker({
2145
3380
  onCancel,
2146
3381
  maxVisible = DEFAULT_MAX_VISIBLE
2147
3382
  }) {
2148
- const [selectedIndex, setSelectedIndex] = (0, import_react16.useState)(0);
2149
- const [scrollOffset, setScrollOffset] = (0, import_react16.useState)(0);
2150
- const selectedRef = (0, import_react16.useRef)(0);
2151
- const resolvedRef = (0, import_react16.useRef)(false);
2152
- (0, import_ink14.useInput)((_input, key) => {
2153
- if (resolvedRef.current) return;
2154
- if (key.escape) {
2155
- resolvedRef.current = true;
2156
- onCancel();
2157
- return;
2158
- }
2159
- if (items.length === 0) return;
2160
- if (key.upArrow) {
2161
- const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
2162
- selectedRef.current = next;
2163
- setSelectedIndex(next);
2164
- if (next < scrollOffset) {
2165
- setScrollOffset(next);
2166
- }
2167
- } else if (key.downArrow) {
2168
- const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
2169
- selectedRef.current = next;
2170
- setSelectedIndex(next);
2171
- if (next >= scrollOffset + maxVisible) {
2172
- setScrollOffset(next - maxVisible + 1);
2173
- }
2174
- } else if (key.return) {
2175
- const item = items[selectedRef.current];
2176
- if (item !== void 0) {
2177
- resolvedRef.current = true;
2178
- onSelect(item);
3383
+ const [state, setState] = (0, import_react17.useState)(() => createSelectionFlowState());
3384
+ const stateRef = (0, import_react17.useRef)(state);
3385
+ const applyAction = (0, import_react17.useCallback)(
3386
+ (action) => {
3387
+ const result = applySelectionInput(stateRef.current, action, {
3388
+ itemCount: items.length,
3389
+ maxVisible
3390
+ });
3391
+ stateRef.current = result.state;
3392
+ setState(result.state);
3393
+ if (result.effect.type === "cancel") {
3394
+ onCancel();
3395
+ } else if (result.effect.type === "select") {
3396
+ const item = items[result.effect.index];
3397
+ if (item !== void 0) {
3398
+ onSelect(item);
3399
+ }
2179
3400
  }
3401
+ },
3402
+ [items, maxVisible, onCancel, onSelect]
3403
+ );
3404
+ (0, import_ink14.useInput)((_input, key) => {
3405
+ const action = getVerticalSelectionInputAction(key);
3406
+ if (action !== void 0) {
3407
+ applyAction(action);
2180
3408
  }
2181
3409
  });
2182
3410
  if (items.length === 0) {
2183
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink14.Box, {});
3411
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, {});
3412
+ }
3413
+ const normalizedState = normalizeSelectionState(state, { itemCount: items.length, maxVisible });
3414
+ if (normalizedState !== state) {
3415
+ stateRef.current = normalizedState;
2184
3416
  }
3417
+ const { selectedIndex, scrollOffset } = normalizedState;
2185
3418
  const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
2186
3419
  const hasMore = scrollOffset + maxVisible < items.length;
2187
3420
  const hasLess = scrollOffset > 0;
2188
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink14.Box, { flexDirection: "column", children: [
2189
- hasLess && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink14.Text, { dimColor: true, children: [
3421
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { flexDirection: "column", children: [
3422
+ hasLess && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { dimColor: true, children: [
2190
3423
  " \u2191 ",
2191
3424
  scrollOffset,
2192
3425
  " more above"
2193
3426
  ] }),
2194
- visibleItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink14.Box, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
2195
- hasMore && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink14.Text, { dimColor: true, children: [
3427
+ visibleItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
3428
+ hasMore && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { dimColor: true, children: [
2196
3429
  " \u2193 ",
2197
3430
  items.length - scrollOffset - maxVisible,
2198
3431
  " more below"
@@ -2201,7 +3434,7 @@ function ListPicker({
2201
3434
  }
2202
3435
 
2203
3436
  // src/ui/SessionPicker.tsx
2204
- var import_jsx_runtime15 = require("react/jsx-runtime");
3437
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2205
3438
  var SESSION_ID_DISPLAY_LENGTH = 8;
2206
3439
  function SessionPicker({
2207
3440
  sessionStore,
@@ -2210,9 +3443,9 @@ function SessionPicker({
2210
3443
  onCancel
2211
3444
  }) {
2212
3445
  const sessions = (sessionStore?.list() ?? []).filter((s) => s.cwd === cwd);
2213
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2214
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
2215
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
3446
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink15.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
3447
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink15.Text, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
3448
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2216
3449
  ListPicker,
2217
3450
  {
2218
3451
  items: sessions,
@@ -2223,24 +3456,24 @@ function SessionPicker({
2223
3456
  });
2224
3457
  const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
2225
3458
  const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
2226
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Text, { children: [
3459
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink15.Text, { children: [
2227
3460
  isSelected ? "> " : " ",
2228
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
3461
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink15.Text, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
2229
3462
  " ",
2230
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
3463
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink15.Text, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
2231
3464
  month: "short",
2232
3465
  day: "numeric",
2233
3466
  hour: "2-digit",
2234
3467
  minute: "2-digit"
2235
3468
  }) }),
2236
3469
  " ",
2237
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink15.Text, { dimColor: true, children: [
3470
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink15.Text, { dimColor: true, children: [
2238
3471
  "msgs: ",
2239
3472
  session.messages.length
2240
3473
  ] }),
2241
- preview ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
3474
+ preview ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2242
3475
  "\n ",
2243
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink15.Text, { color: "gray", children: preview })
3476
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink15.Text, { color: "gray", children: preview })
2244
3477
  ] }) : null
2245
3478
  ] });
2246
3479
  },
@@ -2251,11 +3484,55 @@ function SessionPicker({
2251
3484
  ] });
2252
3485
  }
2253
3486
 
3487
+ // src/ui/BackgroundTaskPanel.tsx
3488
+ var import_ink16 = require("ink");
3489
+ var import_jsx_runtime17 = require("react/jsx-runtime");
3490
+ var MS_PER_SECOND = 1e3;
3491
+ var SECONDS_PER_MINUTE = 60;
3492
+ var MINUTES_PER_HOUR = 60;
3493
+ function getStatusColor(status) {
3494
+ if (status === "completed") return "green";
3495
+ if (status === "failed") return "red";
3496
+ if (status === "cancelled") return "yellow";
3497
+ return "cyan";
3498
+ }
3499
+ function getStatusMarker(status) {
3500
+ if (status === "queued" || status === "running") return "\u25A1";
3501
+ return "\u25A0";
3502
+ }
3503
+ function getTaskPreview(task) {
3504
+ return task.errorPreview ?? task.resultPreview ?? task.currentAction ?? task.preview;
3505
+ }
3506
+ function formatAge(iso) {
3507
+ if (!iso) return void 0;
3508
+ const timestamp = Date.parse(iso);
3509
+ if (Number.isNaN(timestamp)) return void 0;
3510
+ const seconds = Math.max(0, Math.floor((Date.now() - timestamp) / MS_PER_SECOND));
3511
+ if (seconds < SECONDS_PER_MINUTE) return `${seconds}s`;
3512
+ const minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
3513
+ if (minutes < MINUTES_PER_HOUR) return `${minutes}m`;
3514
+ return `${Math.floor(minutes / MINUTES_PER_HOUR)}h`;
3515
+ }
3516
+ function BackgroundTaskPanel({ tasks }) {
3517
+ if (tasks.length === 0) return null;
3518
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink16.Box, { flexDirection: "column", marginBottom: 1, children: [
3519
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { color: "cyan", bold: true, children: "Background" }),
3520
+ tasks.map((task) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_ink16.Text, { children: [
3521
+ "- ",
3522
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { color: getStatusColor(task.status), children: getStatusMarker(task.status) }),
3523
+ ` ${task.kind}:${task.label} ${task.id}`,
3524
+ task.status === "running" && formatAge(task.lastActivityAt) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { dimColor: true, children: ` idle ${formatAge(task.lastActivityAt)}` }) : null,
3525
+ task.timeoutReason ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { dimColor: true, children: ` (${task.timeoutReason})` }) : null,
3526
+ getTaskPreview(task) ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(import_ink16.Text, { dimColor: true, children: ` - ${getTaskPreview(task)}` }) : null
3527
+ ] }, task.id))
3528
+ ] });
3529
+ }
3530
+
2254
3531
  // src/ui/App.tsx
2255
- var import_jsx_runtime16 = require("react/jsx-runtime");
3532
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2256
3533
  function App(props) {
2257
- const [activeSessionId, setActiveSessionId] = (0, import_react17.useState)(props.resumeSessionId);
2258
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3534
+ const [activeSessionId, setActiveSessionId] = (0, import_react18.useState)(props.resumeSessionId);
3535
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2259
3536
  AppInner,
2260
3537
  {
2261
3538
  ...props,
@@ -2267,6 +3544,7 @@ function App(props) {
2267
3544
  }
2268
3545
  function AppInner(props) {
2269
3546
  const cwd = props.cwd;
3547
+ const providerDefinitions = props.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
2270
3548
  const {
2271
3549
  interactiveSession,
2272
3550
  registry,
@@ -2276,12 +3554,15 @@ function AppInner(props) {
2276
3554
  activeTools,
2277
3555
  isThinking,
2278
3556
  isAborting,
3557
+ isShuttingDown,
2279
3558
  pendingPrompt,
3559
+ backgroundTasks,
2280
3560
  permissionRequest,
2281
3561
  contextState,
2282
3562
  handleSubmit: baseHandleSubmit,
2283
3563
  handleAbort,
2284
- handleCancelQueue
3564
+ handleCancelQueue,
3565
+ handleShutdown
2285
3566
  } = useInteractiveSession({
2286
3567
  cwd,
2287
3568
  provider: props.provider,
@@ -2290,38 +3571,65 @@ function AppInner(props) {
2290
3571
  sessionStore: props.sessionStore,
2291
3572
  resumeSessionId: props.resumeSessionId,
2292
3573
  forkSession: props.forkSession,
2293
- sessionName: props.sessionName
3574
+ sessionName: props.sessionName,
3575
+ backgroundTaskRunners: props.backgroundTaskRunners,
3576
+ subagentRunnerFactory: props.subagentRunnerFactory,
3577
+ commandModules: props.commandModules,
3578
+ providerDefinitions
2294
3579
  });
2295
3580
  const pluginCallbacks = usePluginCallbacks(cwd);
2296
- const [sessionName, setSessionName] = (0, import_react17.useState)(props.sessionName);
3581
+ const { exit } = (0, import_ink17.useApp)();
3582
+ const [sessionName, setSessionName] = (0, import_react18.useState)(props.sessionName);
2297
3583
  const {
2298
3584
  handleSubmit,
2299
3585
  pendingModelId,
3586
+ pendingProviderProfile,
3587
+ pendingProviderSetupType,
2300
3588
  showPluginTUI,
2301
3589
  showSessionPicker,
2302
3590
  setShowPluginTUI,
2303
3591
  setShowSessionPicker,
2304
- handleModelConfirm
3592
+ handleModelConfirm,
3593
+ handleProviderConfirm,
3594
+ handleProviderSetupSubmit,
3595
+ handleProviderSetupCancel
2305
3596
  } = useSideEffects({
3597
+ cwd,
2306
3598
  interactiveSession,
2307
3599
  addEntry,
2308
3600
  baseHandleSubmit,
2309
- setSessionName
3601
+ setSessionName,
3602
+ providerDefinitions
2310
3603
  });
2311
- (0, import_react17.useEffect)(() => {
3604
+ (0, import_react18.useEffect)(() => {
2312
3605
  const name = interactiveSession?.getName?.();
2313
3606
  if (name && !sessionName) setSessionName(name);
2314
3607
  }, [interactiveSession, sessionName]);
2315
- (0, import_react17.useEffect)(() => {
3608
+ (0, import_react18.useEffect)(() => {
2316
3609
  const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
2317
3610
  process.stdout.write(`\x1B]0;${title}\x07`);
2318
3611
  }, [sessionName]);
2319
- (0, import_ink16.useInput)(
2320
- (_input, key) => {
2321
- if (key.escape && isThinking) handleAbort();
2322
- },
2323
- { isActive: !permissionRequest && !showPluginTUI && !showSessionPicker }
2324
- );
3612
+ (0, import_ink17.useInput)((_input, key) => {
3613
+ if (!key.escape || !isThinking) return;
3614
+ if (permissionRequest || showPluginTUI || showSessionPicker) return;
3615
+ handleAbort();
3616
+ });
3617
+ (0, import_ink17.useInput)((input, key) => {
3618
+ if (!key.ctrl || input !== "c" || isShuttingDown) return;
3619
+ void handleShutdown("prompt_input_exit").finally(() => exit());
3620
+ });
3621
+ (0, import_react18.useEffect)(() => {
3622
+ const onSigterm = () => {
3623
+ if (isShuttingDown) return;
3624
+ void handleShutdown("other").finally(() => exit());
3625
+ };
3626
+ process.once("SIGINT", onSigterm);
3627
+ process.once("SIGTERM", onSigterm);
3628
+ return () => {
3629
+ process.off("SIGINT", onSigterm);
3630
+ process.off("SIGTERM", onSigterm);
3631
+ };
3632
+ }, [handleShutdown, exit, isShuttingDown]);
2325
3633
  let permissionMode = props.permissionMode ?? "default";
2326
3634
  let sessionId = "";
2327
3635
  try {
@@ -2330,41 +3638,59 @@ function AppInner(props) {
2330
3638
  sessionId = session.getSessionId();
2331
3639
  } catch {
2332
3640
  }
2333
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", children: [
2334
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2335
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { color: "cyan", bold: true, children: `
3641
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_ink17.Box, { flexDirection: "column", children: [
3642
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_ink17.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
3643
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Text, { color: "cyan", bold: true, children: `
2336
3644
  ____ ___ ____ ___ _____ _
2337
3645
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
2338
3646
  | |_) | | | | _ \\| | | || | / _ \\
2339
3647
  | _ <| |_| | |_) | |_| || |/ ___ \\
2340
3648
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
2341
3649
  ` }),
2342
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Text, { dimColor: true, children: [
3650
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_ink17.Text, { dimColor: true, children: [
2343
3651
  " v",
2344
3652
  props.version ?? "0.0.0"
2345
3653
  ] })
2346
3654
  ] }),
2347
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_ink16.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2348
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MessageList, { history }),
2349
- (isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
3655
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_ink17.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
3656
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MessageList, { history }),
3657
+ isShuttingDown && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Box, { marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Text, { color: "yellow", children: "Shutting down..." }) }),
3658
+ (isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(StreamingIndicator, { text: streamingText, activeTools }) }),
3659
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(BackgroundTaskPanel, { tasks: backgroundTasks })
2350
3660
  ] }),
2351
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(PermissionPrompt, { request: permissionRequest }),
2352
- pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3661
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(PermissionPrompt, { request: permissionRequest }),
3662
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2353
3663
  ConfirmPrompt,
2354
3664
  {
2355
- message: `Change model to ${(0, import_agent_core5.getModelName)(pendingModelId)}? This will restart the session.`,
3665
+ message: `Change model to ${(0, import_agent_core7.getModelName)(pendingModelId)}? This will restart the session.`,
2356
3666
  onSelect: handleModelConfirm
2357
3667
  }
2358
3668
  ),
2359
- showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3669
+ pendingProviderProfile && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3670
+ ConfirmPrompt,
3671
+ {
3672
+ message: `Change provider to ${pendingProviderProfile}? This will restart the session.`,
3673
+ onSelect: handleProviderConfirm
3674
+ }
3675
+ ),
3676
+ pendingProviderSetupType && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
3677
+ ProviderSetupPrompt,
3678
+ {
3679
+ type: pendingProviderSetupType,
3680
+ providerDefinitions,
3681
+ onSubmit: handleProviderSetupSubmit,
3682
+ onCancel: handleProviderSetupCancel
3683
+ }
3684
+ ),
3685
+ showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2360
3686
  PluginTUI,
2361
3687
  {
2362
3688
  callbacks: pluginCallbacks,
2363
3689
  onClose: () => setShowPluginTUI(false),
2364
- addMessage: (msg) => addEntry((0, import_agent_core5.messageToHistoryEntry)((0, import_agent_core5.createSystemMessage)(msg.content)))
3690
+ addMessage: (msg) => addEntry((0, import_agent_core7.messageToHistoryEntry)((0, import_agent_core7.createSystemMessage)(msg.content)))
2365
3691
  }
2366
3692
  ),
2367
- showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3693
+ showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2368
3694
  SessionPicker,
2369
3695
  {
2370
3696
  sessionStore: props.sessionStore,
@@ -2375,15 +3701,15 @@ function AppInner(props) {
2375
3701
  },
2376
3702
  onCancel: () => {
2377
3703
  setShowSessionPicker(false);
2378
- addEntry((0, import_agent_core5.messageToHistoryEntry)((0, import_agent_core5.createSystemMessage)("Session resume cancelled.")));
3704
+ addEntry((0, import_agent_core7.messageToHistoryEntry)((0, import_agent_core7.createSystemMessage)("Session resume cancelled.")));
2379
3705
  }
2380
3706
  }
2381
3707
  ),
2382
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3708
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2383
3709
  StatusBar,
2384
3710
  {
2385
3711
  permissionMode,
2386
- modelName: props.modelId ? (0, import_agent_core5.getModelName)(props.modelId) : "",
3712
+ modelName: props.modelId ? (0, import_agent_core7.getModelName)(props.modelId) : "",
2387
3713
  sessionId,
2388
3714
  messageCount: history.length,
2389
3715
  isThinking,
@@ -2393,24 +3719,24 @@ function AppInner(props) {
2393
3719
  sessionName
2394
3720
  }
2395
3721
  ),
2396
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
3722
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2397
3723
  InputArea,
2398
3724
  {
2399
3725
  onSubmit: handleSubmit,
2400
3726
  onCancelQueue: handleCancelQueue,
2401
- isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isThinking && !!pendingPrompt,
3727
+ isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isShuttingDown || !!pendingProviderSetupType || isThinking && !!pendingPrompt,
2402
3728
  isAborting,
2403
3729
  pendingPrompt,
2404
3730
  registry,
2405
3731
  sessionName
2406
3732
  }
2407
3733
  ),
2408
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(import_ink16.Text, { children: " " })
3734
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(import_ink17.Text, { children: " " })
2409
3735
  ] });
2410
3736
  }
2411
3737
 
2412
3738
  // src/ui/render.tsx
2413
- var import_jsx_runtime17 = require("react/jsx-runtime");
3739
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2414
3740
  function renderApp(options) {
2415
3741
  process.on("unhandledRejection", (reason) => {
2416
3742
  process.stderr.write(`
@@ -2424,8 +3750,8 @@ function renderApp(options) {
2424
3750
  if (process.stdin.isTTY && process.stdout.isTTY) {
2425
3751
  process.stdout.write("\x1B[?2004h");
2426
3752
  }
2427
- const instance = (0, import_ink17.render)(/* @__PURE__ */ (0, import_jsx_runtime17.jsx)(App, { ...options }), {
2428
- exitOnCtrlC: true
3753
+ const instance = (0, import_ink18.render)(/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(App, { ...options }), {
3754
+ exitOnCtrlC: false
2429
3755
  });
2430
3756
  instance.waitUntilExit().then(() => {
2431
3757
  if (process.stdout.isTTY) {
@@ -2442,29 +3768,613 @@ function renderApp(options) {
2442
3768
  });
2443
3769
  }
2444
3770
 
2445
- // src/cli.ts
2446
- var import_meta = {};
2447
- function checkSettingsFile(filePath) {
2448
- if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
3771
+ // src/background/managed-shell-process-runner.ts
3772
+ var import_node_child_process = require("child_process");
3773
+ var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
3774
+ var DEFAULT_OUTPUT_LIMIT_BYTES = 3e4;
3775
+ var DEFAULT_KILL_GRACE_MS = 2e3;
3776
+ var LOG_PAGE_SIZE = 200;
3777
+ function createCapture(limitBytes) {
3778
+ const chunks = [];
3779
+ let capturedBytes = 0;
3780
+ let truncated = false;
3781
+ return {
3782
+ appendOutput(text) {
3783
+ if (truncated) return;
3784
+ const remaining = limitBytes - capturedBytes;
3785
+ const buffer = Buffer.from(text, "utf8");
3786
+ if (buffer.byteLength <= remaining) {
3787
+ chunks.push(text);
3788
+ capturedBytes += buffer.byteLength;
3789
+ return;
3790
+ }
3791
+ chunks.push(buffer.subarray(0, Math.max(remaining, 0)).toString("utf8"));
3792
+ chunks.push("\n[output truncated]\n");
3793
+ truncated = true;
3794
+ },
3795
+ getOutput() {
3796
+ return chunks.join("");
3797
+ }
3798
+ };
3799
+ }
3800
+ function appendLog(lines, source, text) {
3801
+ for (const line of text.split(/\r?\n/)) {
3802
+ if (line.length > 0) lines.push(`[${source}] ${line}`);
3803
+ }
3804
+ }
3805
+ function resolveShell(request) {
3806
+ return { command: request.shell ?? "sh", args: ["-c", request.command] };
3807
+ }
3808
+ function sendInput(child, input) {
3809
+ return new Promise((resolve, reject) => {
3810
+ const onError = (error) => {
3811
+ child.stdin.off("error", onError);
3812
+ reject(error);
3813
+ };
3814
+ child.stdin.once("error", onError);
3815
+ child.stdin.end(input, () => {
3816
+ child.stdin.off("error", onError);
3817
+ resolve();
3818
+ });
3819
+ });
3820
+ }
3821
+ function createManagedShellProcessRunner(options = {}) {
3822
+ const killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS;
3823
+ return {
3824
+ kind: "process",
3825
+ start(task) {
3826
+ if (task.request.kind !== "process") {
3827
+ throw new import_agent_sdk3.BackgroundTaskError("runner", `Invalid process task kind: ${task.request.kind}`);
3828
+ }
3829
+ return startProcessTask(task.taskId, task.request, killGraceMs);
3830
+ }
3831
+ };
3832
+ }
3833
+ function startProcessTask(taskId, request, killGraceMs) {
3834
+ const shell = resolveShell(request);
3835
+ const runtime = {
3836
+ taskId,
3837
+ request,
3838
+ child: (0, import_node_child_process.spawn)(shell.command, shell.args, {
3839
+ cwd: request.cwd,
3840
+ env: { ...process.env, ...request.env ?? {} },
3841
+ stdio: ["pipe", "pipe", "pipe"]
3842
+ }),
3843
+ logs: [],
3844
+ capture: createCapture(request.outputLimitBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES),
3845
+ killGraceMs
3846
+ };
3847
+ const result = createProcessResult(runtime);
3848
+ return createProcessHandle(runtime, result);
3849
+ }
3850
+ function createProcessResult(runtime) {
3851
+ let settled = false;
3852
+ return new Promise((resolve, reject) => {
3853
+ const timeoutTimer = runtime.request.timeoutMs ? setTimeout(() => {
3854
+ appendLog(runtime.logs, "system", `timed out after ${runtime.request.timeoutMs}ms`);
3855
+ runtime.child.kill("SIGTERM");
3856
+ rejectOnceLocal(new import_agent_sdk3.BackgroundTaskError("timeout", "Background process timed out"));
3857
+ }, runtime.request.timeoutMs) : void 0;
3858
+ function clearTimers() {
3859
+ if (timeoutTimer) clearTimeout(timeoutTimer);
3860
+ if (runtime.killTimer) clearTimeout(runtime.killTimer);
3861
+ }
3862
+ function resolveOnce(exitCode, signalCode) {
3863
+ if (settled) return;
3864
+ settled = true;
3865
+ clearTimers();
3866
+ resolve({
3867
+ taskId: runtime.taskId,
3868
+ kind: "process",
3869
+ output: runtime.capture.getOutput(),
3870
+ exitCode,
3871
+ signalCode
3872
+ });
3873
+ }
3874
+ function rejectOnceLocal(error) {
3875
+ if (settled) return;
3876
+ settled = true;
3877
+ clearTimers();
3878
+ reject(error);
3879
+ }
3880
+ attachOutputListeners(runtime);
3881
+ runtime.child.on("error", (error) => {
3882
+ appendLog(runtime.logs, "system", error.message);
3883
+ rejectOnceLocal(new import_agent_sdk3.BackgroundTaskError("process", error.message));
3884
+ });
3885
+ runtime.child.on("close", (code, signal) => {
3886
+ resolveOnce(code ?? void 0, signal ?? void 0);
3887
+ });
3888
+ if (runtime.request.stdin) {
3889
+ runtime.child.stdin.write(runtime.request.stdin);
3890
+ runtime.child.stdin.end();
3891
+ }
3892
+ });
3893
+ }
3894
+ function createProcessHandle(runtime, result) {
3895
+ return {
3896
+ taskId: runtime.taskId,
3897
+ ...runtime.child.pid !== void 0 ? { pid: runtime.child.pid } : {},
3898
+ result,
3899
+ cancel: async (reason) => {
3900
+ cancelProcess(runtime, reason);
3901
+ },
3902
+ send: async (input) => {
3903
+ if (!input.stdin) return;
3904
+ await sendInput(runtime.child, input.stdin);
3905
+ },
3906
+ readLog: (cursor) => Promise.resolve(readProcessLog(runtime, cursor))
3907
+ };
3908
+ }
3909
+ function attachOutputListeners(runtime) {
3910
+ runtime.child.stdout.on("data", (chunk) => {
3911
+ const text = chunk.toString("utf8");
3912
+ runtime.capture.appendOutput(text);
3913
+ appendLog(runtime.logs, "stdout", text);
3914
+ });
3915
+ runtime.child.stderr.on("data", (chunk) => {
3916
+ const text = chunk.toString("utf8");
3917
+ runtime.capture.appendOutput(text);
3918
+ appendLog(runtime.logs, "stderr", text);
3919
+ });
3920
+ }
3921
+ function cancelProcess(runtime, reason) {
3922
+ appendLog(runtime.logs, "system", reason ? `cancel requested: ${reason}` : "cancel requested");
3923
+ if (!runtime.child.killed) runtime.child.kill("SIGTERM");
3924
+ runtime.killTimer = setTimeout(() => {
3925
+ if (!runtime.child.killed) runtime.child.kill("SIGKILL");
3926
+ }, runtime.killGraceMs);
3927
+ }
3928
+ function readProcessLog(runtime, cursor) {
3929
+ const offset = cursor?.offset ?? 0;
3930
+ const nextOffset = Math.min(offset + LOG_PAGE_SIZE, runtime.logs.length);
3931
+ return {
3932
+ taskId: runtime.taskId,
3933
+ cursor,
3934
+ nextCursor: nextOffset < runtime.logs.length ? { offset: nextOffset } : void 0,
3935
+ lines: runtime.logs.slice(offset, nextOffset)
3936
+ };
3937
+ }
3938
+
3939
+ // src/subagents/child-process-subagent-runner.ts
3940
+ var import_node_child_process3 = require("child_process");
3941
+ var import_node_fs5 = require("fs");
3942
+ var import_node_path7 = require("path");
3943
+ var import_agent_sdk7 = require("@robota-sdk/agent-sdk");
3944
+
3945
+ // src/subagents/child-process-subagent-runner-result.ts
3946
+ var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
3947
+
3948
+ // src/subagents/child-process-subagent-ipc.ts
3949
+ function isRecord(value) {
3950
+ return typeof value === "object" && value !== null;
3951
+ }
3952
+ function hasString(value, key) {
3953
+ return typeof value[key] === "string";
3954
+ }
3955
+ function isSubagentWorkerChildMessage(value) {
3956
+ if (!isRecord(value) || !hasString(value, "type")) return false;
3957
+ switch (value.type) {
3958
+ case "ready":
3959
+ return true;
3960
+ case "text_delta":
3961
+ return hasString(value, "delta");
3962
+ case "tool_start":
3963
+ return hasString(value, "toolName");
3964
+ case "tool_end":
3965
+ return hasString(value, "toolName") && typeof value.success === "boolean";
3966
+ case "result":
3967
+ return hasString(value, "output");
3968
+ case "error":
3969
+ return hasString(value, "message");
3970
+ case "cancelled":
3971
+ return value.reason === void 0 || typeof value.reason === "string";
3972
+ default:
3973
+ return false;
3974
+ }
3975
+ }
3976
+
3977
+ // src/subagents/child-process-subagent-transport.ts
3978
+ var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
3979
+ function handleWorkerMessage(message, startWorker, resolveOnce, rejectOnce, emit) {
3980
+ switch (message.type) {
3981
+ case "ready":
3982
+ startWorker();
3983
+ break;
3984
+ case "result":
3985
+ resolveOnce(message.output);
3986
+ break;
3987
+ case "error":
3988
+ rejectOnce(new import_agent_sdk4.BackgroundTaskError("runner", message.message));
3989
+ break;
3990
+ case "cancelled":
3991
+ rejectOnce(new import_agent_sdk4.BackgroundTaskError("runner", message.reason ?? "Subagent worker cancelled"));
3992
+ break;
3993
+ case "text_delta":
3994
+ emit?.({ type: "background_task_text_delta", delta: message.delta });
3995
+ break;
3996
+ case "tool_start":
3997
+ emit?.({
3998
+ type: "background_task_tool_start",
3999
+ toolName: message.toolName,
4000
+ firstArg: extractFirstArg(message.toolArgs)
4001
+ });
4002
+ break;
4003
+ case "tool_end":
4004
+ emit?.({
4005
+ type: "background_task_tool_end",
4006
+ toolName: message.toolName,
4007
+ success: message.success
4008
+ });
4009
+ break;
4010
+ default:
4011
+ rejectOnce(new import_agent_sdk4.BackgroundTaskError("runner", "Unhandled subagent worker message"));
4012
+ }
4013
+ }
4014
+ function extractFirstArg(toolArgs) {
4015
+ if (!toolArgs) return void 0;
4016
+ const firstValue = Object.values(toolArgs)[0];
4017
+ if (firstValue === void 0) return void 0;
4018
+ return typeof firstValue === "object" ? JSON.stringify(firstValue) : String(firstValue);
4019
+ }
4020
+ function sendWorkerMessage(child, message) {
4021
+ return new Promise((resolve, reject) => {
4022
+ if (!child.connected) {
4023
+ reject(new import_agent_sdk4.BackgroundTaskError("crash", "Subagent worker IPC channel is closed"));
4024
+ return;
4025
+ }
4026
+ child.send(message, (error) => {
4027
+ if (error) {
4028
+ reject(error);
4029
+ return;
4030
+ }
4031
+ resolve();
4032
+ });
4033
+ });
4034
+ }
4035
+ async function cancelChildProcess(runtime, reason) {
4036
+ if (runtime.child.connected) {
4037
+ await sendWorkerMessage(runtime.child, { type: "cancel", reason }).catch(() => void 0);
4038
+ }
4039
+ runtime.killTimer = setTimeout(() => {
4040
+ if (!runtime.child.killed) {
4041
+ runtime.child.kill("SIGTERM");
4042
+ }
4043
+ }, runtime.killGraceMs);
4044
+ }
4045
+
4046
+ // src/subagents/child-process-subagent-runner-result.ts
4047
+ function createChildProcessSubagentResult(options) {
4048
+ return new Promise((resolve, reject) => {
4049
+ new ChildProcessSubagentResultController(options, resolve, reject).start();
4050
+ });
4051
+ }
4052
+ var ChildProcessSubagentResultController = class {
4053
+ constructor(options, resolve, reject) {
4054
+ this.options = options;
4055
+ this.resolve = resolve;
4056
+ this.reject = reject;
4057
+ this.timeoutTimer = createTimeoutTimer(this.options.runtime, (error) => this.rejectOnce(error));
4058
+ }
4059
+ settled = false;
4060
+ started = false;
4061
+ timeoutTimer;
4062
+ start() {
4063
+ const { child } = this.options.runtime;
4064
+ child.on("message", this.onMessage);
4065
+ child.on("error", this.onError);
4066
+ child.on("exit", this.onExit);
4067
+ child.once("spawn", () => {
4068
+ setImmediate(this.startWorker);
4069
+ });
4070
+ }
4071
+ startWorker = () => {
4072
+ if (this.started) return;
4073
+ this.started = true;
4074
+ const { child } = this.options.runtime;
4075
+ void sendWorkerMessage(child, { type: "start", payload: this.options.payload }).catch(
4076
+ (error) => {
4077
+ this.rejectOnce(error instanceof Error ? error : new Error(String(error)));
4078
+ }
4079
+ );
4080
+ };
4081
+ onMessage = (message) => {
4082
+ if (!isSubagentWorkerChildMessage(message)) {
4083
+ this.rejectOnce(
4084
+ new import_agent_sdk5.BackgroundTaskError("runner", "Received malformed subagent worker message")
4085
+ );
4086
+ return;
4087
+ }
4088
+ const { job } = this.options.runtime;
4089
+ handleWorkerMessage(message, this.startWorker, this.resolveOnce, this.rejectOnce, job.emit);
4090
+ };
4091
+ onError = (error) => {
4092
+ this.rejectOnce(new import_agent_sdk5.BackgroundTaskError("crash", error.message));
4093
+ };
4094
+ onExit = (code, signal) => {
4095
+ if (this.settled) return;
4096
+ this.rejectOnce(new import_agent_sdk5.BackgroundTaskError("crash", formatEarlyExitMessage(code, signal)));
4097
+ };
4098
+ resolveOnce = (output) => {
4099
+ if (this.settled) return;
4100
+ this.settled = true;
4101
+ this.clearTimers();
4102
+ this.cleanup();
4103
+ const { runtime, resolveTranscriptPath } = this.options;
4104
+ this.resolve(toSubagentResult(runtime.job, output, resolveTranscriptPath));
4105
+ };
4106
+ rejectOnce = (error) => {
4107
+ if (this.settled) return;
4108
+ this.settled = true;
4109
+ this.clearTimers();
4110
+ this.cleanup();
4111
+ this.reject(error);
4112
+ };
4113
+ clearTimers() {
4114
+ if (this.timeoutTimer) clearTimeout(this.timeoutTimer);
4115
+ if (this.options.runtime.killTimer) clearTimeout(this.options.runtime.killTimer);
4116
+ }
4117
+ cleanup() {
4118
+ const { child } = this.options.runtime;
4119
+ child.off("message", this.onMessage);
4120
+ child.off("error", this.onError);
4121
+ child.off("exit", this.onExit);
4122
+ }
4123
+ };
4124
+ function createCancellationResult(jobId) {
4125
+ let settled = false;
4126
+ let rejectFn = () => {
4127
+ };
4128
+ const promise = new Promise((_resolve, reject) => {
4129
+ rejectFn = reject;
4130
+ });
4131
+ return {
4132
+ promise,
4133
+ reject(reason) {
4134
+ if (settled) return;
4135
+ settled = true;
4136
+ rejectFn(new import_agent_sdk5.BackgroundTaskError("runner", reason ?? `Subagent job cancelled: ${jobId}`));
4137
+ }
4138
+ };
4139
+ }
4140
+ function createTimeoutTimer(runtime, rejectOnce) {
4141
+ if (!runtime.job.request.timeoutMs) return void 0;
4142
+ return setTimeout(() => {
4143
+ void cancelChildProcess(runtime, "Subagent worker timed out");
4144
+ rejectOnce(new import_agent_sdk5.BackgroundTaskError("timeout", "Subagent worker timed out"));
4145
+ }, runtime.job.request.timeoutMs);
4146
+ }
4147
+ function toSubagentResult(job, output, resolveTranscriptPath) {
4148
+ const transcriptPath = resolveTranscriptPath(job);
4149
+ return {
4150
+ jobId: job.jobId,
4151
+ output,
4152
+ ...transcriptPath ? { metadata: { transcriptPath, logPath: transcriptPath } } : {}
4153
+ };
4154
+ }
4155
+ function formatEarlyExitMessage(code, signal) {
4156
+ const detail = signal !== null ? `signal ${signal}` : `exit code ${code === null ? "unknown" : code}`;
4157
+ return `Subagent worker exited before result: ${detail}`;
4158
+ }
4159
+
4160
+ // src/subagents/git-worktree-isolation-adapter.ts
4161
+ var import_node_child_process2 = require("child_process");
4162
+ var import_node_crypto2 = require("crypto");
4163
+ var import_node_fs4 = require("fs");
4164
+ var import_node_path6 = require("path");
4165
+ var import_agent_sdk6 = require("@robota-sdk/agent-sdk");
4166
+ var WORKTREE_DIR = ".robota/worktrees";
4167
+ var BRANCH_PREFIX = "robota";
4168
+ var GIT_ENCODING = "utf8";
4169
+ var SHORT_ID_LENGTH = 8;
4170
+ function createGitWorktreeIsolationAdapter(options) {
4171
+ return new GitWorktreeIsolationAdapter(options);
4172
+ }
4173
+ var GitWorktreeIsolationAdapter = class {
4174
+ worktreeDir;
4175
+ branchPrefix;
4176
+ constructor(options = {}) {
4177
+ this.worktreeDir = options.worktreeDir ?? WORKTREE_DIR;
4178
+ this.branchPrefix = options.branchPrefix ?? BRANCH_PREFIX;
4179
+ }
4180
+ prepare(request) {
4181
+ const repoRoot = runGit(request.cwd, ["rev-parse", "--show-toplevel"]).trim();
4182
+ const shortId = (0, import_node_crypto2.randomUUID)().slice(0, SHORT_ID_LENGTH);
4183
+ const branchName = `${this.branchPrefix}/${request.jobId}-${shortId}`;
4184
+ const worktreeRoot = (0, import_node_path6.join)(repoRoot, this.worktreeDir);
4185
+ const worktreePath = (0, import_node_path6.join)(worktreeRoot, `${request.jobId}-${shortId}`);
4186
+ (0, import_node_fs4.mkdirSync)(worktreeRoot, { recursive: true });
4187
+ runGit(repoRoot, ["worktree", "add", "-b", branchName, worktreePath, "HEAD"]);
4188
+ return { repoRoot, worktreePath, branchName };
4189
+ }
4190
+ isClean(worktree) {
4191
+ return runGit(worktree.worktreePath, ["status", "--porcelain"]).trim().length === 0;
4192
+ }
4193
+ remove(worktree) {
4194
+ runGit(worktree.repoRoot, ["worktree", "remove", "--force", worktree.worktreePath]);
4195
+ runGit(worktree.repoRoot, ["branch", "-D", worktree.branchName]);
4196
+ }
4197
+ };
4198
+ function runGit(cwd, args) {
2449
4199
  try {
2450
- const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
2451
- if (raw.length === 0) return "incomplete";
2452
- const parsed = JSON.parse(raw);
2453
- const provider = parsed.provider;
2454
- if (!provider?.apiKey) return "incomplete";
2455
- return "valid";
2456
- } catch {
2457
- return "corrupt";
4200
+ return (0, import_node_child_process2.execFileSync)("git", args, {
4201
+ cwd,
4202
+ encoding: GIT_ENCODING,
4203
+ stdio: ["ignore", "pipe", "pipe"]
4204
+ });
4205
+ } catch (error) {
4206
+ const message = error instanceof Error ? error.message : String(error);
4207
+ throw new import_agent_sdk6.BackgroundTaskError("runner", `git ${args.join(" ")} failed: ${message}`);
4208
+ }
4209
+ }
4210
+
4211
+ // src/subagents/child-process-subagent-runner.ts
4212
+ var DEFAULT_KILL_GRACE_MS2 = 2e3;
4213
+ var LOG_PAGE_SIZE2 = 200;
4214
+ function createChildProcessSubagentRunnerFactory(options = {}) {
4215
+ return (deps) => {
4216
+ const runner = new ChildProcessSubagentRunner(deps, options);
4217
+ if (options.worktreeIsolation === false) return runner;
4218
+ return (0, import_agent_sdk7.createWorktreeSubagentRunner)({
4219
+ runner,
4220
+ worktreeAdapter: options.worktreeAdapter ?? createGitWorktreeIsolationAdapter(),
4221
+ hooks: deps.config.hooks,
4222
+ hookTypeExecutors: deps.hookTypeExecutors
4223
+ });
4224
+ };
4225
+ }
4226
+ var ChildProcessSubagentRunner = class {
4227
+ constructor(deps, options = {}) {
4228
+ this.deps = deps;
4229
+ this.workerPath = options.workerPath ?? resolveDefaultWorkerPath();
4230
+ this.execArgv = options.execArgv;
4231
+ this.killGraceMs = options.killGraceMs ?? DEFAULT_KILL_GRACE_MS2;
4232
+ this.providerConfig = options.providerConfig;
4233
+ this.env = options.env;
4234
+ this.logsDir = options.logsDir;
4235
+ }
4236
+ workerPath;
4237
+ execArgv;
4238
+ killGraceMs;
4239
+ providerConfig;
4240
+ env;
4241
+ logsDir;
4242
+ start(job) {
4243
+ const child = (0, import_node_child_process3.fork)(this.workerPath, [], {
4244
+ cwd: job.request.cwd,
4245
+ env: { ...process.env, ...this.env ?? {} },
4246
+ execArgv: this.execArgv ?? resolveDefaultExecArgv(this.workerPath),
4247
+ stdio: ["ignore", "ignore", "ignore", "ipc"]
4248
+ });
4249
+ const runtime = {
4250
+ job,
4251
+ child,
4252
+ killGraceMs: this.killGraceMs
4253
+ };
4254
+ const payload = this.createStartPayload(job);
4255
+ const workerResult = createChildProcessSubagentResult({
4256
+ runtime,
4257
+ payload,
4258
+ resolveTranscriptPath: (request) => this.resolveTranscriptPath(request)
4259
+ });
4260
+ const cancellation = createCancellationResult(job.jobId);
4261
+ void workerResult.catch(() => void 0);
4262
+ const result = Promise.race([workerResult, cancellation.promise]);
4263
+ const transcriptPath = this.resolveTranscriptPath(job);
4264
+ return {
4265
+ jobId: job.jobId,
4266
+ ...child.pid !== void 0 && { pid: child.pid },
4267
+ ...transcriptPath !== void 0 && { transcriptPath, logPath: transcriptPath },
4268
+ result,
4269
+ cancel: async (reason) => {
4270
+ cancellation.reject(reason);
4271
+ await cancelChildProcess(runtime, reason);
4272
+ },
4273
+ send: async (prompt) => {
4274
+ await sendWorkerMessage(child, { type: "send", prompt });
4275
+ },
4276
+ ...transcriptPath !== void 0 && {
4277
+ readLog: async (cursor) => readTranscriptLog(job.jobId, transcriptPath, cursor)
4278
+ }
4279
+ };
4280
+ }
4281
+ createStartPayload(job) {
4282
+ const definition = resolveAgentDefinition(job.request.type, this.deps.customAgentRegistry);
4283
+ return {
4284
+ jobId: job.jobId,
4285
+ request: job.request,
4286
+ agentDefinition: applyRequestOverrides(definition, job),
4287
+ parentConfig: this.deps.config,
4288
+ parentContext: this.deps.context,
4289
+ providerProfile: createProviderProfile(this.providerConfig, this.deps, job),
4290
+ permissionMode: this.deps.permissionMode,
4291
+ ...this.logsDir ? { logsDir: this.logsDir } : {}
4292
+ };
4293
+ }
4294
+ resolveTranscriptPath(job) {
4295
+ if (!this.logsDir) return void 0;
4296
+ return (0, import_node_path7.join)(this.logsDir, job.request.parentSessionId, "subagents", `${job.jobId}.jsonl`);
4297
+ }
4298
+ };
4299
+ function resolveAgentDefinition(agentType, customRegistry) {
4300
+ const definition = customRegistry?.(agentType) ?? (0, import_agent_sdk7.getBuiltInAgent)(agentType);
4301
+ if (!definition) {
4302
+ throw new import_agent_sdk7.BackgroundTaskError("validation", `Unknown agent type: ${agentType}`);
4303
+ }
4304
+ return definition;
4305
+ }
4306
+ function applyRequestOverrides(definition, job) {
4307
+ return {
4308
+ ...definition,
4309
+ ...job.request.model ? { model: job.request.model } : {},
4310
+ ...job.request.allowedTools ? { tools: job.request.allowedTools } : {},
4311
+ ...job.request.disallowedTools ? { disallowedTools: job.request.disallowedTools } : {}
4312
+ };
4313
+ }
4314
+ function createProviderProfile(providerConfig, deps, job) {
4315
+ const provider = providerConfig ?? deps.config.provider;
4316
+ return {
4317
+ profileName: deps.config.currentProvider,
4318
+ type: provider.name,
4319
+ model: job.request.model ?? provider.model,
4320
+ apiKey: provider.apiKey,
4321
+ baseURL: provider.baseURL,
4322
+ timeout: provider.timeout
4323
+ };
4324
+ }
4325
+ function resolveDefaultWorkerPath() {
4326
+ const entryPoint = process.argv[1] ?? "";
4327
+ const entryDir = entryPoint ? (0, import_node_path7.dirname)(entryPoint) : process.cwd();
4328
+ const extension = entryPoint.endsWith(".ts") || entryPoint.endsWith(".tsx") ? ".ts" : ".js";
4329
+ const candidates = [
4330
+ (0, import_node_path7.join)(entryDir, "subagents", `child-process-subagent-worker${extension}`),
4331
+ (0, import_node_path7.join)(entryDir, `child-process-subagent-worker${extension}`)
4332
+ ];
4333
+ for (const candidate of candidates) {
4334
+ if ((0, import_node_fs5.existsSync)(candidate)) {
4335
+ return candidate;
4336
+ }
4337
+ }
4338
+ return candidates[0];
4339
+ }
4340
+ function resolveDefaultExecArgv(workerPath) {
4341
+ if (!workerPath.endsWith(".ts")) {
4342
+ return process.execArgv;
4343
+ }
4344
+ if (process.execArgv.some((arg) => arg.includes("tsx"))) {
4345
+ return process.execArgv;
4346
+ }
4347
+ return [...process.execArgv, "--import", "tsx"];
4348
+ }
4349
+ function readTranscriptLog(jobId, transcriptPath, cursor) {
4350
+ const offset = cursor?.offset ?? 0;
4351
+ if (!(0, import_node_fs5.existsSync)(transcriptPath)) {
4352
+ return {
4353
+ taskId: jobId,
4354
+ cursor,
4355
+ lines: []
4356
+ };
2458
4357
  }
4358
+ const lines = (0, import_node_fs5.readFileSync)(transcriptPath, "utf8").split(/\r?\n/).filter(Boolean);
4359
+ const nextOffset = Math.min(offset + LOG_PAGE_SIZE2, lines.length);
4360
+ return {
4361
+ taskId: jobId,
4362
+ cursor,
4363
+ nextCursor: nextOffset < lines.length ? { offset: nextOffset } : void 0,
4364
+ lines: lines.slice(offset, nextOffset)
4365
+ };
2459
4366
  }
4367
+
4368
+ // src/cli.ts
4369
+ var import_meta = {};
2460
4370
  function readVersion() {
2461
4371
  try {
2462
4372
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
2463
- const dir = (0, import_node_path5.dirname)(thisFile);
2464
- const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
4373
+ const dir = (0, import_node_path8.dirname)(thisFile);
4374
+ const candidates = [(0, import_node_path8.join)(dir, "..", "..", "package.json"), (0, import_node_path8.join)(dir, "..", "package.json")];
2465
4375
  for (const pkgPath of candidates) {
2466
4376
  try {
2467
- const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
4377
+ const raw = (0, import_node_fs6.readFileSync)(pkgPath, "utf-8");
2468
4378
  const pkg = JSON.parse(raw);
2469
4379
  if (pkg.version !== void 0 && pkg.name !== void 0) {
2470
4380
  return pkg.version;
@@ -2512,63 +4422,6 @@ function promptInput(label, masked = false) {
2512
4422
  stdin.on("data", onData);
2513
4423
  });
2514
4424
  }
2515
- async function ensureConfig(cwd) {
2516
- const userPath = getUserSettingsPath();
2517
- const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
2518
- const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
2519
- const paths = [userPath, projectPath, localPath];
2520
- const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
2521
- if (checks.some((c) => c.status === "valid")) {
2522
- return;
2523
- }
2524
- const corrupt = checks.filter((c) => c.status === "corrupt");
2525
- const incomplete = checks.filter((c) => c.status === "incomplete");
2526
- process.stdout.write("\n");
2527
- if (corrupt.length > 0) {
2528
- for (const c of corrupt) {
2529
- process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
2530
- `);
2531
- }
2532
- process.stdout.write("\n");
2533
- }
2534
- if (incomplete.length > 0) {
2535
- for (const c of incomplete) {
2536
- process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
2537
- `);
2538
- }
2539
- process.stdout.write("\n");
2540
- }
2541
- if (corrupt.length === 0 && incomplete.length === 0) {
2542
- process.stdout.write(" Welcome to Robota CLI!\n");
2543
- process.stdout.write(" No configuration found. Let's set up.\n");
2544
- } else {
2545
- process.stdout.write(" Reconfiguring...\n");
2546
- }
2547
- process.stdout.write("\n");
2548
- const apiKey = await promptInput(" Anthropic API key: ", true);
2549
- if (!apiKey) {
2550
- process.stderr.write("\n No API key provided. Exiting.\n");
2551
- process.exit(1);
2552
- }
2553
- const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
2554
- const settingsDir = (0, import_node_path5.dirname)(userPath);
2555
- (0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
2556
- const settings = {
2557
- provider: {
2558
- name: "anthropic",
2559
- model: "claude-sonnet-4-6",
2560
- apiKey
2561
- }
2562
- };
2563
- if (language) {
2564
- settings.language = language;
2565
- }
2566
- (0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
2567
- process.stdout.write(`
2568
- Config saved to ${userPath}
2569
-
2570
- `);
2571
- }
2572
4425
  function resetConfig() {
2573
4426
  const userPath = getUserSettingsPath();
2574
4427
  if (deleteSettings(userPath)) {
@@ -2578,7 +4431,7 @@ function resetConfig() {
2578
4431
  process.stdout.write("No user settings found.\n");
2579
4432
  }
2580
4433
  }
2581
- async function startCli() {
4434
+ async function startCli(options = {}) {
2582
4435
  const args = parseCliArgs();
2583
4436
  if (args.version) {
2584
4437
  process.stdout.write(`robota ${readVersion()}
@@ -2590,11 +4443,32 @@ async function startCli() {
2590
4443
  return;
2591
4444
  }
2592
4445
  const cwd = process.cwd();
2593
- await ensureConfig(cwd);
2594
- const providerSettings = readProviderSettings(cwd);
4446
+ const providerDefinitions = options.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
4447
+ if (args.configure) {
4448
+ await runInteractiveProviderSetup(cwd, args, promptInput, providerDefinitions);
4449
+ return;
4450
+ }
4451
+ if (handleProviderConfigurationArgs(cwd, args, providerDefinitions)) {
4452
+ return;
4453
+ }
4454
+ try {
4455
+ await ensureConfig(cwd, args, promptInput, providerDefinitions);
4456
+ } catch (error) {
4457
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
4458
+ `);
4459
+ process.exit(1);
4460
+ }
4461
+ const providerOptions = args.provider ? { providerOverride: args.provider, providerDefinitions } : { providerDefinitions };
4462
+ const providerSettings = readProviderSettings(cwd, providerOptions);
2595
4463
  const modelId = args.model ?? providerSettings.model;
2596
- const provider = createProviderFromSettings(cwd, args.model);
2597
- const sessionStore = new import_agent_sessions.SessionStore();
4464
+ const provider = createProviderFromSettings(cwd, args.model, providerOptions);
4465
+ const backgroundTaskRunners = [createManagedShellProcessRunner()];
4466
+ const paths = (0, import_agent_sdk8.projectPaths)(cwd);
4467
+ const subagentRunnerFactory = createChildProcessSubagentRunnerFactory({
4468
+ providerConfig: { ...providerSettings, model: modelId },
4469
+ logsDir: paths.logs
4470
+ });
4471
+ const sessionStore = new import_agent_sessions.SessionStore(paths.sessions);
2598
4472
  let resumeSessionId;
2599
4473
  if (args.continueMode) {
2600
4474
  const sessions = sessionStore.list().filter((s) => s.cwd === cwd);
@@ -2629,13 +4503,27 @@ async function startCli() {
2629
4503
  process.stderr.write("Print mode (-p) requires a prompt argument.\n");
2630
4504
  process.exit(1);
2631
4505
  }
2632
- const session = new import_agent_sdk4.InteractiveSession({
4506
+ const appendParts = [];
4507
+ if (args.appendSystemPrompt) appendParts.push(args.appendSystemPrompt);
4508
+ if (args.jsonSchema)
4509
+ appendParts.push(
4510
+ `Respond with valid JSON only, matching this JSON schema:
4511
+ ${args.jsonSchema}`
4512
+ );
4513
+ const appendSystemPrompt = appendParts.length > 0 ? appendParts.join("\n\n") : void 0;
4514
+ const session = new import_agent_sdk8.InteractiveSession({
2633
4515
  cwd,
2634
4516
  provider,
2635
4517
  permissionMode: args.permissionMode ?? "bypassPermissions",
2636
4518
  maxTurns: args.maxTurns,
2637
- sessionStore,
2638
- sessionName: args.sessionName
4519
+ sessionStore: args.noSessionPersistence ? void 0 : sessionStore,
4520
+ sessionName: args.sessionName,
4521
+ bare: args.bare || void 0,
4522
+ allowedTools: args.allowedTools ? args.allowedTools.split(",").map((t) => t.trim()).filter((t) => t.length > 0) : void 0,
4523
+ appendSystemPrompt,
4524
+ backgroundTaskRunners,
4525
+ subagentRunnerFactory,
4526
+ commandModules: options.commandModules
2639
4527
  });
2640
4528
  const transport = (0, import_agent_transport_headless.createHeadlessTransport)({
2641
4529
  outputFormat: args.outputFormat ?? "text",
@@ -2643,6 +4531,7 @@ async function startCli() {
2643
4531
  });
2644
4532
  session.attachTransport(transport);
2645
4533
  await transport.start();
4534
+ await session.shutdown({ reason: "prompt_input_exit", message: "Headless transport complete" });
2646
4535
  process.exit(transport.getExitCode());
2647
4536
  }
2648
4537
  renderApp({
@@ -2656,10 +4545,19 @@ async function startCli() {
2656
4545
  sessionStore,
2657
4546
  resumeSessionId,
2658
4547
  forkSession: args.forkSession,
2659
- sessionName: args.sessionName
4548
+ sessionName: args.sessionName,
4549
+ backgroundTaskRunners,
4550
+ subagentRunnerFactory,
4551
+ commandModules: options.commandModules,
4552
+ providerDefinitions
2660
4553
  });
2661
4554
  }
2662
4555
  // Annotate the CommonJS export names for ESM import in node:
2663
4556
  0 && (module.exports = {
4557
+ ChildProcessSubagentRunner,
4558
+ GitWorktreeIsolationAdapter,
4559
+ createChildProcessSubagentRunnerFactory,
4560
+ createGitWorktreeIsolationAdapter,
4561
+ createManagedShellProcessRunner,
2664
4562
  startCli
2665
4563
  });