@robota-sdk/agent-sdk 3.0.0-beta.52 → 3.0.0-beta.53

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,247 +1,632 @@
1
- // src/hooks/prompt-executor.ts
2
- function extractJson(raw) {
3
- const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
4
- if (codeBlockMatch) {
5
- return codeBlockMatch[1].trim();
6
- }
7
- return raw.trim();
8
- }
9
- var PromptExecutor = class {
10
- type = "prompt";
11
- providerFactory;
12
- defaultModel;
13
- constructor(options) {
14
- this.providerFactory = options.providerFactory;
15
- this.defaultModel = options.defaultModel;
16
- }
17
- async execute(definition, input) {
18
- const promptDef = definition;
19
- const model = promptDef.model ?? this.defaultModel;
20
- try {
21
- const provider = this.providerFactory(model);
22
- const prompt = `${promptDef.prompt}
23
-
24
- Context:
25
- ${JSON.stringify(input)}
1
+ // src/interactive/interactive-session.ts
2
+ import {
3
+ createUserMessage,
4
+ createAssistantMessage,
5
+ createSystemMessage,
6
+ messageToHistoryEntry
7
+ } from "@robota-sdk/agent-core";
26
8
 
27
- Respond with JSON: { "ok": boolean, "reason"?: string }`;
28
- const rawResponse = await provider.complete(prompt);
29
- const jsonStr = extractJson(rawResponse);
30
- let parsed;
31
- try {
32
- parsed = JSON.parse(jsonStr);
33
- } catch {
9
+ // src/commands/system-command.ts
10
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
11
+ function createSystemCommands() {
12
+ return [
13
+ {
14
+ name: "help",
15
+ description: "Show available commands",
16
+ execute: (_session, _args) => ({
17
+ message: [
18
+ "Available commands:",
19
+ " help \u2014 Show this help",
20
+ " clear \u2014 Clear conversation",
21
+ " compact [instr] \u2014 Compact context (optional focus instructions)",
22
+ " mode [m] \u2014 Show/change permission mode",
23
+ " model <id> \u2014 Change AI model",
24
+ " language <code> \u2014 Set response language (ko, en, ja, zh)",
25
+ " cost \u2014 Show session info",
26
+ " context \u2014 Context window info",
27
+ " permissions \u2014 Permission rules",
28
+ " resume \u2014 Resume a previous session",
29
+ " rename <name> \u2014 Rename the current session",
30
+ " reset \u2014 Delete settings and exit"
31
+ ].join("\n"),
32
+ success: true
33
+ })
34
+ },
35
+ {
36
+ name: "clear",
37
+ description: "Clear conversation history",
38
+ execute: (session, _args) => {
39
+ const underlying = session.getSession();
40
+ underlying.clearHistory();
41
+ return { message: "Conversation cleared.", success: true };
42
+ }
43
+ },
44
+ {
45
+ name: "compact",
46
+ description: "Compress context window",
47
+ execute: async (session, args) => {
48
+ const underlying = session.getSession();
49
+ const instructions = args.trim() || void 0;
50
+ const before = underlying.getContextState().usedPercentage;
51
+ await underlying.compact(instructions);
52
+ const after = underlying.getContextState().usedPercentage;
34
53
  return {
35
- exitCode: 1,
36
- stdout: "",
37
- stderr: `Failed to parse AI response as JSON: ${rawResponse}`
54
+ message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
55
+ success: true,
56
+ data: { before, after }
38
57
  };
39
58
  }
40
- if (parsed.ok) {
41
- return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
59
+ },
60
+ {
61
+ name: "mode",
62
+ description: "Show/change permission mode",
63
+ execute: (session, args) => {
64
+ const underlying = session.getSession();
65
+ const arg = args.trim().split(/\s+/)[0];
66
+ if (!arg) {
67
+ return {
68
+ message: `Current mode: ${underlying.getPermissionMode()}`,
69
+ success: true,
70
+ data: { mode: underlying.getPermissionMode() }
71
+ };
72
+ }
73
+ if (VALID_MODES.includes(arg)) {
74
+ underlying.setPermissionMode(arg);
75
+ return {
76
+ message: `Permission mode set to: ${arg}`,
77
+ success: true,
78
+ data: { mode: arg }
79
+ };
80
+ }
81
+ return {
82
+ message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
83
+ success: false
84
+ };
42
85
  }
43
- return {
44
- exitCode: 2,
45
- stdout: "",
46
- stderr: parsed.reason ?? "Blocked by prompt hook"
47
- };
48
- } catch (err) {
49
- const message = err instanceof Error ? err.message : String(err);
50
- return { exitCode: 1, stdout: "", stderr: message };
51
- }
52
- }
53
- };
54
-
55
- // src/hooks/agent-executor.ts
56
- var DEFAULT_MAX_TURNS = 50;
57
- var DEFAULT_TIMEOUT_SECONDS = 60;
58
- function extractJson2(raw) {
59
- const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
60
- if (codeBlockMatch) {
61
- return codeBlockMatch[1].trim();
62
- }
63
- return raw.trim();
64
- }
65
- var AgentExecutor = class {
66
- type = "agent";
67
- sessionFactory;
68
- constructor(options) {
69
- this.sessionFactory = options.sessionFactory;
70
- }
71
- async execute(definition, input) {
72
- const agentDef = definition;
73
- const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
74
- const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
75
- try {
76
- const session = this.sessionFactory({ maxTurns, timeout });
77
- const prompt = `Hook input:
78
- ${JSON.stringify(input)}
79
-
80
- Respond with JSON: { "ok": boolean, "reason"?: string }`;
81
- const rawResponse = await session.run(prompt);
82
- const jsonStr = extractJson2(rawResponse);
83
- let parsed;
84
- try {
85
- parsed = JSON.parse(jsonStr);
86
- } catch {
86
+ },
87
+ {
88
+ name: "model",
89
+ description: "Change AI model",
90
+ execute: (_session, args) => {
91
+ const modelId = args.trim().split(/\s+/)[0];
92
+ if (!modelId) {
93
+ return { message: "Usage: model <model-id>", success: false };
94
+ }
87
95
  return {
88
- exitCode: 1,
89
- stdout: "",
90
- stderr: `Failed to parse agent response as JSON: ${rawResponse}`
96
+ message: `Model change requested: ${modelId}`,
97
+ success: true,
98
+ data: { modelId }
91
99
  };
92
100
  }
93
- if (parsed.ok) {
94
- return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
101
+ },
102
+ {
103
+ name: "language",
104
+ description: "Set response language",
105
+ execute: (_session, args) => {
106
+ const lang = args.trim().split(/\s+/)[0];
107
+ if (!lang) {
108
+ return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
109
+ }
110
+ return {
111
+ message: `Language set to "${lang}".`,
112
+ success: true,
113
+ data: { language: lang }
114
+ };
95
115
  }
96
- return {
97
- exitCode: 2,
98
- stdout: "",
99
- stderr: parsed.reason ?? "Blocked by agent hook"
100
- };
101
- } catch (err) {
102
- const message = err instanceof Error ? err.message : String(err);
103
- return { exitCode: 1, stdout: "", stderr: message };
104
- }
105
- }
106
- };
107
-
108
- // src/assembly/create-session.ts
109
- import { Session as Session2 } from "@robota-sdk/agent-sessions";
110
-
111
- // src/context/system-prompt-builder.ts
112
- var TRUST_LEVEL_DESCRIPTIONS = {
113
- safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
114
- moderate: "moderate (default mode \u2014 write and bash tools require approval)",
115
- full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
116
- };
117
- function buildProjectSection(info) {
118
- const lines = ["## Current Project"];
119
- if (info.name !== void 0) {
120
- lines.push(`- **Name:** ${info.name}`);
121
- }
122
- if (info.type !== "unknown") {
123
- lines.push(`- **Type:** ${info.type}`);
124
- }
125
- if (info.language !== "unknown") {
126
- lines.push(`- **Language:** ${info.language}`);
127
- }
128
- if (info.packageManager !== void 0) {
129
- lines.push(`- **Package manager:** ${info.packageManager}`);
130
- }
131
- return lines.join("\n");
132
- }
133
- function buildToolsSection(descriptions) {
134
- if (descriptions.length === 0) {
135
- return "";
136
- }
137
- const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
138
- return lines.join("\n");
139
- }
140
- function buildSkillsSection(skills) {
141
- const invocable = skills.filter((s) => s.disableModelInvocation !== true);
142
- if (invocable.length === 0) {
143
- return "";
144
- }
145
- const lines = [
146
- "## Skills",
147
- "The following skills are available:",
148
- "",
149
- ...invocable.map((s) => `- ${s.name}: ${s.description}`)
150
- ];
151
- return lines.join("\n");
152
- }
153
- function buildSystemPrompt(params) {
154
- const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
155
- const sections = [];
156
- const roleLines = [
157
- "## Role",
158
- "You are an AI coding assistant with access to tools that let you read and modify code.",
159
- "You help developers understand, write, and improve their codebase.",
160
- "Always be precise, follow existing code conventions, and prefer minimal changes."
116
+ },
117
+ {
118
+ name: "cost",
119
+ description: "Show session info",
120
+ execute: (session, _args) => {
121
+ const underlying = session.getSession();
122
+ const sessionId = underlying.getSessionId();
123
+ const messageCount = underlying.getMessageCount();
124
+ return {
125
+ message: `Session: ${sessionId}
126
+ Messages: ${messageCount}`,
127
+ success: true,
128
+ data: { sessionId, messageCount }
129
+ };
130
+ }
131
+ },
132
+ {
133
+ name: "context",
134
+ description: "Context window info",
135
+ execute: (session, _args) => {
136
+ const ctx = session.getContextState();
137
+ return {
138
+ message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
139
+ success: true,
140
+ data: {
141
+ usedTokens: ctx.usedTokens,
142
+ maxTokens: ctx.maxTokens,
143
+ percentage: ctx.usedPercentage
144
+ }
145
+ };
146
+ }
147
+ },
148
+ {
149
+ name: "permissions",
150
+ description: "Show permission rules",
151
+ execute: (session, _args) => {
152
+ const underlying = session.getSession();
153
+ const mode = underlying.getPermissionMode();
154
+ const sessionAllowed = underlying.getSessionAllowedTools();
155
+ const lines = [`Permission mode: ${mode}`];
156
+ if (sessionAllowed.length > 0) {
157
+ lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
158
+ } else {
159
+ lines.push("No session-approved tools.");
160
+ }
161
+ return {
162
+ message: lines.join("\n"),
163
+ success: true,
164
+ data: { mode, sessionAllowed }
165
+ };
166
+ }
167
+ },
168
+ {
169
+ name: "resume",
170
+ description: "Resume a previous session",
171
+ execute: (_session, _args) => ({
172
+ message: "Opening session picker...",
173
+ success: true,
174
+ data: { triggerResumePicker: true }
175
+ })
176
+ },
177
+ {
178
+ name: "rename",
179
+ description: "Rename the current session",
180
+ execute: (_session, args) => {
181
+ const name = args.trim();
182
+ if (!name) {
183
+ return { message: "Usage: rename <name>", success: false };
184
+ }
185
+ return {
186
+ message: `Session renamed to "${name}".`,
187
+ success: true,
188
+ data: { name }
189
+ };
190
+ }
191
+ },
192
+ {
193
+ name: "reset",
194
+ description: "Delete settings",
195
+ execute: (_session, _args) => {
196
+ return {
197
+ message: "Reset requested.",
198
+ success: true,
199
+ data: { resetRequested: true }
200
+ };
201
+ }
202
+ }
161
203
  ];
162
- if (language) {
163
- roleLines.push(
164
- `Always respond in ${language}. Use ${language} for all explanations and communications.`
165
- );
204
+ }
205
+ var SystemCommandExecutor = class {
206
+ commands;
207
+ constructor(commands) {
208
+ this.commands = /* @__PURE__ */ new Map();
209
+ for (const cmd of commands ?? createSystemCommands()) {
210
+ this.commands.set(cmd.name, cmd);
211
+ }
166
212
  }
167
- sections.push(roleLines.join("\n"));
168
- if (cwd) {
169
- sections.push(`## Working Directory
170
- \`${cwd}\``);
213
+ /** Register an additional command. */
214
+ register(command) {
215
+ this.commands.set(command.name, command);
171
216
  }
172
- sections.push(buildProjectSection(projectInfo));
173
- sections.push(
174
- [
175
- "## Permission Mode",
176
- `Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
177
- ].join("\n")
178
- );
179
- if (agentsMd.trim().length > 0) {
180
- sections.push(["## Agent Instructions", agentsMd].join("\n"));
217
+ /** Execute a command by name. Returns null if command not found. */
218
+ async execute(name, session, args) {
219
+ const cmd = this.commands.get(name);
220
+ if (!cmd) return null;
221
+ return await cmd.execute(session, args);
181
222
  }
182
- if (claudeMd.trim().length > 0) {
183
- sections.push(["## Project Notes", claudeMd].join("\n"));
223
+ /** List all registered commands. */
224
+ listCommands() {
225
+ return [...this.commands.values()];
184
226
  }
185
- sections.push(
186
- [
187
- "## Web Search",
188
- "You have access to web search. When the user asks to search, look up, or find current/latest information,",
189
- "you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
190
- "Always prefer web search for: news, latest versions, current events, live documentation."
191
- ].join("\n")
192
- );
193
- const toolsSection = buildToolsSection(toolDescriptions);
194
- if (toolsSection.length > 0) {
195
- sections.push(toolsSection);
227
+ /** Check if a command exists. */
228
+ hasCommand(name) {
229
+ return this.commands.has(name);
196
230
  }
197
- if (params.skills !== void 0 && params.skills.length > 0) {
198
- const skillsSection = buildSkillsSection(params.skills);
199
- if (skillsSection.length > 0) {
200
- sections.push(skillsSection);
231
+ };
232
+
233
+ // src/interactive/interactive-session-execution.ts
234
+ function isAbortError(err) {
235
+ return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
236
+ }
237
+ function extractToolSummaries(history, historyBefore) {
238
+ const summaries = [];
239
+ for (let i = historyBefore; i < history.length; i++) {
240
+ const msg = history[i];
241
+ if (msg?.role === "assistant" && msg.toolCalls) {
242
+ for (const tc of msg.toolCalls) {
243
+ summaries.push({ name: tc.function.name, args: tc.function.arguments });
244
+ }
201
245
  }
202
246
  }
203
- return sections.join("\n\n");
247
+ return summaries;
204
248
  }
205
-
206
- // src/assembly/create-tools.ts
207
- import {
208
- bashTool,
209
- readTool,
210
- writeTool,
211
- editTool,
212
- globTool,
213
- grepTool,
214
- webFetchTool,
215
- webSearchTool
216
- } from "@robota-sdk/agent-tools";
217
- var DEFAULT_TOOL_DESCRIPTIONS = [
218
- "Bash \u2014 execute shell commands",
219
- "Read \u2014 read file contents with line numbers",
220
- "Write \u2014 write content to a file",
221
- "Edit \u2014 replace a string in a file",
222
- "Glob \u2014 find files matching a pattern",
223
- "Grep \u2014 search file contents with regex",
224
- "WebSearch \u2014 search the internet (Anthropic built-in)"
225
- ];
226
- function createDefaultTools() {
227
- return [
228
- bashTool,
229
- readTool,
230
- writeTool,
231
- editTool,
232
- globTool,
233
- grepTool,
234
- webFetchTool,
235
- webSearchTool
236
- ];
249
+ function buildResult(response, sessionHistory, interactiveHistory, historyBefore, contextState) {
250
+ const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
251
+ return {
252
+ response,
253
+ history: interactiveHistory,
254
+ toolSummaries,
255
+ contextState
256
+ };
257
+ }
258
+ function buildInterruptedResult(sessionHistory, interactiveHistory, historyBefore, contextState) {
259
+ const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
260
+ const parts = [];
261
+ for (let i = historyBefore; i < sessionHistory.length; i++) {
262
+ const msg = sessionHistory[i];
263
+ if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
264
+ }
265
+ return {
266
+ response: parts.join("\n\n"),
267
+ history: interactiveHistory,
268
+ toolSummaries,
269
+ contextState
270
+ };
271
+ }
272
+ function persistSession(sessionStore, session, sessionName, cwd, history) {
273
+ try {
274
+ const sessionId = session.getSessionId();
275
+ const existing = sessionStore.load(sessionId);
276
+ sessionStore.save({
277
+ id: sessionId,
278
+ name: sessionName ?? existing?.name,
279
+ cwd,
280
+ createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
281
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
282
+ messages: session.getHistory(),
283
+ history
284
+ });
285
+ } catch {
286
+ }
237
287
  }
288
+ var NOOP_TERMINAL = {
289
+ write: () => {
290
+ },
291
+ writeLine: () => {
292
+ },
293
+ writeMarkdown: () => {
294
+ },
295
+ writeError: () => {
296
+ },
297
+ prompt: () => Promise.resolve(""),
298
+ select: () => Promise.resolve(0),
299
+ spinner: () => ({ stop: () => {
300
+ }, update: () => {
301
+ } })
302
+ };
238
303
 
239
- // src/tools/agent-tool.ts
240
- import { z } from "zod";
241
- import { createZodFunctionTool } from "@robota-sdk/agent-tools";
304
+ // src/interactive/interactive-session-streaming.ts
305
+ import { randomUUID } from "crypto";
306
+ var TOOL_ARG_DISPLAY_MAX = 80;
307
+ var TAIL_KEEP = 30;
308
+ var MAX_COMPLETED_TOOLS = 50;
309
+ var STREAMING_FLUSH_INTERVAL_MS = 16;
310
+ function extractFirstArg(toolArgs) {
311
+ if (!toolArgs) return "";
312
+ const firstVal = Object.values(toolArgs)[0];
313
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
314
+ return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
315
+ }
316
+ function pushToolSummaryToHistory(state) {
317
+ if (state.activeTools.length === 0) return;
318
+ const summary = state.activeTools.map((t) => {
319
+ const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
320
+ return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
321
+ }).join("\n");
322
+ state.history.push({
323
+ id: randomUUID(),
324
+ timestamp: /* @__PURE__ */ new Date(),
325
+ category: "event",
326
+ type: "tool-summary",
327
+ data: {
328
+ tools: state.activeTools.map((t) => ({
329
+ toolName: t.toolName,
330
+ firstArg: t.firstArg,
331
+ isRunning: t.isRunning,
332
+ result: t.result
333
+ })),
334
+ summary
335
+ }
336
+ });
337
+ }
338
+ function trimCompletedTools(activeTools) {
339
+ const completed = activeTools.filter((t) => !t.isRunning);
340
+ if (completed.length <= MAX_COMPLETED_TOOLS) return activeTools;
341
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
342
+ let removed = 0;
343
+ return activeTools.filter((t) => {
344
+ if (!t.isRunning && removed < excess) {
345
+ removed++;
346
+ return false;
347
+ }
348
+ return true;
349
+ });
350
+ }
351
+ function applyToolStart(state, event) {
352
+ const firstArg = extractFirstArg(event.toolArgs);
353
+ const toolState = { toolName: event.toolName, firstArg, isRunning: true };
354
+ state.activeTools.push(toolState);
355
+ state.history.push({
356
+ id: randomUUID(),
357
+ timestamp: /* @__PURE__ */ new Date(),
358
+ category: "event",
359
+ type: "tool-start",
360
+ data: { toolName: event.toolName, firstArg, isRunning: true }
361
+ });
362
+ return toolState;
363
+ }
364
+ function applyToolEnd(state, event) {
365
+ const result = event.denied ? "denied" : event.success === false ? "error" : "success";
366
+ const idx = state.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
367
+ if (idx === -1) return null;
368
+ const finished = { ...state.activeTools[idx], isRunning: false, result };
369
+ state.activeTools[idx] = finished;
370
+ state.activeTools = trimCompletedTools(state.activeTools);
371
+ state.history.push({
372
+ id: randomUUID(),
373
+ timestamp: /* @__PURE__ */ new Date(),
374
+ category: "event",
375
+ type: "tool-end",
376
+ data: {
377
+ toolName: finished.toolName,
378
+ firstArg: finished.firstArg,
379
+ isRunning: false,
380
+ result
381
+ }
382
+ });
383
+ return finished;
384
+ }
242
385
 
243
- // src/agents/built-in-agents.ts
244
- var GENERAL_PURPOSE_SYSTEM_PROMPT = `You are a general-purpose task execution agent. You have access to all tools available in the parent session and can perform any task delegated to you.
386
+ // src/hooks/prompt-executor.ts
387
+ function extractJson(raw) {
388
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
389
+ if (codeBlockMatch) {
390
+ return codeBlockMatch[1].trim();
391
+ }
392
+ return raw.trim();
393
+ }
394
+ var PromptExecutor = class {
395
+ type = "prompt";
396
+ providerFactory;
397
+ defaultModel;
398
+ constructor(options) {
399
+ this.providerFactory = options.providerFactory;
400
+ this.defaultModel = options.defaultModel;
401
+ }
402
+ async execute(definition, input) {
403
+ const promptDef = definition;
404
+ const model = promptDef.model ?? this.defaultModel;
405
+ try {
406
+ const provider = this.providerFactory(model);
407
+ const prompt = `${promptDef.prompt}
408
+
409
+ Context:
410
+ ${JSON.stringify(input)}
411
+
412
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
413
+ const rawResponse = await provider.complete(prompt);
414
+ const jsonStr = extractJson(rawResponse);
415
+ let parsed;
416
+ try {
417
+ parsed = JSON.parse(jsonStr);
418
+ } catch {
419
+ return {
420
+ exitCode: 1,
421
+ stdout: "",
422
+ stderr: `Failed to parse AI response as JSON: ${rawResponse}`
423
+ };
424
+ }
425
+ if (parsed.ok) {
426
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
427
+ }
428
+ return {
429
+ exitCode: 2,
430
+ stdout: "",
431
+ stderr: parsed.reason ?? "Blocked by prompt hook"
432
+ };
433
+ } catch (err) {
434
+ const message = err instanceof Error ? err.message : String(err);
435
+ return { exitCode: 1, stdout: "", stderr: message };
436
+ }
437
+ }
438
+ };
439
+
440
+ // src/hooks/agent-executor.ts
441
+ var DEFAULT_MAX_TURNS = 50;
442
+ var DEFAULT_TIMEOUT_SECONDS = 60;
443
+ function extractJson2(raw) {
444
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
445
+ if (codeBlockMatch) {
446
+ return codeBlockMatch[1].trim();
447
+ }
448
+ return raw.trim();
449
+ }
450
+ var AgentExecutor = class {
451
+ type = "agent";
452
+ sessionFactory;
453
+ constructor(options) {
454
+ this.sessionFactory = options.sessionFactory;
455
+ }
456
+ async execute(definition, input) {
457
+ const agentDef = definition;
458
+ const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
459
+ const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
460
+ try {
461
+ const session = this.sessionFactory({ maxTurns, timeout });
462
+ const prompt = `Hook input:
463
+ ${JSON.stringify(input)}
464
+
465
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
466
+ const rawResponse = await session.run(prompt);
467
+ const jsonStr = extractJson2(rawResponse);
468
+ let parsed;
469
+ try {
470
+ parsed = JSON.parse(jsonStr);
471
+ } catch {
472
+ return {
473
+ exitCode: 1,
474
+ stdout: "",
475
+ stderr: `Failed to parse agent response as JSON: ${rawResponse}`
476
+ };
477
+ }
478
+ if (parsed.ok) {
479
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
480
+ }
481
+ return {
482
+ exitCode: 2,
483
+ stdout: "",
484
+ stderr: parsed.reason ?? "Blocked by agent hook"
485
+ };
486
+ } catch (err) {
487
+ const message = err instanceof Error ? err.message : String(err);
488
+ return { exitCode: 1, stdout: "", stderr: message };
489
+ }
490
+ }
491
+ };
492
+
493
+ // src/assembly/create-session.ts
494
+ import { Session as Session2 } from "@robota-sdk/agent-sessions";
495
+
496
+ // src/context/system-prompt-builder.ts
497
+ var TRUST_LEVEL_DESCRIPTIONS = {
498
+ safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
499
+ moderate: "moderate (default mode \u2014 write and bash tools require approval)",
500
+ full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
501
+ };
502
+ function buildProjectSection(info) {
503
+ const lines = ["## Current Project"];
504
+ if (info.name !== void 0) {
505
+ lines.push(`- **Name:** ${info.name}`);
506
+ }
507
+ if (info.type !== "unknown") {
508
+ lines.push(`- **Type:** ${info.type}`);
509
+ }
510
+ if (info.language !== "unknown") {
511
+ lines.push(`- **Language:** ${info.language}`);
512
+ }
513
+ if (info.packageManager !== void 0) {
514
+ lines.push(`- **Package manager:** ${info.packageManager}`);
515
+ }
516
+ return lines.join("\n");
517
+ }
518
+ function buildToolsSection(descriptions) {
519
+ if (descriptions.length === 0) {
520
+ return "";
521
+ }
522
+ const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
523
+ return lines.join("\n");
524
+ }
525
+ function buildSkillsSection(skills) {
526
+ const invocable = skills.filter((s) => s.disableModelInvocation !== true);
527
+ if (invocable.length === 0) {
528
+ return "";
529
+ }
530
+ const lines = [
531
+ "## Skills",
532
+ "The following skills are available:",
533
+ "",
534
+ ...invocable.map((s) => `- ${s.name}: ${s.description}`)
535
+ ];
536
+ return lines.join("\n");
537
+ }
538
+ function buildSystemPrompt(params) {
539
+ const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
540
+ const sections = [];
541
+ const roleLines = [
542
+ "## Role",
543
+ "You are an AI coding assistant with access to tools that let you read and modify code.",
544
+ "You help developers understand, write, and improve their codebase.",
545
+ "Always be precise, follow existing code conventions, and prefer minimal changes."
546
+ ];
547
+ if (language) {
548
+ roleLines.push(
549
+ `Always respond in ${language}. Use ${language} for all explanations and communications.`
550
+ );
551
+ }
552
+ sections.push(roleLines.join("\n"));
553
+ if (cwd) {
554
+ sections.push(`## Working Directory
555
+ \`${cwd}\``);
556
+ }
557
+ sections.push(buildProjectSection(projectInfo));
558
+ sections.push(
559
+ [
560
+ "## Permission Mode",
561
+ `Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
562
+ ].join("\n")
563
+ );
564
+ if (agentsMd.trim().length > 0) {
565
+ sections.push(["## Agent Instructions", agentsMd].join("\n"));
566
+ }
567
+ if (claudeMd.trim().length > 0) {
568
+ sections.push(["## Project Notes", claudeMd].join("\n"));
569
+ }
570
+ sections.push(
571
+ [
572
+ "## Web Search",
573
+ "You have access to web search. When the user asks to search, look up, or find current/latest information,",
574
+ "you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
575
+ "Always prefer web search for: news, latest versions, current events, live documentation."
576
+ ].join("\n")
577
+ );
578
+ const toolsSection = buildToolsSection(toolDescriptions);
579
+ if (toolsSection.length > 0) {
580
+ sections.push(toolsSection);
581
+ }
582
+ if (params.skills !== void 0 && params.skills.length > 0) {
583
+ const skillsSection = buildSkillsSection(params.skills);
584
+ if (skillsSection.length > 0) {
585
+ sections.push(skillsSection);
586
+ }
587
+ }
588
+ return sections.join("\n\n");
589
+ }
590
+
591
+ // src/assembly/create-tools.ts
592
+ import {
593
+ bashTool,
594
+ readTool,
595
+ writeTool,
596
+ editTool,
597
+ globTool,
598
+ grepTool,
599
+ webFetchTool,
600
+ webSearchTool
601
+ } from "@robota-sdk/agent-tools";
602
+ var DEFAULT_TOOL_DESCRIPTIONS = [
603
+ "Bash \u2014 execute shell commands",
604
+ "Read \u2014 read file contents with line numbers",
605
+ "Write \u2014 write content to a file",
606
+ "Edit \u2014 replace a string in a file",
607
+ "Glob \u2014 find files matching a pattern",
608
+ "Grep \u2014 search file contents with regex",
609
+ "WebSearch \u2014 search the internet (Anthropic built-in)"
610
+ ];
611
+ function createDefaultTools() {
612
+ return [
613
+ bashTool,
614
+ readTool,
615
+ writeTool,
616
+ editTool,
617
+ globTool,
618
+ grepTool,
619
+ webFetchTool,
620
+ webSearchTool
621
+ ];
622
+ }
623
+
624
+ // src/tools/agent-tool.ts
625
+ import { z } from "zod";
626
+ import { createZodFunctionTool } from "@robota-sdk/agent-tools";
627
+
628
+ // src/agents/built-in-agents.ts
629
+ var GENERAL_PURPOSE_SYSTEM_PROMPT = `You are a general-purpose task execution agent. You have access to all tools available in the parent session and can perform any task delegated to you.
245
630
 
246
631
  Your role is to complete the assigned task thoroughly and accurately. Follow these guidelines:
247
632
 
@@ -677,7 +1062,7 @@ function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
677
1062
  return join2(baseLogsDir, parentSessionId, "subagents");
678
1063
  }
679
1064
 
680
- // src/interactive/interactive-session.ts
1065
+ // src/interactive/interactive-session-init.ts
681
1066
  import { FileSessionLogger as FileSessionLogger2 } from "@robota-sdk/agent-sessions";
682
1067
 
683
1068
  // src/paths.ts
@@ -972,298 +1357,65 @@ function tryReadJson(filePath) {
972
1357
  if (!existsSync4(filePath)) return void 0;
973
1358
  try {
974
1359
  return JSON.parse(readFileSync4(filePath, "utf-8"));
975
- } catch {
976
- return void 0;
977
- }
978
- }
979
- function detectPackageManager(cwd) {
980
- if (existsSync4(join6(cwd, "pnpm-workspace.yaml")) || existsSync4(join6(cwd, "pnpm-lock.yaml"))) {
981
- return "pnpm";
982
- }
983
- if (existsSync4(join6(cwd, "yarn.lock"))) {
984
- return "yarn";
985
- }
986
- if (existsSync4(join6(cwd, "bun.lockb"))) {
987
- return "bun";
988
- }
989
- if (existsSync4(join6(cwd, "package-lock.json"))) {
990
- return "npm";
991
- }
992
- return void 0;
993
- }
994
- async function detectProject(cwd) {
995
- const pkgJsonPath = join6(cwd, "package.json");
996
- const tsconfigPath = join6(cwd, "tsconfig.json");
997
- const pyprojectPath = join6(cwd, "pyproject.toml");
998
- const cargoPath = join6(cwd, "Cargo.toml");
999
- const goModPath = join6(cwd, "go.mod");
1000
- if (existsSync4(pkgJsonPath)) {
1001
- const pkgJson = tryReadJson(pkgJsonPath);
1002
- const language = existsSync4(tsconfigPath) ? "typescript" : "javascript";
1003
- const packageManager = detectPackageManager(cwd);
1004
- return {
1005
- type: "node",
1006
- name: pkgJson?.name,
1007
- packageManager,
1008
- language
1009
- };
1010
- }
1011
- if (existsSync4(pyprojectPath) || existsSync4(join6(cwd, "setup.py"))) {
1012
- return {
1013
- type: "python",
1014
- language: "python"
1015
- };
1016
- }
1017
- if (existsSync4(cargoPath)) {
1018
- return {
1019
- type: "rust",
1020
- language: "rust"
1021
- };
1022
- }
1023
- if (existsSync4(goModPath)) {
1024
- return {
1025
- type: "go",
1026
- language: "go"
1027
- };
1028
- }
1029
- return {
1030
- type: "unknown",
1031
- language: "unknown"
1032
- };
1033
- }
1034
-
1035
- // src/interactive/interactive-session.ts
1036
- import {
1037
- createUserMessage,
1038
- createAssistantMessage,
1039
- createSystemMessage,
1040
- messageToHistoryEntry
1041
- } from "@robota-sdk/agent-core";
1042
- import { randomUUID } from "crypto";
1043
-
1044
- // src/commands/system-command.ts
1045
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
1046
- function createSystemCommands() {
1047
- return [
1048
- {
1049
- name: "help",
1050
- description: "Show available commands",
1051
- execute: (_session, _args) => ({
1052
- message: [
1053
- "Available commands:",
1054
- " help \u2014 Show this help",
1055
- " clear \u2014 Clear conversation",
1056
- " compact [instr] \u2014 Compact context (optional focus instructions)",
1057
- " mode [m] \u2014 Show/change permission mode",
1058
- " model <id> \u2014 Change AI model",
1059
- " language <code> \u2014 Set response language (ko, en, ja, zh)",
1060
- " cost \u2014 Show session info",
1061
- " context \u2014 Context window info",
1062
- " permissions \u2014 Permission rules",
1063
- " resume \u2014 Resume a previous session",
1064
- " rename <name> \u2014 Rename the current session",
1065
- " reset \u2014 Delete settings and exit"
1066
- ].join("\n"),
1067
- success: true
1068
- })
1069
- },
1070
- {
1071
- name: "clear",
1072
- description: "Clear conversation history",
1073
- execute: (session, _args) => {
1074
- const underlying = session.getSession();
1075
- underlying.clearHistory();
1076
- return { message: "Conversation cleared.", success: true };
1077
- }
1078
- },
1079
- {
1080
- name: "compact",
1081
- description: "Compress context window",
1082
- execute: async (session, args) => {
1083
- const underlying = session.getSession();
1084
- const instructions = args.trim() || void 0;
1085
- const before = underlying.getContextState().usedPercentage;
1086
- await underlying.compact(instructions);
1087
- const after = underlying.getContextState().usedPercentage;
1088
- return {
1089
- message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
1090
- success: true,
1091
- data: { before, after }
1092
- };
1093
- }
1094
- },
1095
- {
1096
- name: "mode",
1097
- description: "Show/change permission mode",
1098
- execute: (session, args) => {
1099
- const underlying = session.getSession();
1100
- const arg = args.trim().split(/\s+/)[0];
1101
- if (!arg) {
1102
- return {
1103
- message: `Current mode: ${underlying.getPermissionMode()}`,
1104
- success: true,
1105
- data: { mode: underlying.getPermissionMode() }
1106
- };
1107
- }
1108
- if (VALID_MODES.includes(arg)) {
1109
- underlying.setPermissionMode(arg);
1110
- return {
1111
- message: `Permission mode set to: ${arg}`,
1112
- success: true,
1113
- data: { mode: arg }
1114
- };
1115
- }
1116
- return {
1117
- message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
1118
- success: false
1119
- };
1120
- }
1121
- },
1122
- {
1123
- name: "model",
1124
- description: "Change AI model",
1125
- execute: (_session, args) => {
1126
- const modelId = args.trim().split(/\s+/)[0];
1127
- if (!modelId) {
1128
- return { message: "Usage: model <model-id>", success: false };
1129
- }
1130
- return {
1131
- message: `Model change requested: ${modelId}`,
1132
- success: true,
1133
- data: { modelId }
1134
- };
1135
- }
1136
- },
1137
- {
1138
- name: "language",
1139
- description: "Set response language",
1140
- execute: (_session, args) => {
1141
- const lang = args.trim().split(/\s+/)[0];
1142
- if (!lang) {
1143
- return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
1144
- }
1145
- return {
1146
- message: `Language set to "${lang}".`,
1147
- success: true,
1148
- data: { language: lang }
1149
- };
1150
- }
1151
- },
1152
- {
1153
- name: "cost",
1154
- description: "Show session info",
1155
- execute: (session, _args) => {
1156
- const underlying = session.getSession();
1157
- const sessionId = underlying.getSessionId();
1158
- const messageCount = underlying.getMessageCount();
1159
- return {
1160
- message: `Session: ${sessionId}
1161
- Messages: ${messageCount}`,
1162
- success: true,
1163
- data: { sessionId, messageCount }
1164
- };
1165
- }
1166
- },
1167
- {
1168
- name: "context",
1169
- description: "Context window info",
1170
- execute: (session, _args) => {
1171
- const ctx = session.getContextState();
1172
- return {
1173
- message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
1174
- success: true,
1175
- data: {
1176
- usedTokens: ctx.usedTokens,
1177
- maxTokens: ctx.maxTokens,
1178
- percentage: ctx.usedPercentage
1179
- }
1180
- };
1181
- }
1182
- },
1183
- {
1184
- name: "permissions",
1185
- description: "Show permission rules",
1186
- execute: (session, _args) => {
1187
- const underlying = session.getSession();
1188
- const mode = underlying.getPermissionMode();
1189
- const sessionAllowed = underlying.getSessionAllowedTools();
1190
- const lines = [`Permission mode: ${mode}`];
1191
- if (sessionAllowed.length > 0) {
1192
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
1193
- } else {
1194
- lines.push("No session-approved tools.");
1195
- }
1196
- return {
1197
- message: lines.join("\n"),
1198
- success: true,
1199
- data: { mode, sessionAllowed }
1200
- };
1201
- }
1202
- },
1203
- {
1204
- name: "resume",
1205
- description: "Resume a previous session",
1206
- execute: (_session, _args) => ({
1207
- message: "Opening session picker...",
1208
- success: true,
1209
- data: { triggerResumePicker: true }
1210
- })
1211
- },
1212
- {
1213
- name: "rename",
1214
- description: "Rename the current session",
1215
- execute: (_session, args) => {
1216
- const name = args.trim();
1217
- if (!name) {
1218
- return { message: "Usage: rename <name>", success: false };
1219
- }
1220
- return {
1221
- message: `Session renamed to "${name}".`,
1222
- success: true,
1223
- data: { name }
1224
- };
1225
- }
1226
- },
1227
- {
1228
- name: "reset",
1229
- description: "Delete settings",
1230
- execute: (_session, _args) => {
1231
- return {
1232
- message: "Reset requested.",
1233
- success: true,
1234
- data: { resetRequested: true }
1235
- };
1236
- }
1237
- }
1238
- ];
1239
- }
1240
- var SystemCommandExecutor = class {
1241
- commands;
1242
- constructor(commands) {
1243
- this.commands = /* @__PURE__ */ new Map();
1244
- for (const cmd of commands ?? createSystemCommands()) {
1245
- this.commands.set(cmd.name, cmd);
1246
- }
1360
+ } catch {
1361
+ return void 0;
1247
1362
  }
1248
- /** Register an additional command. */
1249
- register(command) {
1250
- this.commands.set(command.name, command);
1363
+ }
1364
+ function detectPackageManager(cwd) {
1365
+ if (existsSync4(join6(cwd, "pnpm-workspace.yaml")) || existsSync4(join6(cwd, "pnpm-lock.yaml"))) {
1366
+ return "pnpm";
1251
1367
  }
1252
- /** Execute a command by name. Returns null if command not found. */
1253
- async execute(name, session, args) {
1254
- const cmd = this.commands.get(name);
1255
- if (!cmd) return null;
1256
- return await cmd.execute(session, args);
1368
+ if (existsSync4(join6(cwd, "yarn.lock"))) {
1369
+ return "yarn";
1257
1370
  }
1258
- /** List all registered commands. */
1259
- listCommands() {
1260
- return [...this.commands.values()];
1371
+ if (existsSync4(join6(cwd, "bun.lockb"))) {
1372
+ return "bun";
1261
1373
  }
1262
- /** Check if a command exists. */
1263
- hasCommand(name) {
1264
- return this.commands.has(name);
1374
+ if (existsSync4(join6(cwd, "package-lock.json"))) {
1375
+ return "npm";
1265
1376
  }
1266
- };
1377
+ return void 0;
1378
+ }
1379
+ async function detectProject(cwd) {
1380
+ const pkgJsonPath = join6(cwd, "package.json");
1381
+ const tsconfigPath = join6(cwd, "tsconfig.json");
1382
+ const pyprojectPath = join6(cwd, "pyproject.toml");
1383
+ const cargoPath = join6(cwd, "Cargo.toml");
1384
+ const goModPath = join6(cwd, "go.mod");
1385
+ if (existsSync4(pkgJsonPath)) {
1386
+ const pkgJson = tryReadJson(pkgJsonPath);
1387
+ const language = existsSync4(tsconfigPath) ? "typescript" : "javascript";
1388
+ const packageManager = detectPackageManager(cwd);
1389
+ return {
1390
+ type: "node",
1391
+ name: pkgJson?.name,
1392
+ packageManager,
1393
+ language
1394
+ };
1395
+ }
1396
+ if (existsSync4(pyprojectPath) || existsSync4(join6(cwd, "setup.py"))) {
1397
+ return {
1398
+ type: "python",
1399
+ language: "python"
1400
+ };
1401
+ }
1402
+ if (existsSync4(cargoPath)) {
1403
+ return {
1404
+ type: "rust",
1405
+ language: "rust"
1406
+ };
1407
+ }
1408
+ if (existsSync4(goModPath)) {
1409
+ return {
1410
+ type: "go",
1411
+ language: "go"
1412
+ };
1413
+ }
1414
+ return {
1415
+ type: "unknown",
1416
+ language: "unknown"
1417
+ };
1418
+ }
1267
1419
 
1268
1420
  // src/plugins/plugin-settings-store.ts
1269
1421
  import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
@@ -1367,8 +1519,11 @@ var PluginSettingsStore = class {
1367
1519
  };
1368
1520
 
1369
1521
  // src/plugins/bundle-plugin-loader.ts
1370
- import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync6 } from "fs";
1522
+ import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1371
1523
  import { join as join7 } from "path";
1524
+
1525
+ // src/plugins/bundle-plugin-utils.ts
1526
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
1372
1527
  function parseSkillFrontmatter(raw) {
1373
1528
  const trimmed = raw.trimStart();
1374
1529
  if (!trimmed.startsWith("---")) {
@@ -1425,6 +1580,8 @@ function getSortedSubdirs(dirPath) {
1425
1580
  return [];
1426
1581
  }
1427
1582
  }
1583
+
1584
+ // src/plugins/bundle-plugin-loader.ts
1428
1585
  var BundlePluginLoader = class {
1429
1586
  pluginsDir;
1430
1587
  enabledPlugins;
@@ -1448,7 +1605,7 @@ var BundlePluginLoader = class {
1448
1605
  */
1449
1606
  discoverAndLoad() {
1450
1607
  const cacheDir = join7(this.pluginsDir, "cache");
1451
- if (!existsSync6(cacheDir)) {
1608
+ if (!existsSync7(cacheDir)) {
1452
1609
  return [];
1453
1610
  }
1454
1611
  const results = [];
@@ -1463,7 +1620,7 @@ var BundlePluginLoader = class {
1463
1620
  const latestVersion = versions[versions.length - 1];
1464
1621
  const versionDir = join7(pluginDir, latestVersion);
1465
1622
  const manifestPath = join7(versionDir, ".claude-plugin", "plugin.json");
1466
- if (!existsSync6(manifestPath)) continue;
1623
+ if (!existsSync7(manifestPath)) continue;
1467
1624
  const manifest = this.readManifest(manifestPath);
1468
1625
  if (!manifest) continue;
1469
1626
  const pluginId = `${manifest.name}@${marketplace}`;
@@ -1513,13 +1670,13 @@ var BundlePluginLoader = class {
1513
1670
  /** Load skills from the plugin's skills/ directory. */
1514
1671
  loadSkills(pluginDir, pluginName) {
1515
1672
  const skillsDir = join7(pluginDir, "skills");
1516
- if (!existsSync6(skillsDir)) return [];
1517
- const entries = readdirSync2(skillsDir, { withFileTypes: true });
1673
+ if (!existsSync7(skillsDir)) return [];
1674
+ const entries = readdirSync3(skillsDir, { withFileTypes: true });
1518
1675
  const skills = [];
1519
1676
  for (const entry of entries) {
1520
1677
  if (!entry.isDirectory()) continue;
1521
1678
  const skillFile = join7(skillsDir, entry.name, "SKILL.md");
1522
- if (!existsSync6(skillFile)) continue;
1679
+ if (!existsSync7(skillFile)) continue;
1523
1680
  const raw = readFileSync6(skillFile, "utf-8");
1524
1681
  const { metadata, content } = parseSkillFrontmatter(raw);
1525
1682
  const description = typeof metadata.description === "string" ? metadata.description : "";
@@ -1536,8 +1693,8 @@ var BundlePluginLoader = class {
1536
1693
  /** Load commands from the plugin's commands/ directory (flat .md files). */
1537
1694
  loadCommands(pluginDir, pluginName) {
1538
1695
  const commandsDir = join7(pluginDir, "commands");
1539
- if (!existsSync6(commandsDir)) return [];
1540
- const entries = readdirSync2(commandsDir, { withFileTypes: true });
1696
+ if (!existsSync7(commandsDir)) return [];
1697
+ const entries = readdirSync3(commandsDir, { withFileTypes: true });
1541
1698
  const commands = [];
1542
1699
  for (const entry of entries) {
1543
1700
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
@@ -1557,7 +1714,7 @@ var BundlePluginLoader = class {
1557
1714
  /** Load hooks from hooks/hooks.json if present. */
1558
1715
  loadHooks(pluginDir) {
1559
1716
  const hooksPath = join7(pluginDir, "hooks", "hooks.json");
1560
- if (!existsSync6(hooksPath)) return {};
1717
+ if (!existsSync7(hooksPath)) return {};
1561
1718
  try {
1562
1719
  const raw = readFileSync6(hooksPath, "utf-8");
1563
1720
  const data = JSON.parse(raw);
@@ -1573,8 +1730,8 @@ var BundlePluginLoader = class {
1573
1730
  loadMcpConfig(pluginDir) {
1574
1731
  const primaryPath = join7(pluginDir, ".mcp.json");
1575
1732
  const fallbackPath = join7(pluginDir, ".claude-plugin", "mcp.json");
1576
- const mcpPath = existsSync6(primaryPath) ? primaryPath : fallbackPath;
1577
- if (!existsSync6(mcpPath)) return void 0;
1733
+ const mcpPath = existsSync7(primaryPath) ? primaryPath : fallbackPath;
1734
+ if (!existsSync7(mcpPath)) return void 0;
1578
1735
  try {
1579
1736
  const raw = readFileSync6(mcpPath, "utf-8");
1580
1737
  return JSON.parse(raw);
@@ -1585,9 +1742,9 @@ var BundlePluginLoader = class {
1585
1742
  /** Load agent definitions from agents/ directory if present. */
1586
1743
  loadAgents(pluginDir) {
1587
1744
  const agentsDir = join7(pluginDir, "agents");
1588
- if (!existsSync6(agentsDir)) return [];
1745
+ if (!existsSync7(agentsDir)) return [];
1589
1746
  try {
1590
- const entries = readdirSync2(agentsDir, { withFileTypes: true });
1747
+ const entries = readdirSync3(agentsDir, { withFileTypes: true });
1591
1748
  return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
1592
1749
  } catch {
1593
1750
  return [];
@@ -1597,7 +1754,7 @@ var BundlePluginLoader = class {
1597
1754
 
1598
1755
  // src/plugins/bundle-plugin-installer.ts
1599
1756
  import { execSync } from "child_process";
1600
- import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync2 } from "fs";
1757
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync2 } from "fs";
1601
1758
  import { join as join8, dirname as dirname3 } from "path";
1602
1759
  var GIT_CLONE_TIMEOUT_MS = 6e4;
1603
1760
  var BundlePluginInstaller = class {
@@ -1631,7 +1788,7 @@ var BundlePluginInstaller = class {
1631
1788
  }
1632
1789
  const version = this.resolveVersion(entry, marketplaceName);
1633
1790
  const targetDir = join8(this.cacheDir, marketplaceName, pluginName, version);
1634
- if (existsSync7(targetDir)) {
1791
+ if (existsSync8(targetDir)) {
1635
1792
  throw new Error(
1636
1793
  `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
1637
1794
  );
@@ -1658,7 +1815,7 @@ var BundlePluginInstaller = class {
1658
1815
  if (!record) {
1659
1816
  throw new Error(`Plugin "${pluginId}" is not installed`);
1660
1817
  }
1661
- if (existsSync7(record.installPath)) {
1818
+ if (existsSync8(record.installPath)) {
1662
1819
  rmSync(record.installPath, { recursive: true, force: true });
1663
1820
  }
1664
1821
  delete registry[pluginId];
@@ -1711,7 +1868,7 @@ var BundlePluginInstaller = class {
1711
1868
  if (typeof source === "string") {
1712
1869
  const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
1713
1870
  const sourcePath = join8(marketplaceDir, source);
1714
- if (!existsSync7(sourcePath)) {
1871
+ if (!existsSync8(sourcePath)) {
1715
1872
  throw new Error(
1716
1873
  `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
1717
1874
  );
@@ -1728,7 +1885,7 @@ var BundlePluginInstaller = class {
1728
1885
  throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
1729
1886
  }
1730
1887
  } catch (err) {
1731
- if (existsSync7(targetDir)) {
1888
+ if (existsSync8(targetDir)) {
1732
1889
  rmSync(targetDir, { recursive: true, force: true });
1733
1890
  }
1734
1891
  throw err;
@@ -1747,7 +1904,7 @@ var BundlePluginInstaller = class {
1747
1904
  }
1748
1905
  /** Read the installed_plugins.json registry. */
1749
1906
  readRegistry() {
1750
- if (!existsSync7(this.registryPath)) {
1907
+ if (!existsSync8(this.registryPath)) {
1751
1908
  return {};
1752
1909
  }
1753
1910
  try {
@@ -1764,7 +1921,7 @@ var BundlePluginInstaller = class {
1764
1921
  /** Write the installed_plugins.json registry. */
1765
1922
  writeRegistry(registry) {
1766
1923
  const dir = dirname3(this.registryPath);
1767
- if (!existsSync7(dir)) {
1924
+ if (!existsSync8(dir)) {
1768
1925
  mkdirSync3(dir, { recursive: true });
1769
1926
  }
1770
1927
  writeFileSync2(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
@@ -1777,16 +1934,66 @@ var BundlePluginInstaller = class {
1777
1934
 
1778
1935
  // src/plugins/marketplace-client.ts
1779
1936
  import { execSync as execSync2 } from "child_process";
1780
- import {
1781
- cpSync as cpSync2,
1782
- existsSync as existsSync8,
1783
- mkdirSync as mkdirSync4,
1784
- readFileSync as readFileSync8,
1785
- renameSync,
1786
- rmSync as rmSync2,
1787
- writeFileSync as writeFileSync3
1788
- } from "fs";
1937
+ import { cpSync as cpSync2, existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync, rmSync as rmSync3 } from "fs";
1938
+ import { join as join10 } from "path";
1939
+
1940
+ // src/plugins/marketplace-registry.ts
1941
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
1789
1942
  import { join as join9, dirname as dirname4 } from "path";
1943
+ function readRegistry(registryPath) {
1944
+ if (!existsSync9(registryPath)) {
1945
+ return {};
1946
+ }
1947
+ try {
1948
+ const raw = readFileSync8(registryPath, "utf-8");
1949
+ const data = JSON.parse(raw);
1950
+ if (typeof data === "object" && data !== null) {
1951
+ return data;
1952
+ }
1953
+ return {};
1954
+ } catch {
1955
+ return {};
1956
+ }
1957
+ }
1958
+ function writeRegistry(registryPath, registry) {
1959
+ const dir = dirname4(registryPath);
1960
+ if (!existsSync9(dir)) {
1961
+ mkdirSync4(dir, { recursive: true });
1962
+ }
1963
+ writeFileSync3(registryPath, JSON.stringify(registry, null, 2), "utf-8");
1964
+ }
1965
+ function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
1966
+ const installedPath = join9(pluginsDir, "installed_plugins.json");
1967
+ if (!existsSync9(installedPath)) return;
1968
+ let registry;
1969
+ try {
1970
+ const raw = readFileSync8(installedPath, "utf-8");
1971
+ const data = JSON.parse(raw);
1972
+ if (typeof data !== "object" || data === null) return;
1973
+ registry = data;
1974
+ } catch {
1975
+ return;
1976
+ }
1977
+ let changed = false;
1978
+ for (const [pluginId, record] of Object.entries(registry)) {
1979
+ if (record.marketplace === marketplaceName) {
1980
+ if (record.installPath && existsSync9(record.installPath)) {
1981
+ rmSync2(record.installPath, { recursive: true, force: true });
1982
+ }
1983
+ delete registry[pluginId];
1984
+ changed = true;
1985
+ }
1986
+ }
1987
+ if (changed) {
1988
+ const dir = dirname4(installedPath);
1989
+ if (!existsSync9(dir)) {
1990
+ mkdirSync4(dir, { recursive: true });
1991
+ }
1992
+ writeFileSync3(installedPath, JSON.stringify(registry, null, 2), "utf-8");
1993
+ }
1994
+ }
1995
+
1996
+ // src/plugins/marketplace-client.ts
1790
1997
  var GIT_TIMEOUT_MS = 6e4;
1791
1998
  var MarketplaceClient = class {
1792
1999
  pluginsDir;
@@ -1796,25 +2003,24 @@ var MarketplaceClient = class {
1796
2003
  constructor(options) {
1797
2004
  this.pluginsDir = options.pluginsDir;
1798
2005
  this.exec = options.exec ?? this.defaultExec;
1799
- this.marketplacesDir = join9(this.pluginsDir, "marketplaces");
1800
- this.registryPath = join9(this.pluginsDir, "known_marketplaces.json");
2006
+ this.marketplacesDir = join10(this.pluginsDir, "marketplaces");
2007
+ this.registryPath = join10(this.pluginsDir, "known_marketplaces.json");
1801
2008
  }
1802
2009
  /**
1803
2010
  * Add a marketplace by cloning its repository.
1804
2011
  *
1805
- * 1. Parse source: `owner/repo` string becomes a GitHub source.
1806
- * 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
1807
- * 3. Read `.claude-plugin/marketplace.json` for the `name` field.
1808
- * 4. Register in `known_marketplaces.json`.
2012
+ * 1. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
2013
+ * 2. Read `.claude-plugin/marketplace.json` for the `name` field.
2014
+ * 3. Register in `known_marketplaces.json`.
1809
2015
  *
1810
2016
  * Returns the registered marketplace name from the manifest.
1811
2017
  */
1812
2018
  addMarketplace(source) {
1813
2019
  const tempName = "temp-" + Date.now().toString(36);
1814
- const tempDir = join9(this.marketplacesDir, tempName);
1815
- mkdirSync4(this.marketplacesDir, { recursive: true });
2020
+ const tempDir = join10(this.marketplacesDir, tempName);
2021
+ mkdirSync5(this.marketplacesDir, { recursive: true });
1816
2022
  if (source.type === "local") {
1817
- if (!existsSync8(source.path)) {
2023
+ if (!existsSync10(source.path)) {
1818
2024
  throw new Error(`Local marketplace path does not exist: ${source.path}`);
1819
2025
  }
1820
2026
  cpSync2(source.path, tempDir, { recursive: true });
@@ -1828,9 +2034,9 @@ var MarketplaceClient = class {
1828
2034
  throw new Error(`Failed to clone marketplace: ${message}`);
1829
2035
  }
1830
2036
  }
1831
- const manifestPath = join9(tempDir, ".claude-plugin", "marketplace.json");
1832
- if (!existsSync8(manifestPath)) {
1833
- rmSync2(tempDir, { recursive: true, force: true });
2037
+ const manifestPath = join10(tempDir, ".claude-plugin", "marketplace.json");
2038
+ if (!existsSync10(manifestPath)) {
2039
+ rmSync3(tempDir, { recursive: true, force: true });
1834
2040
  throw new Error(
1835
2041
  source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
1836
2042
  );
@@ -1838,22 +2044,22 @@ var MarketplaceClient = class {
1838
2044
  const manifest = this.readManifestFromPath(manifestPath);
1839
2045
  const name = manifest.name;
1840
2046
  if (!name) {
1841
- rmSync2(tempDir, { recursive: true, force: true });
2047
+ rmSync3(tempDir, { recursive: true, force: true });
1842
2048
  throw new Error('Marketplace manifest does not contain a "name" field');
1843
2049
  }
1844
- const registry = this.readRegistry();
2050
+ const registry = readRegistry(this.registryPath);
1845
2051
  if (registry[name]) {
1846
- rmSync2(tempDir, { recursive: true, force: true });
2052
+ rmSync3(tempDir, { recursive: true, force: true });
1847
2053
  throw new Error(`Marketplace "${name}" already exists`);
1848
2054
  }
1849
- const finalDir = join9(this.marketplacesDir, name);
2055
+ const finalDir = join10(this.marketplacesDir, name);
1850
2056
  renameSync(tempDir, finalDir);
1851
2057
  registry[name] = {
1852
2058
  source,
1853
2059
  installLocation: finalDir,
1854
2060
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1855
2061
  };
1856
- this.writeRegistry(registry);
2062
+ writeRegistry(this.registryPath, registry);
1857
2063
  return name;
1858
2064
  }
1859
2065
  /**
@@ -1862,41 +2068,38 @@ var MarketplaceClient = class {
1862
2068
  * and removes from the registry.
1863
2069
  */
1864
2070
  removeMarketplace(name) {
1865
- const registry = this.readRegistry();
2071
+ const registry = readRegistry(this.registryPath);
1866
2072
  const entry = registry[name];
1867
2073
  if (!entry) {
1868
2074
  throw new Error(`Marketplace "${name}" not found`);
1869
2075
  }
1870
- this.removeInstalledPluginsForMarketplace(name);
1871
- if (existsSync8(entry.installLocation)) {
1872
- rmSync2(entry.installLocation, { recursive: true, force: true });
2076
+ removeInstalledPluginsForMarketplace(this.pluginsDir, name);
2077
+ if (existsSync10(entry.installLocation)) {
2078
+ rmSync3(entry.installLocation, { recursive: true, force: true });
1873
2079
  }
1874
2080
  delete registry[name];
1875
- this.writeRegistry(registry);
2081
+ writeRegistry(this.registryPath, registry);
1876
2082
  }
1877
2083
  /**
1878
2084
  * Update a marketplace by running git pull on its clone.
1879
2085
  * The manifest is re-read from disk on demand (via fetchManifest), so the
1880
2086
  * updated manifest is automatically available after pull.
1881
- *
1882
- * TODO: After pull, detect version changes in installed plugins and offer
1883
- * to update them (re-install at new version).
1884
2087
  */
1885
2088
  updateMarketplace(name) {
1886
- const registry = this.readRegistry();
2089
+ const registry = readRegistry(this.registryPath);
1887
2090
  const entry = registry[name];
1888
2091
  if (!entry) {
1889
2092
  throw new Error(`Marketplace "${name}" not found`);
1890
2093
  }
1891
- if (!existsSync8(entry.installLocation)) {
2094
+ if (!existsSync10(entry.installLocation)) {
1892
2095
  throw new Error(`Marketplace directory for "${name}" does not exist`);
1893
2096
  }
1894
2097
  if (entry.source.type === "local") {
1895
2098
  const localSource = entry.source;
1896
- if (!existsSync8(localSource.path)) {
2099
+ if (!existsSync10(localSource.path)) {
1897
2100
  throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
1898
2101
  }
1899
- rmSync2(entry.installLocation, { recursive: true, force: true });
2102
+ rmSync3(entry.installLocation, { recursive: true, force: true });
1900
2103
  cpSync2(localSource.path, entry.installLocation, { recursive: true });
1901
2104
  } else {
1902
2105
  const command = `git -C ${entry.installLocation} pull`;
@@ -1908,28 +2111,26 @@ var MarketplaceClient = class {
1908
2111
  }
1909
2112
  }
1910
2113
  entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1911
- this.writeRegistry(registry);
2114
+ writeRegistry(this.registryPath, registry);
1912
2115
  }
1913
2116
  /** List all registered marketplaces. */
1914
2117
  listMarketplaces() {
1915
- const registry = this.readRegistry();
2118
+ const registry = readRegistry(this.registryPath);
1916
2119
  return Object.entries(registry).map(([name, entry]) => ({
1917
2120
  name,
1918
2121
  source: entry.source,
1919
2122
  lastUpdated: entry.lastUpdated
1920
2123
  }));
1921
2124
  }
1922
- /**
1923
- * Read the marketplace manifest from a registered marketplace's clone.
1924
- */
2125
+ /** Read the marketplace manifest from a registered marketplace's clone. */
1925
2126
  fetchManifest(marketplaceName) {
1926
- const registry = this.readRegistry();
2127
+ const registry = readRegistry(this.registryPath);
1927
2128
  const entry = registry[marketplaceName];
1928
2129
  if (!entry) {
1929
2130
  throw new Error(`Marketplace "${marketplaceName}" not found`);
1930
2131
  }
1931
- const manifestPath = join9(entry.installLocation, ".claude-plugin", "marketplace.json");
1932
- if (!existsSync8(manifestPath)) {
2132
+ const manifestPath = join10(entry.installLocation, ".claude-plugin", "marketplace.json");
2133
+ if (!existsSync10(manifestPath)) {
1933
2134
  throw new Error(
1934
2135
  `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
1935
2136
  );
@@ -1938,7 +2139,7 @@ var MarketplaceClient = class {
1938
2139
  }
1939
2140
  /** Get the clone directory path for a registered marketplace. */
1940
2141
  getMarketplaceDir(name) {
1941
- const registry = this.readRegistry();
2142
+ const registry = readRegistry(this.registryPath);
1942
2143
  const entry = registry[name];
1943
2144
  if (!entry) {
1944
2145
  throw new Error(`Marketplace "${name}" not found`);
@@ -1990,44 +2191,9 @@ var MarketplaceClient = class {
1990
2191
  throw new Error("URL marketplace source is not yet supported");
1991
2192
  }
1992
2193
  }
1993
- /**
1994
- * Remove all installed plugins that belong to a given marketplace.
1995
- * Reads installed_plugins.json, deletes cache directories for matching plugins,
1996
- * and updates the registry.
1997
- */
1998
- removeInstalledPluginsForMarketplace(marketplaceName) {
1999
- const installedPath = join9(this.pluginsDir, "installed_plugins.json");
2000
- if (!existsSync8(installedPath)) return;
2001
- let registry;
2002
- try {
2003
- const raw = readFileSync8(installedPath, "utf-8");
2004
- const data = JSON.parse(raw);
2005
- if (typeof data !== "object" || data === null) return;
2006
- registry = data;
2007
- } catch {
2008
- return;
2009
- }
2010
- let changed = false;
2011
- for (const [pluginId, record] of Object.entries(registry)) {
2012
- if (record.marketplace === marketplaceName) {
2013
- if (record.installPath && existsSync8(record.installPath)) {
2014
- rmSync2(record.installPath, { recursive: true, force: true });
2015
- }
2016
- delete registry[pluginId];
2017
- changed = true;
2018
- }
2019
- }
2020
- if (changed) {
2021
- const dir = dirname4(installedPath);
2022
- if (!existsSync8(dir)) {
2023
- mkdirSync4(dir, { recursive: true });
2024
- }
2025
- writeFileSync3(installedPath, JSON.stringify(registry, null, 2), "utf-8");
2026
- }
2027
- }
2028
2194
  /** Read and parse a marketplace.json from a file path. */
2029
2195
  readManifestFromPath(path) {
2030
- const raw = readFileSync8(path, "utf-8");
2196
+ const raw = readFileSync9(path, "utf-8");
2031
2197
  const data = JSON.parse(raw);
2032
2198
  if (typeof data !== "object" || data === null) {
2033
2199
  throw new Error("Invalid marketplace manifest: not an object");
@@ -2038,30 +2204,6 @@ var MarketplaceClient = class {
2038
2204
  }
2039
2205
  return data;
2040
2206
  }
2041
- /** Read the known_marketplaces.json registry. */
2042
- readRegistry() {
2043
- if (!existsSync8(this.registryPath)) {
2044
- return {};
2045
- }
2046
- try {
2047
- const raw = readFileSync8(this.registryPath, "utf-8");
2048
- const data = JSON.parse(raw);
2049
- if (typeof data === "object" && data !== null) {
2050
- return data;
2051
- }
2052
- return {};
2053
- } catch {
2054
- return {};
2055
- }
2056
- }
2057
- /** Write the known_marketplaces.json registry. */
2058
- writeRegistry(registry) {
2059
- const dir = dirname4(this.registryPath);
2060
- if (!existsSync8(dir)) {
2061
- mkdirSync4(dir, { recursive: true });
2062
- }
2063
- writeFileSync3(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
2064
- }
2065
2207
  /** Default exec implementation using child_process. */
2066
2208
  defaultExec(command, options) {
2067
2209
  return execSync2(command, { timeout: options.timeout, stdio: "pipe" });
@@ -2069,9 +2211,9 @@ var MarketplaceClient = class {
2069
2211
  };
2070
2212
 
2071
2213
  // src/plugins/plugin-hooks-merger.ts
2072
- import { join as join10, dirname as dirname5 } from "path";
2214
+ import { join as join11, dirname as dirname5 } from "path";
2073
2215
  function buildPluginEnv(plugin) {
2074
- const dataDir = join10(dirname5(dirname5(plugin.pluginDir)), "data", plugin.manifest.name);
2216
+ const dataDir = join11(dirname5(dirname5(plugin.pluginDir)), "data", plugin.manifest.name);
2075
2217
  return {
2076
2218
  CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
2077
2219
  CLAUDE_PLUGIN_PATH: plugin.pluginDir,
@@ -2132,41 +2274,111 @@ function mergeHooksIntoConfig(configHooks, pluginHooks) {
2132
2274
  return merged;
2133
2275
  }
2134
2276
 
2135
- // src/interactive/interactive-session.ts
2277
+ // src/interactive/interactive-session-init.ts
2136
2278
  import { homedir as homedir3 } from "os";
2137
- import { join as join11 } from "path";
2138
- var TOOL_ARG_DISPLAY_MAX = 80;
2139
- var TAIL_KEEP = 30;
2140
- var MAX_COMPLETED_TOOLS = 50;
2141
- var STREAMING_FLUSH_INTERVAL_MS = 16;
2279
+ import { join as join12 } from "path";
2280
+ async function createInteractiveSession(options) {
2281
+ const cwd = options.cwd;
2282
+ const [config, context, projectInfo] = await Promise.all([
2283
+ loadConfig(cwd),
2284
+ loadContext(cwd),
2285
+ detectProject(cwd)
2286
+ ]);
2287
+ const pluginsDir = join12(homedir3(), ".robota", "plugins");
2288
+ const pluginLoader = new BundlePluginLoader(pluginsDir);
2289
+ let mergedConfig = config;
2290
+ try {
2291
+ const plugins = pluginLoader.loadPluginsSync();
2292
+ if (plugins.length > 0) {
2293
+ const pluginHooks = mergePluginHooks(plugins);
2294
+ mergedConfig = {
2295
+ ...config,
2296
+ hooks: mergeHooksIntoConfig(
2297
+ config.hooks,
2298
+ pluginHooks
2299
+ )
2300
+ };
2301
+ }
2302
+ } catch {
2303
+ }
2304
+ const paths = projectPaths(cwd);
2305
+ const sessionId = options.resumeSessionId && !options.forkSession ? options.resumeSessionId : void 0;
2306
+ return createSession({
2307
+ config: mergedConfig,
2308
+ context,
2309
+ projectInfo,
2310
+ permissionMode: options.permissionMode,
2311
+ maxTurns: options.maxTurns,
2312
+ terminal: NOOP_TERMINAL,
2313
+ sessionLogger: new FileSessionLogger2(paths.logs),
2314
+ permissionHandler: options.permissionHandler,
2315
+ provider: options.provider,
2316
+ onTextDelta: options.onTextDelta,
2317
+ onToolExecution: options.onToolExecution,
2318
+ sessionId
2319
+ });
2320
+ }
2321
+ function injectSavedMessage(session, msg) {
2322
+ if (!msg || typeof msg !== "object") return;
2323
+ const m = msg;
2324
+ if (!m.role || !m.content) return;
2325
+ const role = m.role;
2326
+ if (role === "tool") {
2327
+ const toolCallId = m.toolCallId ?? "";
2328
+ const name = m.name ?? void 0;
2329
+ session.injectMessage("tool", m.content, { toolCallId, name });
2330
+ } else if (role === "user" || role === "assistant" || role === "system") {
2331
+ session.injectMessage(role, m.content);
2332
+ }
2333
+ }
2334
+ function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingSession) {
2335
+ const record = sessionStore.load(resumeSessionId);
2336
+ if (!record) {
2337
+ return { history: [], sessionName: void 0, pendingRestoreMessages: null };
2338
+ }
2339
+ const history = record.history ?? [];
2340
+ const sessionName = record.name;
2341
+ let pendingRestoreMessages = null;
2342
+ if (!forkSession && record.messages) {
2343
+ if (existingSession) {
2344
+ for (const msg of record.messages) {
2345
+ injectSavedMessage(existingSession, msg);
2346
+ }
2347
+ } else {
2348
+ pendingRestoreMessages = record.messages;
2349
+ }
2350
+ }
2351
+ return { history, sessionName, pendingRestoreMessages };
2352
+ }
2353
+
2354
+ // src/interactive/interactive-session.ts
2142
2355
  var InteractiveSession = class {
2143
2356
  session = null;
2144
2357
  commandExecutor;
2145
2358
  listeners = /* @__PURE__ */ new Map();
2146
2359
  initialized = false;
2147
2360
  initPromise = null;
2148
- // Streaming state
2149
2361
  streamingText = "";
2150
2362
  flushTimer = null;
2151
- // Tool state
2152
2363
  activeTools = [];
2153
- // Execution state
2154
2364
  executing = false;
2155
2365
  pendingPrompt = null;
2156
2366
  pendingDisplayInput;
2157
2367
  pendingRawInput;
2158
- // Full history timeline (chat messages + events)
2159
2368
  history = [];
2160
- // Session persistence
2161
2369
  sessionStore;
2162
2370
  sessionName;
2163
2371
  cwd;
2164
- // Session restore state
2165
2372
  pendingRestoreMessages = null;
2166
2373
  resumeSessionId;
2167
2374
  forkSession;
2168
2375
  constructor(options) {
2169
2376
  this.commandExecutor = new SystemCommandExecutor(createSystemCommands());
2377
+ this.sessionStore = options.sessionStore;
2378
+ this.sessionName = options.sessionName;
2379
+ this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
2380
+ this.resumeSessionId = options.resumeSessionId;
2381
+ this.forkSession = options.forkSession ?? false;
2170
2382
  if ("session" in options && options.session) {
2171
2383
  this.session = options.session;
2172
2384
  this.initialized = true;
@@ -2174,98 +2386,46 @@ var InteractiveSession = class {
2174
2386
  const stdOpts = options;
2175
2387
  this.initPromise = this.initializeAsync(stdOpts);
2176
2388
  }
2177
- this.sessionStore = options.sessionStore;
2178
- this.sessionName = options.sessionName;
2179
- this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
2180
- this.resumeSessionId = options.resumeSessionId;
2181
- this.forkSession = options.forkSession ?? false;
2182
2389
  if (options.resumeSessionId && this.sessionStore) {
2183
- const record = this.sessionStore.load(options.resumeSessionId);
2184
- if (record) {
2185
- this.history = record.history ?? [];
2186
- this.sessionName = record.name;
2187
- if (record.messages) {
2188
- if (this.session) {
2189
- for (const msg of record.messages) {
2190
- const m = msg;
2191
- if (m.role && m.content) {
2192
- this.session.injectMessage(m.role, m.content);
2193
- }
2194
- }
2195
- } else {
2196
- this.pendingRestoreMessages = record.messages;
2197
- }
2198
- }
2199
- }
2390
+ const restored = loadSessionRecord(
2391
+ this.sessionStore,
2392
+ options.resumeSessionId,
2393
+ this.forkSession,
2394
+ this.session
2395
+ );
2396
+ if (restored.history.length > 0) this.history = restored.history;
2397
+ if (restored.sessionName) this.sessionName = restored.sessionName;
2398
+ this.pendingRestoreMessages = restored.pendingRestoreMessages;
2200
2399
  }
2201
2400
  }
2202
2401
  async initializeAsync(options) {
2203
- const cwd = options.cwd;
2204
- const [config, context, projectInfo] = await Promise.all([
2205
- loadConfig(cwd),
2206
- loadContext(cwd),
2207
- detectProject(cwd)
2208
- ]);
2209
- const pluginsDir = join11(homedir3(), ".robota", "plugins");
2210
- const pluginLoader = new BundlePluginLoader(pluginsDir);
2211
- let mergedConfig = config;
2212
- try {
2213
- const plugins = pluginLoader.loadPluginsSync();
2214
- if (plugins.length > 0) {
2215
- const pluginHooks = mergePluginHooks(plugins);
2216
- mergedConfig = {
2217
- ...config,
2218
- hooks: mergeHooksIntoConfig(
2219
- config.hooks,
2220
- pluginHooks
2221
- )
2222
- };
2223
- }
2224
- } catch {
2225
- }
2226
- const paths = projectPaths(cwd);
2227
- const sessionId = this.resumeSessionId && !this.forkSession ? this.resumeSessionId : void 0;
2228
- this.session = createSession({
2229
- config: mergedConfig,
2230
- context,
2231
- projectInfo,
2402
+ this.session = await createInteractiveSession({
2403
+ cwd: options.cwd,
2404
+ provider: options.provider,
2232
2405
  permissionMode: options.permissionMode,
2233
2406
  maxTurns: options.maxTurns,
2234
- terminal: NOOP_TERMINAL,
2235
- sessionLogger: new FileSessionLogger2(paths.logs),
2236
2407
  permissionHandler: options.permissionHandler,
2237
- provider: options.provider,
2408
+ resumeSessionId: this.resumeSessionId,
2409
+ forkSession: this.forkSession,
2238
2410
  onTextDelta: (delta) => this.handleTextDelta(delta),
2239
- onToolExecution: (event) => this.handleToolExecution(event),
2240
- sessionId
2411
+ onToolExecution: (event) => this.handleToolExecution(event)
2241
2412
  });
2242
2413
  if (this.pendingRestoreMessages) {
2243
- for (const msg of this.pendingRestoreMessages) {
2244
- if (msg && typeof msg === "object" && "role" in msg && "content" in msg) {
2245
- this.session.injectMessage(
2246
- msg.role,
2247
- msg.content
2248
- );
2249
- }
2250
- }
2414
+ for (const msg of this.pendingRestoreMessages) injectSavedMessage(this.session, msg);
2251
2415
  this.pendingRestoreMessages = null;
2252
2416
  }
2253
2417
  this.initialized = true;
2254
2418
  }
2255
2419
  async ensureInitialized() {
2256
- if (this.initialized) return;
2257
- if (this.initPromise) await this.initPromise;
2420
+ if (!this.initialized && this.initPromise) await this.initPromise;
2258
2421
  }
2259
2422
  getSessionOrThrow() {
2260
2423
  if (!this.session)
2261
2424
  throw new Error("InteractiveSession not initialized. Call submit() or await initialization.");
2262
2425
  return this.session;
2263
2426
  }
2264
- // ── Event system ──────────────────────────────────────────────
2265
2427
  on(event, handler) {
2266
- if (!this.listeners.has(event)) {
2267
- this.listeners.set(event, /* @__PURE__ */ new Set());
2268
- }
2428
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
2269
2429
  this.listeners.get(event).add(handler);
2270
2430
  }
2271
2431
  off(event, handler) {
@@ -2273,14 +2433,8 @@ var InteractiveSession = class {
2273
2433
  }
2274
2434
  emit(event, ...args) {
2275
2435
  const handlers = this.listeners.get(event);
2276
- if (handlers) {
2277
- for (const handler of handlers) {
2278
- handler(...args);
2279
- }
2280
- }
2436
+ if (handlers) for (const handler of handlers) handler(...args);
2281
2437
  }
2282
- // ── Public API ────────────────────────────────────────────────
2283
- /** Submit a prompt. Queues if already executing (max 1 queued). */
2284
2438
  async submit(input, displayInput, rawInput) {
2285
2439
  await this.ensureInitialized();
2286
2440
  if (this.executing) {
@@ -2291,27 +2445,24 @@ var InteractiveSession = class {
2291
2445
  }
2292
2446
  await this.executePrompt(input, displayInput, rawInput);
2293
2447
  }
2294
- /** Execute a system command by name. Returns null if not found. */
2295
2448
  async executeCommand(name, args) {
2296
2449
  await this.ensureInitialized();
2297
2450
  return this.commandExecutor.execute(name, this, args);
2298
2451
  }
2299
- /** List all registered system commands. */
2300
2452
  listCommands() {
2301
2453
  return this.commandExecutor.listCommands().map((cmd) => ({
2302
2454
  name: cmd.name,
2303
2455
  description: cmd.description
2304
2456
  }));
2305
2457
  }
2306
- /** Abort current execution and clear queue. */
2307
2458
  abort() {
2308
- this.pendingPrompt = null;
2309
- this.pendingDisplayInput = void 0;
2310
- this.pendingRawInput = void 0;
2459
+ this.clearPendingQueue();
2311
2460
  this.session?.abort();
2312
2461
  }
2313
- /** Cancel queued prompt without aborting current execution. */
2314
2462
  cancelQueue() {
2463
+ this.clearPendingQueue();
2464
+ }
2465
+ clearPendingQueue() {
2315
2466
  this.pendingPrompt = null;
2316
2467
  this.pendingDisplayInput = void 0;
2317
2468
  this.pendingRawInput = void 0;
@@ -2322,11 +2473,9 @@ var InteractiveSession = class {
2322
2473
  getPendingPrompt() {
2323
2474
  return this.pendingPrompt;
2324
2475
  }
2325
- /** Get full history timeline (chat + events) for TUI rendering */
2326
2476
  getFullHistory() {
2327
2477
  return this.history;
2328
2478
  }
2329
- /** Get chat messages only (backward compatible) */
2330
2479
  getMessages() {
2331
2480
  return this.history.filter((e) => e.category === "chat").map((e) => e.data);
2332
2481
  }
@@ -2339,11 +2488,12 @@ var InteractiveSession = class {
2339
2488
  getContextState() {
2340
2489
  return this.getSessionOrThrow().getContextState();
2341
2490
  }
2342
- /** Get session name. */
2343
2491
  getName() {
2344
2492
  return this.sessionName;
2345
2493
  }
2346
- /** Set session name and persist if store is available. */
2494
+ getSession() {
2495
+ return this.getSessionOrThrow();
2496
+ }
2347
2497
  setName(name) {
2348
2498
  this.sessionName = name;
2349
2499
  if (this.sessionStore && this.session) {
@@ -2359,15 +2509,9 @@ var InteractiveSession = class {
2359
2509
  }
2360
2510
  }
2361
2511
  }
2362
- /** Attach a transport adapter to this session. Calls transport.attach(this). */
2363
2512
  attachTransport(transport) {
2364
2513
  transport.attach(this);
2365
2514
  }
2366
- /** Access underlying Session. For advanced use / testing only. */
2367
- getSession() {
2368
- return this.getSessionOrThrow();
2369
- }
2370
- // ── Execution ─────────────────────────────────────────────────
2371
2515
  async executePrompt(input, displayInput, rawInput) {
2372
2516
  this.executing = true;
2373
2517
  this.clearStreaming();
@@ -2377,25 +2521,35 @@ var InteractiveSession = class {
2377
2521
  try {
2378
2522
  const response = await this.getSessionOrThrow().run(input, rawInput);
2379
2523
  this.flushStreaming();
2380
- this.pushToolSummaryMessage();
2524
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2381
2525
  this.clearStreaming();
2382
- const result = this.buildResult(response || "(empty response)", historyBefore);
2526
+ const result = buildResult(
2527
+ response || "(empty response)",
2528
+ this.getSessionOrThrow().getHistory(),
2529
+ this.history,
2530
+ historyBefore,
2531
+ this.getContextState()
2532
+ );
2383
2533
  this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
2384
2534
  this.emit("complete", result);
2385
2535
  this.emit("context_update", this.getContextState());
2386
2536
  } catch (err) {
2387
2537
  this.flushStreaming();
2388
2538
  if (isAbortError(err)) {
2389
- const result = this.buildInterruptedResult(historyBefore);
2390
- this.pushToolSummaryMessage();
2539
+ const result = buildInterruptedResult(
2540
+ this.getSessionOrThrow().getHistory(),
2541
+ this.history,
2542
+ historyBefore,
2543
+ this.getContextState()
2544
+ );
2545
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2391
2546
  this.clearStreaming();
2392
- if (result.response) {
2547
+ if (result.response)
2393
2548
  this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
2394
- }
2395
2549
  this.history.push(messageToHistoryEntry(createSystemMessage("Interrupted by user.")));
2396
2550
  this.emit("interrupted", result);
2397
2551
  } else {
2398
- this.pushToolSummaryMessage();
2552
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2399
2553
  this.clearStreaming();
2400
2554
  const errMsg = err instanceof Error ? err.message : String(err);
2401
2555
  this.history.push(messageToHistoryEntry(createSystemMessage(`Error: ${errMsg}`)));
@@ -2405,33 +2559,23 @@ var InteractiveSession = class {
2405
2559
  this.executing = false;
2406
2560
  this.emit("thinking", false);
2407
2561
  if (this.sessionStore && this.session) {
2408
- try {
2409
- const sessionId = this.getSessionOrThrow().getSessionId();
2410
- const existing = this.sessionStore.load(sessionId);
2411
- this.sessionStore.save({
2412
- id: sessionId,
2413
- name: this.sessionName ?? existing?.name,
2414
- cwd: this.cwd ?? "",
2415
- createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2416
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2417
- messages: this.getSessionOrThrow().getHistory(),
2418
- history: this.history
2419
- });
2420
- } catch {
2421
- }
2562
+ persistSession(
2563
+ this.sessionStore,
2564
+ this.session,
2565
+ this.sessionName,
2566
+ this.cwd ?? "",
2567
+ this.history
2568
+ );
2422
2569
  }
2423
2570
  if (this.pendingPrompt) {
2424
2571
  const queued = this.pendingPrompt;
2425
2572
  const queuedDisplay = this.pendingDisplayInput;
2426
2573
  const queuedRaw = this.pendingRawInput;
2427
- this.pendingPrompt = null;
2428
- this.pendingDisplayInput = void 0;
2429
- this.pendingRawInput = void 0;
2574
+ this.clearPendingQueue();
2430
2575
  setTimeout(() => this.executePrompt(queued, queuedDisplay, queuedRaw), 0);
2431
2576
  }
2432
2577
  }
2433
2578
  }
2434
- // ── Streaming callbacks ───────────────────────────────────────
2435
2579
  handleTextDelta(delta) {
2436
2580
  this.streamingText += delta;
2437
2581
  this.emit("text_delta", delta);
@@ -2442,67 +2586,17 @@ var InteractiveSession = class {
2442
2586
  }
2443
2587
  }
2444
2588
  handleToolExecution(event) {
2589
+ const streamingState = { activeTools: this.activeTools, history: this.history };
2445
2590
  if (event.type === "start") {
2446
- const firstArg = extractFirstArg(event.toolArgs);
2447
- const state = { toolName: event.toolName, firstArg, isRunning: true };
2448
- this.activeTools.push(state);
2449
- this.emit("tool_start", state);
2450
- this.history.push({
2451
- id: randomUUID(),
2452
- timestamp: /* @__PURE__ */ new Date(),
2453
- category: "event",
2454
- type: "tool-start",
2455
- data: { toolName: event.toolName, firstArg, isRunning: true }
2456
- });
2591
+ const toolState = applyToolStart(streamingState, event);
2592
+ this.activeTools = streamingState.activeTools;
2593
+ this.emit("tool_start", toolState);
2457
2594
  } else {
2458
- const result = event.denied ? "denied" : event.success === false ? "error" : "success";
2459
- const idx = this.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
2460
- if (idx !== -1) {
2461
- const finished = { ...this.activeTools[idx], isRunning: false, result };
2462
- this.activeTools[idx] = finished;
2463
- this.trimCompletedTools();
2464
- this.emit("tool_end", finished);
2465
- this.history.push({
2466
- id: randomUUID(),
2467
- timestamp: /* @__PURE__ */ new Date(),
2468
- category: "event",
2469
- type: "tool-end",
2470
- data: {
2471
- toolName: finished.toolName,
2472
- firstArg: finished.firstArg,
2473
- isRunning: false,
2474
- result
2475
- }
2476
- });
2477
- }
2595
+ const finished = applyToolEnd(streamingState, event);
2596
+ this.activeTools = streamingState.activeTools;
2597
+ if (finished) this.emit("tool_end", finished);
2478
2598
  }
2479
2599
  }
2480
- // ── Helpers ───────────────────────────────────────────────────
2481
- /** Push tool execution summary into messages (before Robota response).
2482
- * Moves tool info from activeTools (real-time display) to messages (permanent display).
2483
- * After this, activeTools will be cleared by clearStreaming(). */
2484
- pushToolSummaryMessage() {
2485
- if (this.activeTools.length === 0) return;
2486
- const summary = this.activeTools.map((t) => {
2487
- const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
2488
- return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
2489
- }).join("\n");
2490
- this.history.push({
2491
- id: randomUUID(),
2492
- timestamp: /* @__PURE__ */ new Date(),
2493
- category: "event",
2494
- type: "tool-summary",
2495
- data: {
2496
- tools: this.activeTools.map((t) => ({
2497
- toolName: t.toolName,
2498
- firstArg: t.firstArg,
2499
- isRunning: t.isRunning,
2500
- result: t.result
2501
- })),
2502
- summary
2503
- }
2504
- });
2505
- }
2506
2600
  clearStreaming() {
2507
2601
  this.streamingText = "";
2508
2602
  this.activeTools = [];
@@ -2517,81 +2611,6 @@ var InteractiveSession = class {
2517
2611
  this.flushTimer = null;
2518
2612
  }
2519
2613
  }
2520
- buildResult(response, historyBefore) {
2521
- const toolSummaries = this.extractToolSummaries(historyBefore);
2522
- return {
2523
- response,
2524
- history: this.history,
2525
- toolSummaries,
2526
- contextState: this.getContextState()
2527
- };
2528
- }
2529
- buildInterruptedResult(historyBefore) {
2530
- const history = this.getSessionOrThrow().getHistory();
2531
- const toolSummaries = this.extractToolSummaries(historyBefore);
2532
- const parts = [];
2533
- for (let i = historyBefore; i < history.length; i++) {
2534
- const msg = history[i];
2535
- if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
2536
- }
2537
- return {
2538
- response: parts.join("\n\n"),
2539
- history: this.history,
2540
- toolSummaries,
2541
- contextState: this.getContextState()
2542
- };
2543
- }
2544
- extractToolSummaries(historyBefore) {
2545
- const history = this.getSessionOrThrow().getHistory();
2546
- const summaries = [];
2547
- for (let i = historyBefore; i < history.length; i++) {
2548
- const msg = history[i];
2549
- if (msg?.role === "assistant" && msg.toolCalls) {
2550
- for (const tc of msg.toolCalls) {
2551
- summaries.push({ name: tc.function.name, args: tc.function.arguments });
2552
- }
2553
- }
2554
- }
2555
- return summaries;
2556
- }
2557
- trimCompletedTools() {
2558
- const completed = this.activeTools.filter((t) => !t.isRunning);
2559
- if (completed.length > MAX_COMPLETED_TOOLS) {
2560
- const excess = completed.length - MAX_COMPLETED_TOOLS;
2561
- let removed = 0;
2562
- this.activeTools = this.activeTools.filter((t) => {
2563
- if (!t.isRunning && removed < excess) {
2564
- removed++;
2565
- return false;
2566
- }
2567
- return true;
2568
- });
2569
- }
2570
- }
2571
- };
2572
- function isAbortError(err) {
2573
- return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
2574
- }
2575
- function extractFirstArg(toolArgs) {
2576
- if (!toolArgs) return "";
2577
- const firstVal = Object.values(toolArgs)[0];
2578
- const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
2579
- return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
2580
- }
2581
- var NOOP_TERMINAL = {
2582
- write: () => {
2583
- },
2584
- writeLine: () => {
2585
- },
2586
- writeMarkdown: () => {
2587
- },
2588
- writeError: () => {
2589
- },
2590
- prompt: () => Promise.resolve(""),
2591
- select: () => Promise.resolve(0),
2592
- spinner: () => ({ stop: () => {
2593
- }, update: () => {
2594
- } })
2595
2614
  };
2596
2615
 
2597
2616
  // src/query.ts
@@ -2746,8 +2765,8 @@ var BuiltinCommandSource = class {
2746
2765
  };
2747
2766
 
2748
2767
  // src/commands/skill-source.ts
2749
- import { readdirSync as readdirSync3, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2750
- import { join as join12, basename as basename2 } from "path";
2768
+ import { readdirSync as readdirSync4, readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
2769
+ import { join as join13, basename as basename2 } from "path";
2751
2770
  import { homedir as homedir4 } from "os";
2752
2771
  var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
2753
2772
  var LIST_KEYS2 = /* @__PURE__ */ new Set(["allowed-tools"]);
@@ -2795,27 +2814,27 @@ function buildCommand(frontmatter, content, fallbackName) {
2795
2814
  return cmd;
2796
2815
  }
2797
2816
  function scanSkillsDir(skillsDir) {
2798
- if (!existsSync9(skillsDir)) return [];
2817
+ if (!existsSync11(skillsDir)) return [];
2799
2818
  const commands = [];
2800
- const entries = readdirSync3(skillsDir, { withFileTypes: true });
2819
+ const entries = readdirSync4(skillsDir, { withFileTypes: true });
2801
2820
  for (const entry of entries) {
2802
2821
  if (!entry.isDirectory()) continue;
2803
- const skillFile = join12(skillsDir, entry.name, "SKILL.md");
2804
- if (!existsSync9(skillFile)) continue;
2805
- const content = readFileSync9(skillFile, "utf-8");
2822
+ const skillFile = join13(skillsDir, entry.name, "SKILL.md");
2823
+ if (!existsSync11(skillFile)) continue;
2824
+ const content = readFileSync10(skillFile, "utf-8");
2806
2825
  const frontmatter = parseFrontmatter2(content);
2807
2826
  commands.push(buildCommand(frontmatter, content, entry.name));
2808
2827
  }
2809
2828
  return commands;
2810
2829
  }
2811
2830
  function scanCommandsDir(commandsDir) {
2812
- if (!existsSync9(commandsDir)) return [];
2831
+ if (!existsSync11(commandsDir)) return [];
2813
2832
  const commands = [];
2814
- const entries = readdirSync3(commandsDir, { withFileTypes: true });
2833
+ const entries = readdirSync4(commandsDir, { withFileTypes: true });
2815
2834
  for (const entry of entries) {
2816
2835
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2817
- const filePath = join12(commandsDir, entry.name);
2818
- const content = readFileSync9(filePath, "utf-8");
2836
+ const filePath = join13(commandsDir, entry.name);
2837
+ const content = readFileSync10(filePath, "utf-8");
2819
2838
  const frontmatter = parseFrontmatter2(content);
2820
2839
  const fallbackName = basename2(entry.name, ".md");
2821
2840
  commands.push(buildCommand(frontmatter, content, fallbackName));
@@ -2834,10 +2853,10 @@ var SkillCommandSource = class {
2834
2853
  getCommands() {
2835
2854
  if (this.cachedCommands) return this.cachedCommands;
2836
2855
  const sources = [
2837
- scanSkillsDir(join12(this.cwd, ".claude", "skills")),
2838
- scanCommandsDir(join12(this.cwd, ".claude", "commands")),
2839
- scanSkillsDir(join12(this.home, ".robota", "skills")),
2840
- scanSkillsDir(join12(this.cwd, ".agents", "skills"))
2856
+ scanSkillsDir(join13(this.cwd, ".claude", "skills")),
2857
+ scanCommandsDir(join13(this.cwd, ".claude", "commands")),
2858
+ scanSkillsDir(join13(this.home, ".robota", "skills")),
2859
+ scanSkillsDir(join13(this.cwd, ".agents", "skills"))
2841
2860
  ];
2842
2861
  const seen = /* @__PURE__ */ new Set();
2843
2862
  const merged = [];