@robota-sdk/agent-cli 3.0.0-beta.43 → 3.0.0-beta.44

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.
@@ -1,17 +1,8 @@
1
1
  // src/cli.ts
2
- import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3
- import { join as join5, dirname as dirname3 } from "path";
2
+ import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3
+ import { join as join5, dirname as dirname2 } from "path";
4
4
  import { fileURLToPath } from "url";
5
- import {
6
- loadConfig,
7
- loadContext,
8
- detectProject,
9
- createSession,
10
- SessionStore,
11
- FileSessionLogger,
12
- projectPaths
13
- } from "@robota-sdk/agent-sdk";
14
- import { promptForApproval } from "@robota-sdk/agent-sdk";
5
+ import { InteractiveSession as InteractiveSession2 } from "@robota-sdk/agent-sdk";
15
6
 
16
7
  // src/utils/cli-args.ts
17
8
  import { parseArgs } from "util";
@@ -94,56 +85,49 @@ function deleteSettings(path) {
94
85
  return false;
95
86
  }
96
87
 
97
- // src/print-terminal.ts
98
- import * as readline from "readline";
99
- var PrintTerminal = class {
100
- write(text) {
101
- process.stdout.write(text);
102
- }
103
- writeLine(text) {
104
- process.stdout.write(text + "\n");
105
- }
106
- writeMarkdown(md) {
107
- process.stdout.write(md);
108
- }
109
- writeError(text) {
110
- process.stderr.write(text + "\n");
111
- }
112
- prompt(question) {
113
- return new Promise((resolve) => {
114
- const rl = readline.createInterface({
115
- input: process.stdin,
116
- output: process.stdout,
117
- terminal: false,
118
- historySize: 0
119
- });
120
- rl.question(question, (answer) => {
121
- rl.close();
122
- resolve(answer);
123
- });
124
- });
125
- }
126
- async select(options, initialIndex = 0) {
127
- for (let i = 0; i < options.length; i++) {
128
- const marker = i === initialIndex ? ">" : " ";
129
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
130
- `);
88
+ // src/utils/provider-factory.ts
89
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
90
+ import { join as join2 } from "path";
91
+ import { homedir } from "os";
92
+ import { AnthropicProvider } from "@robota-sdk/agent-provider-anthropic";
93
+ function readProviderSettings(cwd) {
94
+ const paths = [
95
+ join2(cwd, ".robota", "settings.local.json"),
96
+ join2(cwd, ".robota", "settings.json"),
97
+ join2(cwd, ".claude", "settings.local.json"),
98
+ join2(cwd, ".claude", "settings.json"),
99
+ join2(homedir(), ".robota", "settings.json"),
100
+ join2(homedir(), ".claude", "settings.json")
101
+ ];
102
+ for (const filePath of paths) {
103
+ if (!existsSync2(filePath)) continue;
104
+ try {
105
+ const raw = readFileSync2(filePath, "utf8");
106
+ const parsed = JSON.parse(raw);
107
+ const provider = parsed.provider;
108
+ if (provider?.apiKey && provider?.name) {
109
+ return {
110
+ name: provider.name,
111
+ model: provider.model ?? "claude-sonnet-4-6",
112
+ apiKey: provider.apiKey
113
+ };
114
+ }
115
+ } catch {
116
+ continue;
131
117
  }
132
- const answer = await this.prompt(
133
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
134
- );
135
- const trimmed = answer.trim().toLowerCase();
136
- if (trimmed === "") return initialIndex;
137
- const num = parseInt(trimmed, 10);
138
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
139
- return initialIndex;
140
118
  }
141
- spinner(_message) {
142
- return { stop() {
143
- }, update() {
144
- } };
119
+ throw new Error("No provider configuration found. Run `robota` to set up.");
120
+ }
121
+ function createProviderFromSettings(cwd, modelOverride) {
122
+ const settings = readProviderSettings(cwd);
123
+ const model = modelOverride ?? settings.model;
124
+ switch (settings.name) {
125
+ case "anthropic":
126
+ return new AnthropicProvider({ apiKey: settings.apiKey, defaultModel: model });
127
+ default:
128
+ throw new Error(`Unknown provider: ${settings.name}. Currently supported: anthropic`);
145
129
  }
146
- };
130
+ }
147
131
 
148
132
  // src/ui/render.tsx
149
133
  import { render } from "ink";
@@ -151,238 +135,148 @@ import { render } from "ink";
151
135
  // src/ui/App.tsx
152
136
  import { useState as useState9, useRef as useRef7 } from "react";
153
137
  import { Box as Box11, Text as Text13, useApp, useInput as useInput7 } from "ink";
154
- import { getModelName, createSystemMessage as createSystemMessage2 } from "@robota-sdk/agent-core";
138
+ import { getModelName, createSystemMessage as createSystemMessage2, messageToHistoryEntry as messageToHistoryEntry2 } from "@robota-sdk/agent-core";
155
139
 
156
140
  // src/ui/hooks/useInteractiveSession.ts
157
141
  import { useState, useRef, useCallback, useEffect } from "react";
158
- import { homedir } from "os";
142
+ import { homedir as homedir2 } from "os";
159
143
  import { join as join3 } from "path";
160
144
  import {
161
145
  InteractiveSession,
162
146
  CommandRegistry,
163
147
  BuiltinCommandSource,
164
148
  SkillCommandSource,
165
- SystemCommandExecutor,
166
- BundlePluginLoader
149
+ PluginCommandSource,
150
+ BundlePluginLoader,
151
+ buildSkillPrompt
167
152
  } from "@robota-sdk/agent-sdk";
168
- import { createSystemMessage } from "@robota-sdk/agent-core";
169
-
170
- // src/commands/plugin-source.ts
171
- var PluginCommandSource = class {
172
- name = "plugin";
173
- plugins;
174
- constructor(plugins) {
175
- this.plugins = plugins;
176
- }
177
- getCommands() {
178
- const commands = [];
179
- for (const plugin of this.plugins) {
180
- for (const skill of plugin.skills) {
181
- const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
182
- commands.push({
183
- name: baseName,
184
- description: `(${plugin.manifest.name}) ${skill.description}`,
185
- source: "plugin",
186
- skillContent: skill.skillContent,
187
- pluginDir: plugin.pluginDir
188
- });
189
- }
190
- for (const cmd of plugin.commands) {
191
- commands.push({
192
- name: cmd.name,
193
- description: cmd.description,
194
- source: "plugin",
195
- skillContent: cmd.skillContent,
196
- pluginDir: plugin.pluginDir
197
- });
198
- }
199
- }
200
- return commands;
201
- }
202
- };
153
+ import { createSystemMessage, messageToHistoryEntry } from "@robota-sdk/agent-core";
154
+ import { randomUUID } from "crypto";
203
155
 
204
- // src/utils/skill-prompt.ts
205
- import { execSync } from "child_process";
206
- function substituteVariables(content, args, context) {
207
- const argParts = args ? args.split(/\s+/) : [];
208
- let result = content;
209
- result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
210
- return argParts[Number(index)] ?? "";
211
- });
212
- result = result.replace(/\$ARGUMENTS/g, args);
213
- result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
214
- return argParts[Number(digit)] ?? "";
215
- });
216
- result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
217
- result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
218
- return result;
219
- }
220
- async function preprocessShellCommands(content) {
221
- const shellPattern = /!`([^`]+)`/g;
222
- if (!shellPattern.test(content)) {
223
- return content;
224
- }
225
- shellPattern.lastIndex = 0;
226
- let result = content;
227
- let match;
228
- const matches = [];
229
- while ((match = shellPattern.exec(content)) !== null) {
230
- matches.push({ full: match[0], command: match[1] });
231
- }
232
- for (const { full, command } of matches) {
233
- let output = "";
234
- try {
235
- output = execSync(command, {
236
- timeout: 5e3,
237
- encoding: "utf-8",
238
- stdio: ["pipe", "pipe", "pipe"]
239
- }).trimEnd();
240
- } catch {
241
- output = "";
156
+ // src/ui/tui-state-manager.ts
157
+ var MAX_RENDERED_MESSAGES = 100;
158
+ var TuiStateManager = class {
159
+ // ── Rendering state ───────────────────────────────────────────
160
+ history = [];
161
+ streamingText = "";
162
+ activeTools = [];
163
+ isThinking = false;
164
+ isAborting = false;
165
+ pendingPrompt = null;
166
+ contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
167
+ /** Called after any state change. React hook sets this to trigger re-render. */
168
+ onChange = null;
169
+ // ── Internal ──────────────────────────────────────────────────
170
+ streamBuf = "";
171
+ notify() {
172
+ this.onChange?.();
173
+ }
174
+ // ── Event handlers (InteractiveSession → state) ───────────────
175
+ onTextDelta = (delta) => {
176
+ this.streamBuf += delta;
177
+ this.streamingText = this.streamBuf;
178
+ this.notify();
179
+ };
180
+ onToolStart = (state) => {
181
+ this.activeTools = [...this.activeTools, state];
182
+ this.notify();
183
+ };
184
+ onToolEnd = (state) => {
185
+ this.activeTools = this.activeTools.map(
186
+ (t) => t.toolName === state.toolName && t.isRunning ? state : t
187
+ );
188
+ this.notify();
189
+ };
190
+ onThinking = (thinking) => {
191
+ this.isThinking = thinking;
192
+ if (thinking) {
193
+ this.streamBuf = "";
194
+ this.streamingText = "";
195
+ this.activeTools = [];
196
+ } else {
197
+ this.isAborting = false;
242
198
  }
243
- result = result.replace(full, output);
244
- }
245
- return result;
246
- }
247
- async function buildSkillPrompt(input, registry, context) {
248
- const parts = input.slice(1).split(/\s+/);
249
- const cmd = parts[0]?.toLowerCase() ?? "";
250
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
251
- if (!skillCmd) return null;
252
- const args = parts.slice(1).join(" ").trim();
253
- const userInstruction = args || skillCmd.description;
254
- if (skillCmd.skillContent) {
255
- let processed = await preprocessShellCommands(skillCmd.skillContent);
256
- processed = substituteVariables(processed, args, context);
257
- return `<skill name="${cmd}">
258
- ${processed}
259
- </skill>
260
-
261
- Execute the "${cmd}" skill: ${userInstruction}`;
262
- }
263
- return `Use the "${cmd}" skill: ${userInstruction}`;
264
- }
265
-
266
- // src/ui/hooks/plugin-hooks-merger.ts
267
- import { join as join2, dirname as dirname2 } from "path";
268
- function buildPluginEnv(plugin) {
269
- const dataDir = join2(dirname2(dirname2(plugin.pluginDir)), "data", plugin.manifest.name);
270
- return {
271
- CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
272
- CLAUDE_PLUGIN_PATH: plugin.pluginDir,
273
- CLAUDE_PLUGIN_DATA: dataDir
199
+ this.notify();
274
200
  };
275
- }
276
- function resolvePluginRoot(group, pluginDir) {
277
- if (Array.isArray(group.hooks)) {
278
- return {
279
- ...group,
280
- hooks: group.hooks.map((h) => {
281
- if (typeof h.command === "string") {
282
- return {
283
- ...h,
284
- command: h.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
285
- };
286
- }
287
- return h;
288
- })
201
+ onComplete = (result) => {
202
+ this.streamBuf = "";
203
+ this.streamingText = "";
204
+ this.activeTools = [];
205
+ this.contextState = {
206
+ percentage: result.contextState.usedPercentage,
207
+ usedTokens: result.contextState.usedTokens,
208
+ maxTokens: result.contextState.maxTokens
289
209
  };
210
+ this.notify();
211
+ };
212
+ onInterrupted = () => {
213
+ this.streamBuf = "";
214
+ this.streamingText = "";
215
+ this.activeTools = [];
216
+ this.notify();
217
+ };
218
+ onError = () => {
219
+ this.streamBuf = "";
220
+ this.streamingText = "";
221
+ this.activeTools = [];
222
+ this.notify();
223
+ };
224
+ // ── State updates from external sources ───────────────────────
225
+ /** Sync history from InteractiveSession */
226
+ syncHistory(entries) {
227
+ if (entries.length === 0) return;
228
+ this.history = entries.length > MAX_RENDERED_MESSAGES ? entries.slice(-MAX_RENDERED_MESSAGES) : [...entries];
229
+ this.notify();
230
+ }
231
+ /** Add a single history entry */
232
+ addEntry(entry) {
233
+ const updated = [...this.history, entry];
234
+ this.history = updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
235
+ this.notify();
236
+ }
237
+ /** Update pending prompt state */
238
+ setPendingPrompt(prompt) {
239
+ this.pendingPrompt = prompt;
240
+ this.notify();
241
+ }
242
+ /** Set aborting flag */
243
+ setAborting(aborting) {
244
+ this.isAborting = aborting;
245
+ this.notify();
246
+ }
247
+ /** Update context state */
248
+ setContextState(state) {
249
+ this.contextState = state;
250
+ this.notify();
290
251
  }
291
- return group;
292
- }
293
- function mergePluginHooks(plugins) {
294
- const merged = {};
295
- for (const plugin of plugins) {
296
- const hooksObj = plugin.hooks;
297
- if (!hooksObj) continue;
298
- const pluginEnv = buildPluginEnv(plugin);
299
- const innerHooks = hooksObj.hooks ?? hooksObj;
300
- for (const [event, groups] of Object.entries(innerHooks)) {
301
- if (!Array.isArray(groups)) continue;
302
- if (!merged[event]) merged[event] = [];
303
- const resolved = groups.map((group) => {
304
- const r = resolvePluginRoot(group, plugin.pluginDir);
305
- r.env = pluginEnv;
306
- return r;
307
- });
308
- merged[event].push(...resolved);
309
- }
310
- }
311
- return merged;
312
- }
313
- function mergeHooksIntoConfig(configHooks, pluginHooks) {
314
- const pluginKeys = Object.keys(pluginHooks);
315
- if (pluginKeys.length === 0) return configHooks;
316
- const merged = {};
317
- for (const [event, groups] of Object.entries(pluginHooks)) {
318
- merged[event] = [...groups];
319
- }
320
- if (configHooks) {
321
- for (const [event, groups] of Object.entries(configHooks)) {
322
- if (!Array.isArray(groups)) continue;
323
- if (!merged[event]) merged[event] = [];
324
- merged[event].push(...groups);
325
- }
326
- }
327
- return merged;
328
- }
252
+ };
329
253
 
330
254
  // src/ui/hooks/useInteractiveSession.ts
331
- var MAX_RENDERED_MESSAGES = 100;
332
255
  function initializeSession(props, permissionHandler) {
333
- const cwd = props.cwd ?? process.cwd();
256
+ const interactiveSession = new InteractiveSession({
257
+ cwd: props.cwd,
258
+ provider: props.provider,
259
+ permissionMode: props.permissionMode,
260
+ maxTurns: props.maxTurns,
261
+ permissionHandler
262
+ });
334
263
  const registry = new CommandRegistry();
335
264
  registry.addSource(new BuiltinCommandSource());
336
- registry.addSource(new SkillCommandSource(cwd));
337
- let pluginHooks = {};
338
- const pluginsDir = join3(homedir(), ".robota", "plugins");
265
+ registry.addSource(new SkillCommandSource(props.cwd));
266
+ const pluginsDir = join3(homedir2(), ".robota", "plugins");
339
267
  const loader = new BundlePluginLoader(pluginsDir);
340
268
  try {
341
269
  const plugins = loader.loadPluginsSync();
342
270
  if (plugins.length > 0) {
343
271
  registry.addSource(new PluginCommandSource(plugins));
344
- pluginHooks = mergePluginHooks(plugins);
345
272
  }
346
273
  } catch {
347
274
  }
348
- const mergedConfig = {
349
- ...props.config,
350
- hooks: mergeHooksIntoConfig(
351
- props.config.hooks,
352
- pluginHooks
353
- )
354
- };
355
- const interactiveSession = new InteractiveSession({
356
- config: mergedConfig,
357
- context: props.context,
358
- projectInfo: props.projectInfo,
359
- sessionStore: props.sessionStore,
360
- permissionMode: props.permissionMode,
361
- maxTurns: props.maxTurns,
362
- cwd,
363
- permissionHandler
364
- });
365
- return {
366
- interactiveSession,
367
- registry,
368
- commandExecutor: new SystemCommandExecutor(),
369
- pluginHooks
370
- };
275
+ const manager = new TuiStateManager();
276
+ return { interactiveSession, registry, manager };
371
277
  }
372
278
  function useInteractiveSession(props) {
373
- const [messages, setMessages] = useState([]);
374
- const addMessage = useCallback((msg) => {
375
- setMessages((prev) => {
376
- const updated = [...prev, msg];
377
- return updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
378
- });
379
- }, []);
380
- const [streamingText, setStreamingText] = useState("");
381
- const [activeTools, setActiveTools] = useState([]);
382
- const [isThinking, setIsThinking] = useState(false);
383
- const [isAborting, setIsAborting] = useState(false);
384
- const [pendingPrompt, setPendingPrompt] = useState(null);
385
- const [contextState, setContextState] = useState({ percentage: 0, usedTokens: 0, maxTokens: 0 });
279
+ const [, forceRender] = useState(0);
386
280
  const [permissionRequest, setPermissionRequest] = useState(null);
387
281
  const permissionQueueRef = useRef([]);
388
282
  const processingRef = useRef(false);
@@ -417,79 +311,54 @@ function useInteractiveSession(props) {
417
311
  if (stateRef.current === null) {
418
312
  stateRef.current = initializeSession(props, permissionHandler);
419
313
  }
420
- const { interactiveSession, registry, commandExecutor } = stateRef.current;
314
+ const { interactiveSession, registry, manager } = stateRef.current;
315
+ manager.onChange = () => forceRender((n) => n + 1);
421
316
  useEffect(() => {
422
- let streamBuf = "";
423
- const onTextDelta = (delta) => {
424
- streamBuf += delta;
425
- setStreamingText(streamBuf);
426
- };
427
- const onToolStart = (state) => {
428
- setActiveTools((prev) => [...prev, state]);
429
- };
430
- const onToolEnd = (state) => {
431
- setActiveTools(
432
- (prev) => prev.map((t) => t.toolName === state.toolName && t.isRunning ? state : t)
433
- );
434
- };
435
- const onThinking = (thinking) => {
436
- setIsThinking(thinking);
437
- if (thinking) {
438
- streamBuf = "";
439
- setStreamingText("");
440
- setActiveTools([]);
441
- } else {
442
- setIsAborting(false);
317
+ interactiveSession.on("text_delta", manager.onTextDelta);
318
+ interactiveSession.on("tool_start", manager.onToolStart);
319
+ interactiveSession.on("tool_end", manager.onToolEnd);
320
+ interactiveSession.on("thinking", manager.onThinking);
321
+ interactiveSession.on("complete", manager.onComplete);
322
+ interactiveSession.on("interrupted", manager.onInterrupted);
323
+ interactiveSession.on("error", manager.onError);
324
+ const initCheck = setInterval(() => {
325
+ try {
326
+ const ctx = interactiveSession.getContextState();
327
+ manager.setContextState({
328
+ percentage: ctx.usedPercentage,
329
+ usedTokens: ctx.usedTokens,
330
+ maxTokens: ctx.maxTokens
331
+ });
332
+ clearInterval(initCheck);
333
+ } catch {
443
334
  }
444
- };
445
- const onComplete = (result) => {
446
- setContextState({
447
- percentage: result.contextState.usedPercentage,
448
- usedTokens: result.contextState.usedTokens,
449
- maxTokens: result.contextState.maxTokens
450
- });
451
- };
452
- const onInterrupted = () => {
453
- };
454
- const onError = () => {
455
- };
456
- interactiveSession.on("text_delta", onTextDelta);
457
- interactiveSession.on("tool_start", onToolStart);
458
- interactiveSession.on("tool_end", onToolEnd);
459
- interactiveSession.on("thinking", onThinking);
460
- interactiveSession.on("complete", onComplete);
461
- interactiveSession.on("interrupted", onInterrupted);
462
- interactiveSession.on("error", onError);
335
+ }, 200);
463
336
  return () => {
464
- interactiveSession.off("text_delta", onTextDelta);
465
- interactiveSession.off("tool_start", onToolStart);
466
- interactiveSession.off("tool_end", onToolEnd);
467
- interactiveSession.off("thinking", onThinking);
468
- interactiveSession.off("complete", onComplete);
469
- interactiveSession.off("interrupted", onInterrupted);
470
- interactiveSession.off("error", onError);
337
+ clearInterval(initCheck);
338
+ interactiveSession.off("text_delta", manager.onTextDelta);
339
+ interactiveSession.off("tool_start", manager.onToolStart);
340
+ interactiveSession.off("tool_end", manager.onToolEnd);
341
+ interactiveSession.off("thinking", manager.onThinking);
342
+ interactiveSession.off("complete", manager.onComplete);
343
+ interactiveSession.off("interrupted", manager.onInterrupted);
344
+ interactiveSession.off("error", manager.onError);
471
345
  };
472
- }, [interactiveSession]);
346
+ }, [interactiveSession, manager]);
473
347
  useEffect(() => {
474
- if (!isThinking) {
475
- const sessionMessages = interactiveSession.getMessages();
476
- if (sessionMessages.length > 0) {
477
- setMessages(
478
- sessionMessages.length > MAX_RENDERED_MESSAGES ? sessionMessages.slice(-MAX_RENDERED_MESSAGES) : [...sessionMessages]
479
- );
480
- }
481
- setPendingPrompt(interactiveSession.getPendingPrompt());
348
+ manager.syncHistory(interactiveSession.getFullHistory());
349
+ if (!manager.isThinking) {
350
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
482
351
  }
483
- }, [isThinking, interactiveSession]);
352
+ }, [manager.isThinking, interactiveSession, manager]);
484
353
  const handleSubmit = useCallback(
485
354
  async (input) => {
486
355
  if (input.startsWith("/")) {
487
356
  const parts = input.slice(1).split(/\s+/);
488
357
  const cmd = parts[0]?.toLowerCase() ?? "";
489
358
  const args = parts.slice(1).join(" ");
490
- const result = await commandExecutor.execute(cmd, interactiveSession, args);
359
+ const result = await interactiveSession.executeCommand(cmd, args);
491
360
  if (result) {
492
- addMessage(createSystemMessage(result.message));
361
+ manager.addEntry(messageToHistoryEntry(createSystemMessage(result.message)));
493
362
  const effects = interactiveSession;
494
363
  if (result.data?.modelId) {
495
364
  effects._pendingModelId = result.data.modelId;
@@ -504,7 +373,7 @@ function useInteractiveSession(props) {
504
373
  return;
505
374
  }
506
375
  const ctx = interactiveSession.getContextState();
507
- setContextState({
376
+ manager.setContextState({
508
377
  percentage: ctx.usedPercentage,
509
378
  usedTokens: ctx.usedTokens,
510
379
  maxTokens: ctx.maxTokens
@@ -513,13 +382,23 @@ function useInteractiveSession(props) {
513
382
  }
514
383
  const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
515
384
  if (skillCmd) {
516
- addMessage(createSystemMessage(`Invoking ${skillCmd.source}: ${cmd}`));
385
+ manager.addEntry({
386
+ id: randomUUID(),
387
+ timestamp: /* @__PURE__ */ new Date(),
388
+ category: "event",
389
+ type: "skill-invocation",
390
+ data: {
391
+ skillName: cmd,
392
+ source: skillCmd.source,
393
+ message: `Invoking ${skillCmd.source}: ${cmd}`
394
+ }
395
+ });
517
396
  const prompt = await buildSkillPrompt(input, registry);
518
397
  if (prompt) {
519
398
  const qualifiedName = registry.resolveQualifiedName(cmd);
520
399
  const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
521
400
  await interactiveSession.submit(prompt, input, hookInput);
522
- setPendingPrompt(interactiveSession.getPendingPrompt());
401
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
523
402
  return;
524
403
  }
525
404
  }
@@ -531,45 +410,38 @@ function useInteractiveSession(props) {
531
410
  interactiveSession._triggerPluginTUI = true;
532
411
  return;
533
412
  }
534
- addMessage(createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`));
413
+ manager.addEntry(
414
+ messageToHistoryEntry(
415
+ createSystemMessage(`Unknown command "/${cmd}". Type /help for help.`)
416
+ )
417
+ );
535
418
  return;
536
419
  }
537
420
  await interactiveSession.submit(input);
538
- setPendingPrompt(interactiveSession.getPendingPrompt());
421
+ manager.setPendingPrompt(interactiveSession.getPendingPrompt());
539
422
  },
540
- [interactiveSession, commandExecutor, registry, addMessage]
423
+ [interactiveSession, registry, manager]
541
424
  );
542
425
  const handleAbort = useCallback(() => {
543
- setIsAborting(true);
426
+ manager.setAborting(true);
544
427
  interactiveSession.abort();
545
- }, [interactiveSession]);
428
+ }, [interactiveSession, manager]);
546
429
  const handleCancelQueue = useCallback(() => {
547
430
  interactiveSession.cancelQueue();
548
- setPendingPrompt(null);
549
- }, [interactiveSession]);
550
- if (contextState.maxTokens === 0) {
551
- const ctx = interactiveSession.getContextState();
552
- setContextState({
553
- percentage: ctx.usedPercentage,
554
- usedTokens: ctx.usedTokens,
555
- maxTokens: ctx.maxTokens
556
- });
557
- }
431
+ manager.setPendingPrompt(null);
432
+ }, [interactiveSession, manager]);
558
433
  return {
559
434
  interactiveSession,
560
435
  registry,
561
- commandExecutor,
562
- pluginHooks: stateRef.current.pluginHooks,
563
- messages,
564
- addMessage,
565
- setMessages,
566
- streamingText,
567
- activeTools,
568
- isThinking,
569
- isAborting,
570
- pendingPrompt,
436
+ history: manager.history,
437
+ addEntry: (entry) => manager.addEntry(entry),
438
+ streamingText: manager.streamingText,
439
+ activeTools: manager.activeTools,
440
+ isThinking: manager.isThinking,
441
+ isAborting: manager.isAborting,
442
+ pendingPrompt: manager.pendingPrompt,
571
443
  permissionRequest,
572
- contextState,
444
+ contextState: manager.contextState,
573
445
  handleSubmit,
574
446
  handleAbort,
575
447
  handleCancelQueue
@@ -578,7 +450,7 @@ function useInteractiveSession(props) {
578
450
 
579
451
  // src/ui/hooks/usePluginCallbacks.ts
580
452
  import { useMemo } from "react";
581
- import { homedir as homedir2 } from "os";
453
+ import { homedir as homedir3 } from "os";
582
454
  import { join as join4 } from "path";
583
455
  import {
584
456
  PluginSettingsStore,
@@ -588,7 +460,7 @@ import {
588
460
  } from "@robota-sdk/agent-sdk";
589
461
  function usePluginCallbacks(cwd) {
590
462
  return useMemo(() => {
591
- const home = homedir2();
463
+ const home = homedir3();
592
464
  const pluginsDir = join4(home, ".robota", "plugins");
593
465
  const userSettingsPath = join4(home, ".robota", "settings.json");
594
466
  const settingsStore = new PluginSettingsStore(userSettingsPath);
@@ -848,8 +720,45 @@ var MessageItem = React2.memo(function MessageItem2({
848
720
  /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: isAssistantMessage(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
849
721
  ] });
850
722
  });
851
- function MessageList({ messages }) {
852
- return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx(MessageItem, { message: msg }, msg.id)) });
723
+ function ToolSummaryEntry({ entry }) {
724
+ const data = entry.data;
725
+ const lines = data?.summary?.split("\n") ?? [];
726
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
727
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "white", bold: true, children: [
728
+ "Tool:",
729
+ " "
730
+ ] }) }),
731
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
732
+ lines.map((line, i) => /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
733
+ " ",
734
+ line
735
+ ] }, i))
736
+ ] });
737
+ }
738
+ function EventEntry({ entry }) {
739
+ const eventData = entry.data;
740
+ const eventMessage = typeof eventData?.message === "string" ? eventData.message : typeof eventData?.content === "string" ? eventData.content : entry.type;
741
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
742
+ /* @__PURE__ */ jsx(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: "yellow", bold: true, children: [
743
+ "System:",
744
+ " "
745
+ ] }) }),
746
+ /* @__PURE__ */ jsx(Text2, { children: " " }),
747
+ /* @__PURE__ */ jsx(Box2, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text2, { wrap: "wrap", children: eventMessage }) })
748
+ ] });
749
+ }
750
+ function EntryItem({ entry }) {
751
+ if (entry.category === "chat") {
752
+ const message = entry.data;
753
+ return /* @__PURE__ */ jsx(MessageItem, { message });
754
+ }
755
+ if (entry.type === "tool-summary") {
756
+ return /* @__PURE__ */ jsx(ToolSummaryEntry, { entry });
757
+ }
758
+ return /* @__PURE__ */ jsx(EventEntry, { entry });
759
+ }
760
+ function MessageList({ history }) {
761
+ return /* @__PURE__ */ jsx(Box2, { flexDirection: "column", children: history.map((entry) => /* @__PURE__ */ jsx(EntryItem, { entry }, entry.id)) });
853
762
  }
854
763
 
855
764
  // src/ui/StatusBar.tsx
@@ -1958,12 +1867,12 @@ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1958
1867
  var EXIT_DELAY_MS = 500;
1959
1868
  function App(props) {
1960
1869
  const { exit } = useApp();
1961
- const cwd = props.cwd ?? process.cwd();
1870
+ const cwd = props.cwd;
1962
1871
  const {
1963
1872
  interactiveSession,
1964
1873
  registry,
1965
- messages,
1966
- addMessage,
1874
+ history,
1875
+ addEntry,
1967
1876
  streamingText,
1968
1877
  activeTools,
1969
1878
  isThinking,
@@ -1975,13 +1884,10 @@ function App(props) {
1975
1884
  handleAbort,
1976
1885
  handleCancelQueue
1977
1886
  } = useInteractiveSession({
1978
- config: props.config,
1979
- context: props.context,
1980
- projectInfo: props.projectInfo,
1981
- sessionStore: props.sessionStore,
1887
+ cwd,
1888
+ provider: props.provider,
1982
1889
  permissionMode: props.permissionMode,
1983
- maxTurns: props.maxTurns,
1984
- cwd
1890
+ maxTurns: props.maxTurns
1985
1891
  });
1986
1892
  const pluginCallbacks = usePluginCallbacks(cwd);
1987
1893
  const [pendingModelId, setPendingModelId] = useState9(null);
@@ -2004,7 +1910,9 @@ function App(props) {
2004
1910
  const settings = readSettings(settingsPath);
2005
1911
  settings.language = lang;
2006
1912
  writeSettings(settingsPath, settings);
2007
- addMessage(createSystemMessage2(`Language set to "${lang}". Restarting...`));
1913
+ addEntry(
1914
+ messageToHistoryEntry2(createSystemMessage2(`Language set to "${lang}". Restarting...`))
1915
+ );
2008
1916
  setTimeout(() => exit(), EXIT_DELAY_MS);
2009
1917
  return;
2010
1918
  }
@@ -2012,9 +1920,9 @@ function App(props) {
2012
1920
  delete sideEffects._resetRequested;
2013
1921
  const settingsPath = getUserSettingsPath();
2014
1922
  if (deleteSettings(settingsPath)) {
2015
- addMessage(createSystemMessage2(`Deleted ${settingsPath}. Exiting...`));
1923
+ addEntry(messageToHistoryEntry2(createSystemMessage2(`Deleted ${settingsPath}. Exiting...`)));
2016
1924
  } else {
2017
- addMessage(createSystemMessage2("No user settings found."));
1925
+ addEntry(messageToHistoryEntry2(createSystemMessage2("No user settings found.")));
2018
1926
  }
2019
1927
  setTimeout(() => exit(), EXIT_DELAY_MS);
2020
1928
  return;
@@ -2038,7 +1946,14 @@ function App(props) {
2038
1946
  },
2039
1947
  { isActive: !permissionRequest && !showPluginTUI }
2040
1948
  );
2041
- const session = interactiveSession.getSession();
1949
+ let permissionMode = props.permissionMode ?? "default";
1950
+ let sessionId = "";
1951
+ try {
1952
+ const session = interactiveSession.getSession();
1953
+ permissionMode = session.getPermissionMode();
1954
+ sessionId = session.getSessionId();
1955
+ } catch {
1956
+ }
2042
1957
  return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
2043
1958
  /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2044
1959
  /* @__PURE__ */ jsx13(Text13, { color: "cyan", bold: true, children: `
@@ -2054,7 +1969,7 @@ function App(props) {
2054
1969
  ] })
2055
1970
  ] }),
2056
1971
  /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2057
- /* @__PURE__ */ jsx13(MessageList, { messages }),
1972
+ /* @__PURE__ */ jsx13(MessageList, { history }),
2058
1973
  (isThinking || activeTools.length > 0) && /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx13(StreamingIndicator, { text: streamingText, activeTools }) })
2059
1974
  ] }),
2060
1975
  permissionRequest && /* @__PURE__ */ jsx13(PermissionPrompt, { request: permissionRequest }),
@@ -2069,21 +1984,25 @@ function App(props) {
2069
1984
  try {
2070
1985
  const settingsPath = getUserSettingsPath();
2071
1986
  updateModelInSettings(settingsPath, pendingModelId);
2072
- addMessage(
2073
- createSystemMessage2(
2074
- `Model changed to ${getModelName(pendingModelId)}. Restarting...`
1987
+ addEntry(
1988
+ messageToHistoryEntry2(
1989
+ createSystemMessage2(
1990
+ `Model changed to ${getModelName(pendingModelId)}. Restarting...`
1991
+ )
2075
1992
  )
2076
1993
  );
2077
1994
  setTimeout(() => exit(), EXIT_DELAY_MS);
2078
1995
  } catch (err) {
2079
- addMessage(
2080
- createSystemMessage2(
2081
- `Failed: ${err instanceof Error ? err.message : String(err)}`
1996
+ addEntry(
1997
+ messageToHistoryEntry2(
1998
+ createSystemMessage2(
1999
+ `Failed: ${err instanceof Error ? err.message : String(err)}`
2000
+ )
2082
2001
  )
2083
2002
  );
2084
2003
  }
2085
2004
  } else {
2086
- addMessage(createSystemMessage2("Model change cancelled."));
2005
+ addEntry(messageToHistoryEntry2(createSystemMessage2("Model change cancelled.")));
2087
2006
  }
2088
2007
  }
2089
2008
  }
@@ -2093,16 +2012,16 @@ function App(props) {
2093
2012
  {
2094
2013
  callbacks: pluginCallbacks,
2095
2014
  onClose: () => setShowPluginTUI(false),
2096
- addMessage: (msg) => addMessage(createSystemMessage2(msg.content))
2015
+ addMessage: (msg) => addEntry(messageToHistoryEntry2(createSystemMessage2(msg.content)))
2097
2016
  }
2098
2017
  ),
2099
2018
  /* @__PURE__ */ jsx13(
2100
2019
  StatusBar,
2101
2020
  {
2102
- permissionMode: session.getPermissionMode(),
2103
- modelName: getModelName(props.config.provider.model),
2104
- sessionId: session.getSessionId(),
2105
- messageCount: messages.length,
2021
+ permissionMode,
2022
+ modelName: props.modelId ? getModelName(props.modelId) : "",
2023
+ sessionId,
2024
+ messageCount: history.length,
2106
2025
  isThinking,
2107
2026
  contextPercentage: contextState.percentage,
2108
2027
  contextUsedTokens: contextState.usedTokens,
@@ -2159,9 +2078,9 @@ function renderApp(options) {
2159
2078
 
2160
2079
  // src/cli.ts
2161
2080
  function checkSettingsFile(filePath) {
2162
- if (!existsSync2(filePath)) return "missing";
2081
+ if (!existsSync3(filePath)) return "missing";
2163
2082
  try {
2164
- const raw = readFileSync2(filePath, "utf8").trim();
2083
+ const raw = readFileSync3(filePath, "utf8").trim();
2165
2084
  if (raw.length === 0) return "incomplete";
2166
2085
  const parsed = JSON.parse(raw);
2167
2086
  const provider = parsed.provider;
@@ -2174,11 +2093,11 @@ function checkSettingsFile(filePath) {
2174
2093
  function readVersion() {
2175
2094
  try {
2176
2095
  const thisFile = fileURLToPath(import.meta.url);
2177
- const dir = dirname3(thisFile);
2096
+ const dir = dirname2(thisFile);
2178
2097
  const candidates = [join5(dir, "..", "..", "package.json"), join5(dir, "..", "package.json")];
2179
2098
  for (const pkgPath of candidates) {
2180
2099
  try {
2181
- const raw = readFileSync2(pkgPath, "utf-8");
2100
+ const raw = readFileSync3(pkgPath, "utf-8");
2182
2101
  const pkg = JSON.parse(raw);
2183
2102
  if (pkg.version !== void 0 && pkg.name !== void 0) {
2184
2103
  return pkg.version;
@@ -2265,7 +2184,7 @@ async function ensureConfig(cwd) {
2265
2184
  process.exit(1);
2266
2185
  }
2267
2186
  const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
2268
- const settingsDir = dirname3(userPath);
2187
+ const settingsDir = dirname2(userPath);
2269
2188
  mkdirSync2(settingsDir, { recursive: true });
2270
2189
  const settings = {
2271
2190
  provider: {
@@ -2305,44 +2224,40 @@ async function startCli() {
2305
2224
  }
2306
2225
  const cwd = process.cwd();
2307
2226
  await ensureConfig(cwd);
2308
- const [config, context, projectInfo] = await Promise.all([
2309
- loadConfig(cwd),
2310
- loadContext(cwd),
2311
- detectProject(cwd)
2312
- ]);
2313
- if (args.model !== void 0) {
2314
- config.provider.model = args.model;
2315
- }
2316
- if (args.language !== void 0) {
2317
- config.language = args.language;
2318
- }
2319
- const sessionStore = new SessionStore();
2227
+ const providerSettings = readProviderSettings(cwd);
2228
+ const modelId = args.model ?? providerSettings.model;
2229
+ const provider = createProviderFromSettings(cwd, args.model);
2320
2230
  if (args.printMode) {
2321
2231
  const prompt = args.positional.join(" ").trim();
2322
2232
  if (prompt.length === 0) {
2323
2233
  process.stderr.write("Print mode (-p) requires a prompt argument.\n");
2324
2234
  process.exit(1);
2325
2235
  }
2326
- const terminal = new PrintTerminal();
2327
- const paths = projectPaths(cwd);
2328
- const session = createSession({
2329
- config,
2330
- context,
2331
- terminal,
2332
- sessionLogger: new FileSessionLogger(paths.logs),
2333
- projectInfo,
2334
- permissionMode: args.permissionMode,
2335
- promptForApproval
2236
+ const session = new InteractiveSession2({
2237
+ cwd,
2238
+ provider,
2239
+ permissionMode: args.permissionMode ?? "bypassPermissions",
2240
+ maxTurns: args.maxTurns
2241
+ });
2242
+ await new Promise((resolve, reject) => {
2243
+ session.on("complete", (result) => {
2244
+ process.stdout.write(result.response + "\n");
2245
+ resolve();
2246
+ });
2247
+ session.on("interrupted", (result) => {
2248
+ if (result.response) process.stdout.write(result.response + "\n");
2249
+ resolve();
2250
+ });
2251
+ session.on("error", (err) => reject(err));
2252
+ session.submit(prompt).catch(reject);
2336
2253
  });
2337
- const response = await session.run(prompt);
2338
- process.stdout.write(response + "\n");
2339
2254
  return;
2340
2255
  }
2341
2256
  renderApp({
2342
- config,
2343
- context,
2344
- projectInfo,
2345
- sessionStore,
2257
+ cwd,
2258
+ provider,
2259
+ modelId,
2260
+ language: args.language,
2346
2261
  permissionMode: args.permissionMode,
2347
2262
  maxTurns: args.maxTurns,
2348
2263
  version: readVersion()