@robota-sdk/agent-sdk 3.0.0-beta.5 → 3.0.0-beta.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,41 +30,155 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- DEFAULT_TOOL_DESCRIPTIONS: () => DEFAULT_TOOL_DESCRIPTIONS,
34
- FileSessionLogger: () => import_agent_sessions3.FileSessionLogger,
35
- Session: () => import_agent_sessions2.Session,
36
- SessionStore: () => import_agent_sessions4.SessionStore,
37
- SilentSessionLogger: () => import_agent_sessions3.SilentSessionLogger,
38
- TRUST_TO_MODE: () => import_agent_core.TRUST_TO_MODE,
39
- agentTool: () => agentTool,
40
- bashTool: () => import_agent_tools3.bashTool,
41
- buildSystemPrompt: () => buildSystemPrompt,
42
- createDefaultTools: () => createDefaultTools,
43
- createProvider: () => createProvider,
44
- createSession: () => createSession,
45
- detectProject: () => detectProject,
46
- editTool: () => import_agent_tools6.editTool,
47
- evaluatePermission: () => import_agent_core2.evaluatePermission,
48
- globTool: () => import_agent_tools7.globTool,
49
- grepTool: () => import_agent_tools8.grepTool,
50
- loadConfig: () => loadConfig,
51
- loadContext: () => loadContext,
33
+ AgentExecutor: () => AgentExecutor,
34
+ BUILT_IN_AGENTS: () => BUILT_IN_AGENTS,
35
+ BuiltinCommandSource: () => BuiltinCommandSource,
36
+ BundlePluginInstaller: () => BundlePluginInstaller,
37
+ BundlePluginLoader: () => BundlePluginLoader,
38
+ CommandRegistry: () => CommandRegistry,
39
+ InteractiveSession: () => InteractiveSession,
40
+ MarketplaceClient: () => MarketplaceClient,
41
+ PluginCommandSource: () => PluginCommandSource,
42
+ PluginSettingsStore: () => PluginSettingsStore,
43
+ PromptExecutor: () => PromptExecutor,
44
+ SkillCommandSource: () => SkillCommandSource,
45
+ TRUST_TO_MODE: () => import_agent_core3.TRUST_TO_MODE,
46
+ assembleSubagentPrompt: () => assembleSubagentPrompt,
47
+ buildSkillPrompt: () => buildSkillPrompt,
48
+ chatEntryToMessage: () => import_agent_core4.chatEntryToMessage,
49
+ createAgentTool: () => createAgentTool,
50
+ createQuery: () => createQuery,
51
+ createSubagentLogger: () => createSubagentLogger,
52
+ createSubagentSession: () => createSubagentSession,
53
+ evaluatePermission: () => import_agent_core5.evaluatePermission,
54
+ getBuiltInAgent: () => getBuiltInAgent,
55
+ getForkWorkerSuffix: () => getForkWorkerSuffix,
56
+ getMessagesForAPI: () => import_agent_core4.getMessagesForAPI,
57
+ getSubagentSuffix: () => getSubagentSuffix,
58
+ isChatEntry: () => import_agent_core4.isChatEntry,
59
+ messageToHistoryEntry: () => import_agent_core4.messageToHistoryEntry,
60
+ parseFrontmatter: () => parseFrontmatter2,
61
+ preprocessShellCommands: () => preprocessShellCommands,
52
62
  projectPaths: () => projectPaths,
53
63
  promptForApproval: () => promptForApproval,
54
- query: () => query,
55
- readTool: () => import_agent_tools4.readTool,
56
- runHooks: () => import_agent_core3.runHooks,
57
- setAgentToolDeps: () => setAgentToolDeps,
58
- userPaths: () => userPaths,
59
- writeTool: () => import_agent_tools5.writeTool
64
+ resolveSubagentLogDir: () => resolveSubagentLogDir,
65
+ retrieveAgentToolDeps: () => retrieveAgentToolDeps,
66
+ runHooks: () => import_agent_core6.runHooks,
67
+ storeAgentToolDeps: () => storeAgentToolDeps,
68
+ substituteVariables: () => substituteVariables,
69
+ userPaths: () => userPaths
60
70
  });
61
71
  module.exports = __toCommonJS(index_exports);
62
72
 
63
- // src/types.ts
64
- var import_agent_core = require("@robota-sdk/agent-core");
73
+ // src/hooks/prompt-executor.ts
74
+ function extractJson(raw) {
75
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
76
+ if (codeBlockMatch) {
77
+ return codeBlockMatch[1].trim();
78
+ }
79
+ return raw.trim();
80
+ }
81
+ var PromptExecutor = class {
82
+ type = "prompt";
83
+ providerFactory;
84
+ defaultModel;
85
+ constructor(options) {
86
+ this.providerFactory = options.providerFactory;
87
+ this.defaultModel = options.defaultModel;
88
+ }
89
+ async execute(definition, input) {
90
+ const promptDef = definition;
91
+ const model = promptDef.model ?? this.defaultModel;
92
+ try {
93
+ const provider = this.providerFactory(model);
94
+ const prompt = `${promptDef.prompt}
95
+
96
+ Context:
97
+ ${JSON.stringify(input)}
98
+
99
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
100
+ const rawResponse = await provider.complete(prompt);
101
+ const jsonStr = extractJson(rawResponse);
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(jsonStr);
105
+ } catch {
106
+ return {
107
+ exitCode: 1,
108
+ stdout: "",
109
+ stderr: `Failed to parse AI response as JSON: ${rawResponse}`
110
+ };
111
+ }
112
+ if (parsed.ok) {
113
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
114
+ }
115
+ return {
116
+ exitCode: 2,
117
+ stdout: "",
118
+ stderr: parsed.reason ?? "Blocked by prompt hook"
119
+ };
120
+ } catch (err) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ return { exitCode: 1, stdout: "", stderr: message };
123
+ }
124
+ }
125
+ };
126
+
127
+ // src/hooks/agent-executor.ts
128
+ var DEFAULT_MAX_TURNS = 50;
129
+ var DEFAULT_TIMEOUT_SECONDS = 60;
130
+ function extractJson2(raw) {
131
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
132
+ if (codeBlockMatch) {
133
+ return codeBlockMatch[1].trim();
134
+ }
135
+ return raw.trim();
136
+ }
137
+ var AgentExecutor = class {
138
+ type = "agent";
139
+ sessionFactory;
140
+ constructor(options) {
141
+ this.sessionFactory = options.sessionFactory;
142
+ }
143
+ async execute(definition, input) {
144
+ const agentDef = definition;
145
+ const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
146
+ const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
147
+ try {
148
+ const session = this.sessionFactory({ maxTurns, timeout });
149
+ const prompt = `Hook input:
150
+ ${JSON.stringify(input)}
151
+
152
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
153
+ const rawResponse = await session.run(prompt);
154
+ const jsonStr = extractJson2(rawResponse);
155
+ let parsed;
156
+ try {
157
+ parsed = JSON.parse(jsonStr);
158
+ } catch {
159
+ return {
160
+ exitCode: 1,
161
+ stdout: "",
162
+ stderr: `Failed to parse agent response as JSON: ${rawResponse}`
163
+ };
164
+ }
165
+ if (parsed.ok) {
166
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
167
+ }
168
+ return {
169
+ exitCode: 2,
170
+ stdout: "",
171
+ stderr: parsed.reason ?? "Blocked by agent hook"
172
+ };
173
+ } catch (err) {
174
+ const message = err instanceof Error ? err.message : String(err);
175
+ return { exitCode: 1, stdout: "", stderr: message };
176
+ }
177
+ }
178
+ };
65
179
 
66
180
  // src/assembly/create-session.ts
67
- var import_agent_sessions = require("@robota-sdk/agent-sessions");
181
+ var import_agent_sessions2 = require("@robota-sdk/agent-sessions");
68
182
 
69
183
  // src/context/system-prompt-builder.ts
70
184
  var TRUST_LEVEL_DESCRIPTIONS = {
@@ -95,17 +209,38 @@ function buildToolsSection(descriptions) {
95
209
  const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
96
210
  return lines.join("\n");
97
211
  }
212
+ function buildSkillsSection(skills) {
213
+ const invocable = skills.filter((s) => s.disableModelInvocation !== true);
214
+ if (invocable.length === 0) {
215
+ return "";
216
+ }
217
+ const lines = [
218
+ "## Skills",
219
+ "The following skills are available:",
220
+ "",
221
+ ...invocable.map((s) => `- ${s.name}: ${s.description}`)
222
+ ];
223
+ return lines.join("\n");
224
+ }
98
225
  function buildSystemPrompt(params) {
99
- const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo } = params;
226
+ const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
100
227
  const sections = [];
101
- sections.push(
102
- [
103
- "## Role",
104
- "You are an AI coding assistant with access to tools that let you read and modify code.",
105
- "You help developers understand, write, and improve their codebase.",
106
- "Always be precise, follow existing code conventions, and prefer minimal changes."
107
- ].join("\n")
108
- );
228
+ const roleLines = [
229
+ "## Role",
230
+ "You are an AI coding assistant with access to tools that let you read and modify code.",
231
+ "You help developers understand, write, and improve their codebase.",
232
+ "Always be precise, follow existing code conventions, and prefer minimal changes."
233
+ ];
234
+ if (language) {
235
+ roleLines.push(
236
+ `Always respond in ${language}. Use ${language} for all explanations and communications.`
237
+ );
238
+ }
239
+ sections.push(roleLines.join("\n"));
240
+ if (cwd) {
241
+ sections.push(`## Working Directory
242
+ \`${cwd}\``);
243
+ }
109
244
  sections.push(buildProjectSection(projectInfo));
110
245
  sections.push(
111
246
  [
@@ -131,6 +266,12 @@ function buildSystemPrompt(params) {
131
266
  if (toolsSection.length > 0) {
132
267
  sections.push(toolsSection);
133
268
  }
269
+ if (params.skills !== void 0 && params.skills.length > 0) {
270
+ const skillsSection = buildSkillsSection(params.skills);
271
+ if (skillsSection.length > 0) {
272
+ sections.push(skillsSection);
273
+ }
274
+ }
134
275
  return sections.join("\n\n");
135
276
  }
136
277
 
@@ -158,96 +299,554 @@ function createDefaultTools() {
158
299
  ];
159
300
  }
160
301
 
161
- // src/assembly/create-provider.ts
162
- var import_agent_provider_anthropic = require("@robota-sdk/agent-provider-anthropic");
163
- function createProvider(config) {
164
- const apiKey = config.provider.apiKey ?? process.env["ANTHROPIC_API_KEY"];
165
- if (!apiKey) {
166
- throw new Error(
167
- "ANTHROPIC_API_KEY is not set. Set the environment variable or configure provider.apiKey in ~/.robota/settings.json"
168
- );
302
+ // src/tools/agent-tool.ts
303
+ var import_zod = require("zod");
304
+ var import_agent_tools2 = require("@robota-sdk/agent-tools");
305
+
306
+ // src/agents/built-in-agents.ts
307
+ 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.
308
+
309
+ Your role is to complete the assigned task thoroughly and accurately. Follow these guidelines:
310
+
311
+ - Execute the task as described in the prompt. Do not expand scope beyond what is requested.
312
+ - Use the most appropriate tools for each step. Prefer precise tools (Read, Grep, Glob) over broad ones (Bash) when possible.
313
+ - Report your findings clearly and concisely when the task is complete.
314
+ - If a task cannot be completed, explain why and what information is missing.
315
+ - Maintain the same code quality standards as the parent session (strict types, no fallbacks, proper error handling).`;
316
+ var EXPLORE_SYSTEM_PROMPT = `You are a codebase exploration and analysis agent. Your purpose is to search, read, and understand code without making any modifications.
317
+
318
+ You operate in read-only mode. You must NEVER attempt to write or edit files. Your tools are restricted to read-only operations: reading files, searching with grep and glob, and running non-destructive bash commands.
319
+
320
+ Your role is to answer questions about the codebase by:
321
+
322
+ - Searching for relevant files, symbols, and patterns using Glob and Grep.
323
+ - Reading source files, configuration, and documentation to understand structure and behavior.
324
+ - Tracing code paths across modules to understand how components interact.
325
+ - Summarizing findings in a clear, structured format with file paths and line references.
326
+ - Identifying architectural patterns, dependencies, and potential issues.
327
+
328
+ When exploring, prefer targeted searches over broad scans. Start with the most likely locations and narrow down. Always include absolute file paths in your responses so the caller can navigate directly to relevant code.`;
329
+ var PLAN_SYSTEM_PROMPT = `You are a planning, research, and architecture agent. Your purpose is to analyze requirements, research approaches, and produce structured plans without making any code modifications.
330
+
331
+ You operate in read-only mode. You must NEVER attempt to write or edit files. Your tools are restricted to read-only operations.
332
+
333
+ Your role is to:
334
+
335
+ - Analyze the current codebase state relevant to the task by reading specs, source code, and tests.
336
+ - Research implementation approaches by examining existing patterns and architectural conventions in the repository.
337
+ - Identify affected files, modules, and interfaces that a proposed change would touch.
338
+ - Assess risks, dependencies, and potential breaking changes.
339
+ - Produce a structured implementation plan with clear steps, file lists, and ordering.
340
+ - Consider edge cases, error handling, and test coverage requirements.
341
+
342
+ Output your plan in a structured format with numbered steps. For each step, specify which files are involved and what changes are needed. Flag any decisions that require human judgment or clarification.`;
343
+ var BUILT_IN_AGENTS = [
344
+ {
345
+ name: "general-purpose",
346
+ description: "General-purpose task execution agent with full tool access.",
347
+ systemPrompt: GENERAL_PURPOSE_SYSTEM_PROMPT
348
+ },
349
+ {
350
+ name: "Explore",
351
+ description: "Read-only codebase exploration and analysis agent.",
352
+ systemPrompt: EXPLORE_SYSTEM_PROMPT,
353
+ model: "claude-haiku-4-5",
354
+ disallowedTools: ["Write", "Edit"]
355
+ },
356
+ {
357
+ name: "Plan",
358
+ description: "Read-only planning, research, and architecture agent.",
359
+ systemPrompt: PLAN_SYSTEM_PROMPT,
360
+ disallowedTools: ["Write", "Edit"]
361
+ }
362
+ ];
363
+ function getBuiltInAgent(name) {
364
+ return BUILT_IN_AGENTS.find((agent) => agent.name === name);
365
+ }
366
+
367
+ // src/assembly/create-subagent-session.ts
368
+ var import_agent_sessions = require("@robota-sdk/agent-sessions");
369
+
370
+ // src/assembly/subagent-prompts.ts
371
+ function getSubagentSuffix() {
372
+ return `When you complete the task, respond with a concise report covering what was done and any key findings \u2014 the caller will relay this to the user, so it only needs the essentials.
373
+
374
+ In your final response, share file paths (always absolute, never relative) that are relevant to the task. Include code snippets only when the exact text is load-bearing \u2014 do not recap code you merely read.
375
+
376
+ Do not use emojis.`;
377
+ }
378
+ function getForkWorkerSuffix() {
379
+ return `You are a worker subagent executing a specific task. Do NOT spawn sub-agents; execute directly. Keep your report under 500 words. Use this structure:
380
+ - Scope: What was requested
381
+ - Result: What was done
382
+ - Key files: Relevant file paths (absolute)
383
+ - Files changed: List of modifications
384
+ - Issues: Any problems encountered`;
385
+ }
386
+ function assembleSubagentPrompt(options) {
387
+ const parts = [options.agentBody];
388
+ if (options.claudeMd) {
389
+ parts.push(options.claudeMd);
390
+ }
391
+ if (options.agentsMd) {
392
+ parts.push(options.agentsMd);
393
+ }
394
+ const suffix = options.isForkWorker ? getForkWorkerSuffix() : getSubagentSuffix();
395
+ parts.push(suffix);
396
+ return parts.join("\n\n");
397
+ }
398
+
399
+ // src/assembly/create-subagent-session.ts
400
+ var MODEL_SHORTCUTS = {
401
+ sonnet: "claude-sonnet-4-6",
402
+ haiku: "claude-haiku-4-5",
403
+ opus: "claude-opus-4-6"
404
+ };
405
+ function resolveModelId(shortName, _parentModel) {
406
+ return MODEL_SHORTCUTS[shortName] ?? shortName;
407
+ }
408
+ function filterTools(parentTools, agentDefinition) {
409
+ let tools = [...parentTools];
410
+ if (agentDefinition.disallowedTools) {
411
+ const denySet = new Set(agentDefinition.disallowedTools);
412
+ tools = tools.filter((t) => !denySet.has(t.getName()));
413
+ }
414
+ if (agentDefinition.tools) {
415
+ const allowSet = new Set(agentDefinition.tools);
416
+ tools = tools.filter((t) => allowSet.has(t.getName()));
417
+ }
418
+ tools = tools.filter((t) => t.getName() !== "Agent");
419
+ return tools;
420
+ }
421
+ function createSubagentSession(options) {
422
+ const { agentDefinition, parentConfig, parentContext, parentTools, terminal } = options;
423
+ const tools = filterTools(parentTools, agentDefinition);
424
+ const model = agentDefinition.model ? resolveModelId(agentDefinition.model, parentConfig.provider.model) : parentConfig.provider.model;
425
+ const systemMessage = assembleSubagentPrompt({
426
+ agentBody: agentDefinition.systemPrompt,
427
+ claudeMd: parentContext.claudeMd,
428
+ agentsMd: parentContext.agentsMd,
429
+ isForkWorker: options.isForkWorker ?? false
430
+ });
431
+ const provider = options.provider;
432
+ return new import_agent_sessions.Session({
433
+ tools,
434
+ provider,
435
+ systemMessage,
436
+ terminal,
437
+ model,
438
+ maxTurns: agentDefinition.maxTurns,
439
+ permissions: parentConfig.permissions,
440
+ permissionMode: options.permissionMode,
441
+ defaultTrustLevel: parentConfig.defaultTrustLevel,
442
+ permissionHandler: options.permissionHandler,
443
+ hooks: options.hooks,
444
+ hookTypeExecutors: options.hookTypeExecutors,
445
+ onTextDelta: options.onTextDelta,
446
+ onToolExecution: options.onToolExecution
447
+ });
448
+ }
449
+
450
+ // src/tools/agent-tool.ts
451
+ function asZodSchema(schema) {
452
+ return schema;
453
+ }
454
+ var AgentSchema = import_zod.z.object({
455
+ prompt: import_zod.z.string().describe("The task for the subagent to perform"),
456
+ subagent_type: import_zod.z.string().optional().describe('Agent type: "general-purpose", "Explore", "Plan", or a custom agent name'),
457
+ model: import_zod.z.string().optional().describe("Optional model override")
458
+ });
459
+ var sessionDepsStore = /* @__PURE__ */ new WeakMap();
460
+ function storeAgentToolDeps(key, deps) {
461
+ sessionDepsStore.set(key, deps);
462
+ }
463
+ function retrieveAgentToolDeps(key) {
464
+ return sessionDepsStore.get(key);
465
+ }
466
+ function resolveAgentDefinition(agentType, customRegistry) {
467
+ const builtIn = getBuiltInAgent(agentType);
468
+ if (builtIn) return builtIn;
469
+ if (customRegistry) return customRegistry(agentType);
470
+ return void 0;
471
+ }
472
+ function generateAgentId() {
473
+ return `agent_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
474
+ }
475
+ function createAgentTool(deps) {
476
+ async function runAgent(args) {
477
+ const agentType = args.subagent_type ?? "general-purpose";
478
+ const agentDef = resolveAgentDefinition(agentType, deps.customAgentRegistry);
479
+ if (!agentDef) {
480
+ return JSON.stringify({
481
+ success: false,
482
+ output: "",
483
+ error: `Unknown agent type: ${agentType}`
484
+ });
485
+ }
486
+ const effectiveDef = args.model ? { ...agentDef, model: args.model } : agentDef;
487
+ const session = createSubagentSession({
488
+ agentDefinition: effectiveDef,
489
+ parentConfig: deps.config,
490
+ parentContext: deps.context,
491
+ parentTools: deps.tools,
492
+ provider: deps.provider,
493
+ terminal: deps.terminal,
494
+ permissionMode: deps.permissionMode,
495
+ permissionHandler: deps.permissionHandler,
496
+ hooks: deps.hooks,
497
+ hookTypeExecutors: deps.hookTypeExecutors,
498
+ onTextDelta: deps.onTextDelta,
499
+ onToolExecution: deps.onToolExecution
500
+ });
501
+ const agentId = generateAgentId();
502
+ try {
503
+ const response = await session.run(args.prompt);
504
+ return JSON.stringify({
505
+ success: true,
506
+ output: response,
507
+ agentId
508
+ });
509
+ } catch (err) {
510
+ const message = err instanceof Error ? err.message : String(err);
511
+ return JSON.stringify({
512
+ success: false,
513
+ output: "",
514
+ error: `Sub-agent error: ${message}`,
515
+ agentId
516
+ });
517
+ }
518
+ }
519
+ return (0, import_agent_tools2.createZodFunctionTool)(
520
+ "Agent",
521
+ "Launch a subagent to handle a task in an isolated context. The subagent gets its own context window and returns a result when done. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.",
522
+ asZodSchema(AgentSchema),
523
+ async (params) => {
524
+ return runAgent(params);
525
+ }
526
+ );
527
+ }
528
+
529
+ // src/agents/agent-definition-loader.ts
530
+ var import_node_fs = require("fs");
531
+ var import_node_path = require("path");
532
+ var import_node_os = require("os");
533
+ var LIST_KEYS = /* @__PURE__ */ new Set(["tools", "disallowedTools"]);
534
+ var NUMBER_KEYS = /* @__PURE__ */ new Set(["maxTurns"]);
535
+ function parseFrontmatter(content) {
536
+ const lines = content.split("\n");
537
+ if (lines[0]?.trim() !== "---") {
538
+ return { frontmatter: null, body: content };
539
+ }
540
+ let endIndex = -1;
541
+ for (let i = 1; i < lines.length; i++) {
542
+ if (lines[i]?.trim() === "---") {
543
+ endIndex = i;
544
+ break;
545
+ }
546
+ }
547
+ if (endIndex === -1) {
548
+ return { frontmatter: null, body: content };
549
+ }
550
+ const result = {};
551
+ for (let i = 1; i < endIndex; i++) {
552
+ const line = lines[i];
553
+ const match = line.match(/^([a-zA-Z][a-zA-Z0-9]*(?:[A-Z][a-z]*)*):\s*(.+)/);
554
+ if (!match) continue;
555
+ const key = match[1];
556
+ const rawValue = match[2].trim();
557
+ if (LIST_KEYS.has(key)) {
558
+ result[key] = rawValue.split(",").map((s) => s.trim());
559
+ } else if (NUMBER_KEYS.has(key)) {
560
+ result[key] = parseInt(rawValue, 10);
561
+ } else {
562
+ result[key] = rawValue;
563
+ }
169
564
  }
170
- return new import_agent_provider_anthropic.AnthropicProvider({ apiKey });
565
+ const body = lines.slice(endIndex + 1).join("\n").trim();
566
+ return {
567
+ frontmatter: Object.keys(result).length > 0 ? result : null,
568
+ body
569
+ };
570
+ }
571
+ function scanAgentsDir(dir) {
572
+ if (!(0, import_node_fs.existsSync)(dir)) return [];
573
+ const agents = [];
574
+ let entries;
575
+ try {
576
+ entries = (0, import_node_fs.readdirSync)(dir, { withFileTypes: true });
577
+ } catch {
578
+ return [];
579
+ }
580
+ for (const entry of entries) {
581
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
582
+ const filePath = (0, import_node_path.join)(dir, entry.name);
583
+ const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
584
+ const { frontmatter, body } = parseFrontmatter(content);
585
+ const fallbackName = (0, import_node_path.basename)(entry.name, ".md");
586
+ const agent = {
587
+ name: frontmatter?.name ?? fallbackName,
588
+ description: frontmatter?.description ?? "",
589
+ systemPrompt: body
590
+ };
591
+ if (frontmatter?.model !== void 0) agent.model = frontmatter.model;
592
+ if (frontmatter?.maxTurns !== void 0) agent.maxTurns = frontmatter.maxTurns;
593
+ if (frontmatter?.tools !== void 0) agent.tools = frontmatter.tools;
594
+ if (frontmatter?.disallowedTools !== void 0)
595
+ agent.disallowedTools = frontmatter.disallowedTools;
596
+ agents.push(agent);
597
+ }
598
+ return agents;
171
599
  }
600
+ var AgentDefinitionLoader = class {
601
+ cwd;
602
+ home;
603
+ constructor(cwd, home) {
604
+ this.cwd = cwd;
605
+ this.home = home ?? (0, import_node_os.homedir)();
606
+ }
607
+ /** Load all agent definitions, merged with built-in agents. Custom overrides built-in on name collision. */
608
+ loadAll() {
609
+ const sources = [
610
+ scanAgentsDir((0, import_node_path.join)(this.cwd, ".claude", "agents")),
611
+ scanAgentsDir((0, import_node_path.join)(this.home, ".robota", "agents"))
612
+ ];
613
+ const seen = /* @__PURE__ */ new Set();
614
+ const customAgents = [];
615
+ for (const agents of sources) {
616
+ for (const agent of agents) {
617
+ if (!seen.has(agent.name)) {
618
+ seen.add(agent.name);
619
+ customAgents.push(agent);
620
+ }
621
+ }
622
+ }
623
+ const result = [...customAgents];
624
+ for (const builtIn of BUILT_IN_AGENTS) {
625
+ if (!seen.has(builtIn.name)) {
626
+ result.push(builtIn);
627
+ }
628
+ }
629
+ return result;
630
+ }
631
+ /** Get a specific agent by name (custom or built-in). */
632
+ getAgent(name) {
633
+ return this.loadAll().find((agent) => agent.name === name);
634
+ }
635
+ };
172
636
 
173
637
  // src/assembly/create-session.ts
174
638
  function createSession(options) {
175
- const provider = options.provider ?? createProvider(options.config);
639
+ if (!options.provider) {
640
+ throw new Error(
641
+ "provider is required. SDK is provider-neutral \u2014 consumer must create and pass a provider instance."
642
+ );
643
+ }
644
+ const provider = options.provider;
176
645
  const defaultTools = createDefaultTools();
177
646
  const tools = [...defaultTools, ...options.additionalTools ?? []];
647
+ const agentLoader = new AgentDefinitionLoader(process.cwd());
648
+ const hookTypeExecutors = [];
649
+ if (options.providerFactory) {
650
+ hookTypeExecutors.push(
651
+ new PromptExecutor({
652
+ providerFactory: options.providerFactory,
653
+ defaultModel: options.config.provider.model
654
+ })
655
+ );
656
+ }
657
+ if (options.sessionFactory) {
658
+ hookTypeExecutors.push(
659
+ new AgentExecutor({
660
+ sessionFactory: options.sessionFactory
661
+ })
662
+ );
663
+ }
664
+ if (options.additionalHookExecutors) {
665
+ hookTypeExecutors.push(...options.additionalHookExecutors);
666
+ }
667
+ const agentToolDeps = {
668
+ config: options.config,
669
+ context: options.context,
670
+ tools,
671
+ terminal: options.terminal,
672
+ provider,
673
+ permissionMode: options.permissionMode,
674
+ permissionHandler: options.permissionHandler,
675
+ hooks: options.config.hooks,
676
+ hookTypeExecutors: hookTypeExecutors.length > 0 ? hookTypeExecutors : void 0,
677
+ onTextDelta: options.onTextDelta,
678
+ onToolExecution: options.onToolExecution,
679
+ customAgentRegistry: (name) => agentLoader.getAgent(name)
680
+ };
681
+ tools.push(createAgentTool(agentToolDeps));
178
682
  const buildPrompt = options.systemPromptBuilder ?? buildSystemPrompt;
179
683
  const systemMessage = buildPrompt({
180
684
  agentsMd: options.context.agentsMd,
181
685
  claudeMd: options.context.claudeMd,
182
686
  toolDescriptions: options.toolDescriptions ?? DEFAULT_TOOL_DESCRIPTIONS,
183
687
  trustLevel: options.config.defaultTrustLevel,
184
- projectInfo: options.projectInfo ?? { type: "unknown", language: "unknown" }
688
+ projectInfo: options.projectInfo ?? { type: "unknown", language: "unknown" },
689
+ cwd: process.cwd(),
690
+ language: options.config.language
185
691
  });
186
- return new import_agent_sessions.Session({
692
+ const defaultAllow = [
693
+ "Read(.agents/**)",
694
+ "Read(.claude/**)",
695
+ "Read(.robota/**)",
696
+ "Glob(.agents/**)",
697
+ "Glob(.claude/**)",
698
+ "Glob(.robota/**)"
699
+ ];
700
+ const mergedPermissions = {
701
+ allow: [...defaultAllow, ...options.config.permissions.allow ?? []],
702
+ deny: options.config.permissions.deny ?? []
703
+ };
704
+ const session = new import_agent_sessions2.Session({
187
705
  tools,
188
706
  provider,
189
707
  systemMessage,
190
708
  terminal: options.terminal,
191
- permissions: options.config.permissions,
709
+ permissions: mergedPermissions,
192
710
  hooks: options.config.hooks,
193
711
  permissionMode: options.permissionMode,
194
712
  defaultTrustLevel: options.config.defaultTrustLevel,
195
713
  model: options.config.provider.model,
196
714
  maxTurns: options.maxTurns,
197
715
  sessionStore: options.sessionStore,
716
+ sessionId: options.sessionId,
198
717
  permissionHandler: options.permissionHandler,
199
718
  onTextDelta: options.onTextDelta,
719
+ onToolExecution: options.onToolExecution,
200
720
  promptForApproval: options.promptForApproval,
201
721
  onCompact: options.onCompact,
202
722
  compactInstructions: options.compactInstructions ?? options.context.compactInstructions,
203
- sessionLogger: options.sessionLogger
723
+ sessionLogger: options.sessionLogger,
724
+ hookTypeExecutors: hookTypeExecutors.length > 0 ? hookTypeExecutors : void 0
204
725
  });
726
+ storeAgentToolDeps(session, agentToolDeps);
727
+ return session;
205
728
  }
206
729
 
207
- // src/index.ts
208
- var import_agent_sessions2 = require("@robota-sdk/agent-sessions");
730
+ // src/assembly/subagent-logger.ts
731
+ var import_node_fs2 = require("fs");
732
+ var import_node_path2 = require("path");
209
733
  var import_agent_sessions3 = require("@robota-sdk/agent-sessions");
734
+ function createSubagentLogger(parentSessionId, _agentId, baseLogsDir) {
735
+ const subagentDir = (0, import_node_path2.join)(baseLogsDir, parentSessionId, "subagents");
736
+ (0, import_node_fs2.mkdirSync)(subagentDir, { recursive: true });
737
+ return new import_agent_sessions3.FileSessionLogger(subagentDir);
738
+ }
739
+ function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
740
+ return (0, import_node_path2.join)(baseLogsDir, parentSessionId, "subagents");
741
+ }
742
+
743
+ // src/interactive/interactive-session.ts
210
744
  var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
211
745
 
746
+ // src/paths.ts
747
+ var import_node_path3 = require("path");
748
+ var import_node_os2 = require("os");
749
+ function projectPaths(cwd) {
750
+ const base = (0, import_node_path3.join)(cwd, ".robota");
751
+ return {
752
+ settings: (0, import_node_path3.join)(base, "settings.json"),
753
+ settingsLocal: (0, import_node_path3.join)(base, "settings.local.json"),
754
+ logs: (0, import_node_path3.join)(base, "logs"),
755
+ sessions: (0, import_node_path3.join)(base, "sessions")
756
+ };
757
+ }
758
+ function userPaths() {
759
+ const base = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota");
760
+ return {
761
+ settings: (0, import_node_path3.join)(base, "settings.json"),
762
+ sessions: (0, import_node_path3.join)(base, "sessions")
763
+ };
764
+ }
765
+
212
766
  // src/config/config-loader.ts
213
767
  var import_fs = require("fs");
214
768
  var import_path = require("path");
215
769
 
216
770
  // src/config/config-types.ts
217
- var import_zod = require("zod");
218
- var ProviderSchema = import_zod.z.object({
219
- name: import_zod.z.string().optional(),
220
- model: import_zod.z.string().optional(),
221
- apiKey: import_zod.z.string().optional()
771
+ var import_zod2 = require("zod");
772
+ var ProviderSchema = import_zod2.z.object({
773
+ name: import_zod2.z.string().optional(),
774
+ model: import_zod2.z.string().optional(),
775
+ apiKey: import_zod2.z.string().optional()
222
776
  });
223
- var PermissionsSchema = import_zod.z.object({
777
+ var PermissionsSchema = import_zod2.z.object({
224
778
  /** Patterns that are always approved without prompting */
225
- allow: import_zod.z.array(import_zod.z.string()).optional(),
779
+ allow: import_zod2.z.array(import_zod2.z.string()).optional(),
226
780
  /** Patterns that are always denied */
227
- deny: import_zod.z.array(import_zod.z.string()).optional()
781
+ deny: import_zod2.z.array(import_zod2.z.string()).optional()
228
782
  });
229
- var EnvSchema = import_zod.z.record(import_zod.z.string()).optional();
230
- var HookDefinitionSchema = import_zod.z.object({
231
- type: import_zod.z.literal("command"),
232
- command: import_zod.z.string()
783
+ var EnvSchema = import_zod2.z.record(import_zod2.z.string()).optional();
784
+ var CommandHookDefinitionSchema = import_zod2.z.object({
785
+ type: import_zod2.z.literal("command"),
786
+ command: import_zod2.z.string(),
787
+ timeout: import_zod2.z.number().optional()
233
788
  });
234
- var HookGroupSchema = import_zod.z.object({
235
- matcher: import_zod.z.string(),
236
- hooks: import_zod.z.array(HookDefinitionSchema)
789
+ var HttpHookDefinitionSchema = import_zod2.z.object({
790
+ type: import_zod2.z.literal("http"),
791
+ url: import_zod2.z.string(),
792
+ headers: import_zod2.z.record(import_zod2.z.string()).optional(),
793
+ timeout: import_zod2.z.number().optional()
237
794
  });
238
- var HooksSchema = import_zod.z.object({
239
- PreToolUse: import_zod.z.array(HookGroupSchema).optional(),
240
- PostToolUse: import_zod.z.array(HookGroupSchema).optional(),
241
- SessionStart: import_zod.z.array(HookGroupSchema).optional(),
242
- Stop: import_zod.z.array(HookGroupSchema).optional()
795
+ var PromptHookDefinitionSchema = import_zod2.z.object({
796
+ type: import_zod2.z.literal("prompt"),
797
+ prompt: import_zod2.z.string(),
798
+ model: import_zod2.z.string().optional()
799
+ });
800
+ var AgentHookDefinitionSchema = import_zod2.z.object({
801
+ type: import_zod2.z.literal("agent"),
802
+ agent: import_zod2.z.string(),
803
+ maxTurns: import_zod2.z.number().optional(),
804
+ timeout: import_zod2.z.number().optional()
805
+ });
806
+ var HookDefinitionSchema = import_zod2.z.discriminatedUnion("type", [
807
+ CommandHookDefinitionSchema,
808
+ HttpHookDefinitionSchema,
809
+ PromptHookDefinitionSchema,
810
+ AgentHookDefinitionSchema
811
+ ]);
812
+ var HookGroupSchema = import_zod2.z.object({
813
+ matcher: import_zod2.z.string(),
814
+ hooks: import_zod2.z.array(HookDefinitionSchema)
815
+ });
816
+ var HooksSchema = import_zod2.z.object({
817
+ PreToolUse: import_zod2.z.array(HookGroupSchema).optional(),
818
+ PostToolUse: import_zod2.z.array(HookGroupSchema).optional(),
819
+ SessionStart: import_zod2.z.array(HookGroupSchema).optional(),
820
+ Stop: import_zod2.z.array(HookGroupSchema).optional(),
821
+ PreCompact: import_zod2.z.array(HookGroupSchema).optional(),
822
+ PostCompact: import_zod2.z.array(HookGroupSchema).optional(),
823
+ UserPromptSubmit: import_zod2.z.array(HookGroupSchema).optional(),
824
+ Notification: import_zod2.z.array(HookGroupSchema).optional()
243
825
  }).optional();
244
- var SettingsSchema = import_zod.z.object({
826
+ var EnabledPluginsSchema = import_zod2.z.record(import_zod2.z.boolean()).optional();
827
+ var MarketplaceSourceSchema = import_zod2.z.object({
828
+ source: import_zod2.z.object({
829
+ type: import_zod2.z.enum(["github", "git", "local", "url"]),
830
+ repo: import_zod2.z.string().optional(),
831
+ url: import_zod2.z.string().optional(),
832
+ path: import_zod2.z.string().optional(),
833
+ ref: import_zod2.z.string().optional()
834
+ })
835
+ });
836
+ var ExtraKnownMarketplacesSchema = import_zod2.z.record(MarketplaceSourceSchema).optional();
837
+ var SettingsSchema = import_zod2.z.object({
245
838
  /** Trust level used when no --permission-mode flag is given */
246
- defaultTrustLevel: import_zod.z.enum(["safe", "moderate", "full"]).optional(),
839
+ defaultTrustLevel: import_zod2.z.enum(["safe", "moderate", "full"]).optional(),
840
+ /** Response language (e.g., "ko", "en", "ja"). Injected into system prompt. */
841
+ language: import_zod2.z.string().optional(),
247
842
  provider: ProviderSchema.optional(),
248
843
  permissions: PermissionsSchema.optional(),
249
844
  env: EnvSchema,
250
- hooks: HooksSchema
845
+ hooks: HooksSchema,
846
+ /** Plugin enablement map: plugin name -> enabled/disabled */
847
+ enabledPlugins: EnabledPluginsSchema,
848
+ /** Extra marketplace URLs for BundlePlugin discovery */
849
+ extraKnownMarketplaces: ExtraKnownMarketplacesSchema
251
850
  });
252
851
 
253
852
  // src/config/config-loader.ts
@@ -271,8 +870,15 @@ function readJsonFile(filePath) {
271
870
  if (!(0, import_fs.existsSync)(filePath)) {
272
871
  return void 0;
273
872
  }
274
- const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
275
- return JSON.parse(raw);
873
+ const raw = (0, import_fs.readFileSync)(filePath, "utf-8").trim();
874
+ if (raw.length === 0) {
875
+ return void 0;
876
+ }
877
+ try {
878
+ return JSON.parse(raw);
879
+ } catch {
880
+ return void 0;
881
+ }
276
882
  }
277
883
  function resolveEnvRef(value) {
278
884
  const ENV_PREFIX = "$ENV:";
@@ -307,13 +913,16 @@ function mergeSettings(layers) {
307
913
  env: {
308
914
  ...merged.env ?? {},
309
915
  ...layer.env ?? {}
310
- }
916
+ },
917
+ enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
918
+ extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
311
919
  };
312
920
  }, {});
313
921
  }
314
922
  function toResolvedConfig(merged) {
315
923
  return {
316
924
  defaultTrustLevel: merged.defaultTrustLevel ?? DEFAULTS.defaultTrustLevel,
925
+ language: merged.language,
317
926
  provider: {
318
927
  name: merged.provider?.name ?? DEFAULTS.provider.name,
319
928
  model: merged.provider?.model ?? DEFAULTS.provider.model,
@@ -324,25 +933,39 @@ function toResolvedConfig(merged) {
324
933
  deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
325
934
  },
326
935
  env: merged.env ?? DEFAULTS.env,
327
- hooks: merged.hooks ?? void 0
936
+ hooks: merged.hooks ?? void 0,
937
+ enabledPlugins: merged.enabledPlugins ?? void 0,
938
+ extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
328
939
  };
329
940
  }
941
+ function getSettingsPaths(cwd) {
942
+ const home = getHomeDir();
943
+ return [
944
+ (0, import_path.join)(home, ".robota", "settings.json"),
945
+ // 1. user (lowest)
946
+ (0, import_path.join)(cwd, ".robota", "settings.json"),
947
+ // 2. project
948
+ (0, import_path.join)(cwd, ".robota", "settings.local.json"),
949
+ // 3. project-local
950
+ (0, import_path.join)(cwd, ".claude", "settings.json"),
951
+ // 4. project, Claude Code compat
952
+ (0, import_path.join)(cwd, ".claude", "settings.local.json")
953
+ // 5. project-local (highest)
954
+ ];
955
+ }
330
956
  async function loadConfig(cwd) {
331
- const userSettingsPath = (0, import_path.join)(getHomeDir(), ".robota", "settings.json");
332
- const projectSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.json");
333
- const localSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.local.json");
334
- const rawLayers = [
335
- readJsonFile(userSettingsPath),
336
- readJsonFile(projectSettingsPath),
337
- readJsonFile(localSettingsPath)
338
- ].filter((v) => v !== void 0);
339
- const parsedLayers = rawLayers.map((raw, index) => {
957
+ const allPaths = getSettingsPaths(cwd);
958
+ const rawEntries = [];
959
+ for (const filePath of allPaths) {
960
+ const raw = readJsonFile(filePath);
961
+ if (raw !== void 0) {
962
+ rawEntries.push({ raw, path: filePath });
963
+ }
964
+ }
965
+ const parsedLayers = rawEntries.map(({ raw, path }) => {
340
966
  const result = SettingsSchema.safeParse(raw);
341
967
  if (!result.success) {
342
- const paths = [userSettingsPath, projectSettingsPath, localSettingsPath].filter(
343
- (_, i) => rawLayers[i] !== void 0
344
- );
345
- throw new Error(`Invalid settings in ${paths[index] ?? "unknown"}: ${result.error.message}`);
968
+ throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
346
969
  }
347
970
  return resolveEnvRefs(result.data);
348
971
  });
@@ -472,196 +1095,1983 @@ async function detectProject(cwd) {
472
1095
  };
473
1096
  }
474
1097
 
475
- // src/permissions/permission-prompt.ts
476
- var import_chalk = __toESM(require("chalk"), 1);
477
- var PERMISSION_OPTIONS = ["Allow", "Deny"];
478
- var ALLOW_INDEX = 0;
479
- function formatArgs(toolArgs) {
480
- const entries = Object.entries(toolArgs);
481
- if (entries.length === 0) {
482
- return "(no arguments)";
483
- }
484
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
485
- }
486
- async function promptForApproval(terminal, toolName, toolArgs) {
487
- terminal.writeLine("");
488
- terminal.writeLine(import_chalk.default.yellow(`[Permission Required] Tool: ${toolName}`));
489
- terminal.writeLine(import_chalk.default.dim(` ${formatArgs(toolArgs)}`));
490
- terminal.writeLine("");
491
- const selected = await terminal.select(PERMISSION_OPTIONS, ALLOW_INDEX);
492
- return selected === ALLOW_INDEX;
493
- }
1098
+ // src/interactive/interactive-session.ts
1099
+ var import_agent_core = require("@robota-sdk/agent-core");
1100
+ var import_node_crypto = require("crypto");
494
1101
 
495
- // src/query.ts
496
- async function query(prompt, options) {
497
- const cwd = options?.cwd ?? process.cwd();
498
- const [config, context, projectInfo] = await Promise.all([
499
- loadConfig(cwd),
500
- loadContext(cwd),
501
- detectProject(cwd)
502
- ]);
503
- const noopTerminal = {
504
- write: () => {
1102
+ // src/commands/system-command.ts
1103
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
1104
+ function createSystemCommands() {
1105
+ return [
1106
+ {
1107
+ name: "help",
1108
+ description: "Show available commands",
1109
+ execute: (_session, _args) => ({
1110
+ message: [
1111
+ "Available commands:",
1112
+ " help \u2014 Show this help",
1113
+ " clear \u2014 Clear conversation",
1114
+ " compact [instr] \u2014 Compact context (optional focus instructions)",
1115
+ " mode [m] \u2014 Show/change permission mode",
1116
+ " model <id> \u2014 Change AI model",
1117
+ " language <code> \u2014 Set response language (ko, en, ja, zh)",
1118
+ " cost \u2014 Show session info",
1119
+ " context \u2014 Context window info",
1120
+ " permissions \u2014 Permission rules",
1121
+ " resume \u2014 Resume a previous session",
1122
+ " rename <name> \u2014 Rename the current session",
1123
+ " reset \u2014 Delete settings and exit"
1124
+ ].join("\n"),
1125
+ success: true
1126
+ })
505
1127
  },
506
- writeLine: () => {
1128
+ {
1129
+ name: "clear",
1130
+ description: "Clear conversation history",
1131
+ execute: (session, _args) => {
1132
+ const underlying = session.getSession();
1133
+ underlying.clearHistory();
1134
+ return { message: "Conversation cleared.", success: true };
1135
+ }
507
1136
  },
508
- writeMarkdown: () => {
1137
+ {
1138
+ name: "compact",
1139
+ description: "Compress context window",
1140
+ execute: async (session, args) => {
1141
+ const underlying = session.getSession();
1142
+ const instructions = args.trim() || void 0;
1143
+ const before = underlying.getContextState().usedPercentage;
1144
+ await underlying.compact(instructions);
1145
+ const after = underlying.getContextState().usedPercentage;
1146
+ return {
1147
+ message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
1148
+ success: true,
1149
+ data: { before, after }
1150
+ };
1151
+ }
509
1152
  },
510
- writeError: () => {
1153
+ {
1154
+ name: "mode",
1155
+ description: "Show/change permission mode",
1156
+ execute: (session, args) => {
1157
+ const underlying = session.getSession();
1158
+ const arg = args.trim().split(/\s+/)[0];
1159
+ if (!arg) {
1160
+ return {
1161
+ message: `Current mode: ${underlying.getPermissionMode()}`,
1162
+ success: true,
1163
+ data: { mode: underlying.getPermissionMode() }
1164
+ };
1165
+ }
1166
+ if (VALID_MODES.includes(arg)) {
1167
+ underlying.setPermissionMode(arg);
1168
+ return {
1169
+ message: `Permission mode set to: ${arg}`,
1170
+ success: true,
1171
+ data: { mode: arg }
1172
+ };
1173
+ }
1174
+ return {
1175
+ message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
1176
+ success: false
1177
+ };
1178
+ }
511
1179
  },
512
- prompt: () => Promise.resolve(""),
513
- select: () => Promise.resolve(0),
514
- spinner: () => ({ stop: () => {
515
- }, update: () => {
516
- } })
517
- };
518
- const session = createSession({
519
- config,
520
- context,
521
- terminal: noopTerminal,
522
- projectInfo,
523
- permissionMode: options?.permissionMode ?? "bypassPermissions",
524
- maxTurns: options?.maxTurns,
525
- provider: options?.provider,
526
- permissionHandler: options?.permissionHandler,
527
- onTextDelta: options?.onTextDelta,
528
- onCompact: options?.onCompact,
529
- compactInstructions: context.compactInstructions,
530
- promptForApproval
531
- });
532
- return session.run(prompt);
533
- }
534
-
535
- // src/index.ts
536
- var import_agent_core2 = require("@robota-sdk/agent-core");
537
- var import_agent_core3 = require("@robota-sdk/agent-core");
538
-
539
- // src/paths.ts
540
- var import_node_path = require("path");
541
- var import_node_os = require("os");
542
- function projectPaths(cwd) {
543
- const base = (0, import_node_path.join)(cwd, ".robota");
544
- return {
545
- settings: (0, import_node_path.join)(base, "settings.json"),
546
- settingsLocal: (0, import_node_path.join)(base, "settings.local.json"),
547
- logs: (0, import_node_path.join)(base, "logs"),
548
- sessions: (0, import_node_path.join)(base, "sessions")
549
- };
550
- }
551
- function userPaths() {
552
- const base = (0, import_node_path.join)((0, import_node_os.homedir)(), ".robota");
553
- return {
554
- settings: (0, import_node_path.join)(base, "settings.json"),
555
- sessions: (0, import_node_path.join)(base, "sessions")
556
- };
557
- }
558
-
559
- // src/tools/agent-tool.ts
560
- var import_zod2 = require("zod");
561
- var import_agent_tools2 = require("@robota-sdk/agent-tools");
562
- function asZodSchema(schema) {
563
- return schema;
564
- }
565
- var AgentSchema = import_zod2.z.object({
566
- prompt: import_zod2.z.string().describe("Task description for the sub-agent"),
567
- description: import_zod2.z.string().optional().describe("Short description of what the sub-agent will do (3-5 words)")
568
- });
569
- var agentToolDeps;
570
- function setAgentToolDeps(deps) {
571
- agentToolDeps = deps;
572
- }
573
- async function runAgent(args) {
574
- if (!agentToolDeps) {
575
- const result = {
576
- success: false,
577
- output: "",
578
- error: "Agent tool not initialized \u2014 missing dependencies"
579
- };
580
- return JSON.stringify(result);
581
- }
582
- const noopTerminal = {
583
- write: () => {
1180
+ {
1181
+ name: "model",
1182
+ description: "Change AI model",
1183
+ execute: (_session, args) => {
1184
+ const modelId = args.trim().split(/\s+/)[0];
1185
+ if (!modelId) {
1186
+ return { message: "Usage: model <model-id>", success: false };
1187
+ }
1188
+ return {
1189
+ message: `Model change requested: ${modelId}`,
1190
+ success: true,
1191
+ data: { modelId }
1192
+ };
1193
+ }
584
1194
  },
585
- writeLine: () => {
1195
+ {
1196
+ name: "language",
1197
+ description: "Set response language",
1198
+ execute: (_session, args) => {
1199
+ const lang = args.trim().split(/\s+/)[0];
1200
+ if (!lang) {
1201
+ return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
1202
+ }
1203
+ return {
1204
+ message: `Language set to "${lang}".`,
1205
+ success: true,
1206
+ data: { language: lang }
1207
+ };
1208
+ }
586
1209
  },
587
- writeMarkdown: () => {
1210
+ {
1211
+ name: "cost",
1212
+ description: "Show session info",
1213
+ execute: (session, _args) => {
1214
+ const underlying = session.getSession();
1215
+ const sessionId = underlying.getSessionId();
1216
+ const messageCount = underlying.getMessageCount();
1217
+ return {
1218
+ message: `Session: ${sessionId}
1219
+ Messages: ${messageCount}`,
1220
+ success: true,
1221
+ data: { sessionId, messageCount }
1222
+ };
1223
+ }
588
1224
  },
589
- writeError: () => {
1225
+ {
1226
+ name: "context",
1227
+ description: "Context window info",
1228
+ execute: (session, _args) => {
1229
+ const ctx = session.getContextState();
1230
+ return {
1231
+ message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
1232
+ success: true,
1233
+ data: {
1234
+ usedTokens: ctx.usedTokens,
1235
+ maxTokens: ctx.maxTokens,
1236
+ percentage: ctx.usedPercentage
1237
+ }
1238
+ };
1239
+ }
590
1240
  },
591
- prompt: () => Promise.resolve(""),
592
- select: () => Promise.resolve(0),
593
- spinner: () => ({ stop: () => {
594
- }, update: () => {
595
- } })
596
- };
597
- const subSession = createSession({
598
- config: agentToolDeps.config,
599
- context: agentToolDeps.context,
600
- projectInfo: agentToolDeps.projectInfo,
601
- terminal: noopTerminal,
602
- // Sub-agents bypass permissions — they inherit parent's trust
603
- permissionMode: "bypassPermissions"
604
- });
605
- try {
606
- const response = await subSession.run(args.prompt);
607
- const result = {
608
- success: true,
609
- output: response
610
- };
611
- return JSON.stringify(result);
612
- } catch (err) {
613
- const message = err instanceof Error ? err.message : String(err);
614
- const result = {
615
- success: false,
616
- output: "",
617
- error: `Sub-agent error: ${message}`
618
- };
619
- return JSON.stringify(result);
620
- }
1241
+ {
1242
+ name: "permissions",
1243
+ description: "Show permission rules",
1244
+ execute: (session, _args) => {
1245
+ const underlying = session.getSession();
1246
+ const mode = underlying.getPermissionMode();
1247
+ const sessionAllowed = underlying.getSessionAllowedTools();
1248
+ const lines = [`Permission mode: ${mode}`];
1249
+ if (sessionAllowed.length > 0) {
1250
+ lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
1251
+ } else {
1252
+ lines.push("No session-approved tools.");
1253
+ }
1254
+ return {
1255
+ message: lines.join("\n"),
1256
+ success: true,
1257
+ data: { mode, sessionAllowed }
1258
+ };
1259
+ }
1260
+ },
1261
+ {
1262
+ name: "resume",
1263
+ description: "Resume a previous session",
1264
+ execute: (_session, _args) => ({
1265
+ message: "Opening session picker...",
1266
+ success: true,
1267
+ data: { triggerResumePicker: true }
1268
+ })
1269
+ },
1270
+ {
1271
+ name: "rename",
1272
+ description: "Rename the current session",
1273
+ execute: (_session, args) => {
1274
+ const name = args.trim();
1275
+ if (!name) {
1276
+ return { message: "Usage: rename <name>", success: false };
1277
+ }
1278
+ return {
1279
+ message: `Session renamed to "${name}".`,
1280
+ success: true,
1281
+ data: { name }
1282
+ };
1283
+ }
1284
+ },
1285
+ {
1286
+ name: "reset",
1287
+ description: "Delete settings",
1288
+ execute: (_session, _args) => {
1289
+ return {
1290
+ message: "Reset requested.",
1291
+ success: true,
1292
+ data: { resetRequested: true }
1293
+ };
1294
+ }
1295
+ }
1296
+ ];
621
1297
  }
622
- var agentTool = (0, import_agent_tools2.createZodFunctionTool)(
623
- "Agent",
624
- "Spawn a sub-agent with isolated context to handle a task. The sub-agent has its own conversation history and can use all tools.",
625
- asZodSchema(AgentSchema),
626
- async (params) => {
627
- return runAgent(params);
1298
+ var SystemCommandExecutor = class {
1299
+ commands;
1300
+ constructor(commands) {
1301
+ this.commands = /* @__PURE__ */ new Map();
1302
+ for (const cmd of commands ?? createSystemCommands()) {
1303
+ this.commands.set(cmd.name, cmd);
1304
+ }
1305
+ }
1306
+ /** Register an additional command. */
1307
+ register(command) {
1308
+ this.commands.set(command.name, command);
1309
+ }
1310
+ /** Execute a command by name. Returns null if command not found. */
1311
+ async execute(name, session, args) {
1312
+ const cmd = this.commands.get(name);
1313
+ if (!cmd) return null;
1314
+ return await cmd.execute(session, args);
628
1315
  }
629
- );
1316
+ /** List all registered commands. */
1317
+ listCommands() {
1318
+ return [...this.commands.values()];
1319
+ }
1320
+ /** Check if a command exists. */
1321
+ hasCommand(name) {
1322
+ return this.commands.has(name);
1323
+ }
1324
+ };
1325
+
1326
+ // src/plugins/plugin-settings-store.ts
1327
+ var import_node_fs3 = require("fs");
1328
+ var import_node_path4 = require("path");
1329
+ var PluginSettingsStore = class {
1330
+ settingsPath;
1331
+ constructor(settingsPath) {
1332
+ this.settingsPath = settingsPath;
1333
+ }
1334
+ /** Read the full settings file from disk. */
1335
+ readAll() {
1336
+ if (!(0, import_node_fs3.existsSync)(this.settingsPath)) {
1337
+ return {};
1338
+ }
1339
+ try {
1340
+ const raw = (0, import_node_fs3.readFileSync)(this.settingsPath, "utf-8");
1341
+ const data = JSON.parse(raw);
1342
+ if (typeof data === "object" && data !== null) {
1343
+ return data;
1344
+ }
1345
+ return {};
1346
+ } catch {
1347
+ return {};
1348
+ }
1349
+ }
1350
+ /** Write the full settings file to disk. */
1351
+ writeAll(settings) {
1352
+ const dir = (0, import_node_path4.dirname)(this.settingsPath);
1353
+ if (!(0, import_node_fs3.existsSync)(dir)) {
1354
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
1355
+ }
1356
+ (0, import_node_fs3.writeFileSync)(this.settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1357
+ }
1358
+ // --- enabledPlugins ---
1359
+ /** Get the enabledPlugins map. */
1360
+ getEnabledPlugins() {
1361
+ const settings = this.readAll();
1362
+ const ep = settings.enabledPlugins;
1363
+ if (typeof ep === "object" && ep !== null) {
1364
+ return ep;
1365
+ }
1366
+ return {};
1367
+ }
1368
+ /** Set a single plugin's enabled state. */
1369
+ setPluginEnabled(pluginId, enabled) {
1370
+ const settings = this.readAll();
1371
+ const ep = this.getEnabledPluginsFrom(settings);
1372
+ ep[pluginId] = enabled;
1373
+ settings.enabledPlugins = ep;
1374
+ this.writeAll(settings);
1375
+ }
1376
+ /** Remove a plugin from enabledPlugins. */
1377
+ removePluginEntry(pluginId) {
1378
+ const settings = this.readAll();
1379
+ const ep = this.getEnabledPluginsFrom(settings);
1380
+ delete ep[pluginId];
1381
+ settings.enabledPlugins = ep;
1382
+ this.writeAll(settings);
1383
+ }
1384
+ // --- extraKnownMarketplaces ---
1385
+ /** Get all persisted marketplace sources. */
1386
+ getMarketplaceSources() {
1387
+ const settings = this.readAll();
1388
+ const extra = settings.extraKnownMarketplaces;
1389
+ if (typeof extra === "object" && extra !== null) {
1390
+ return extra;
1391
+ }
1392
+ return {};
1393
+ }
1394
+ /** Add or update a marketplace source. */
1395
+ setMarketplaceSource(name, source) {
1396
+ const settings = this.readAll();
1397
+ const extra = this.getMarketplaceSourcesFrom(settings);
1398
+ extra[name] = { source };
1399
+ settings.extraKnownMarketplaces = extra;
1400
+ this.writeAll(settings);
1401
+ }
1402
+ /** Remove a marketplace source. */
1403
+ removeMarketplaceSource(name) {
1404
+ const settings = this.readAll();
1405
+ const extra = this.getMarketplaceSourcesFrom(settings);
1406
+ delete extra[name];
1407
+ settings.extraKnownMarketplaces = extra;
1408
+ this.writeAll(settings);
1409
+ }
1410
+ // --- helpers ---
1411
+ getEnabledPluginsFrom(settings) {
1412
+ const ep = settings.enabledPlugins;
1413
+ if (typeof ep === "object" && ep !== null) {
1414
+ return ep;
1415
+ }
1416
+ return {};
1417
+ }
1418
+ getMarketplaceSourcesFrom(settings) {
1419
+ const extra = settings.extraKnownMarketplaces;
1420
+ if (typeof extra === "object" && extra !== null) {
1421
+ return extra;
1422
+ }
1423
+ return {};
1424
+ }
1425
+ };
1426
+
1427
+ // src/plugins/bundle-plugin-loader.ts
1428
+ var import_node_fs4 = require("fs");
1429
+ var import_node_path5 = require("path");
1430
+ function parseSkillFrontmatter(raw) {
1431
+ const trimmed = raw.trimStart();
1432
+ if (!trimmed.startsWith("---")) {
1433
+ return { metadata: {}, content: raw };
1434
+ }
1435
+ const endIndex = trimmed.indexOf("---", 3);
1436
+ if (endIndex === -1) {
1437
+ return { metadata: {}, content: raw };
1438
+ }
1439
+ const frontmatterBlock = trimmed.slice(3, endIndex).trim();
1440
+ const content = trimmed.slice(endIndex + 3).trimStart();
1441
+ const metadata = {};
1442
+ for (const line of frontmatterBlock.split("\n")) {
1443
+ const colonIndex = line.indexOf(":");
1444
+ if (colonIndex === -1) continue;
1445
+ const key = line.slice(0, colonIndex).trim();
1446
+ let value = line.slice(colonIndex + 1).trim();
1447
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
1448
+ const inner = value.slice(1, -1);
1449
+ value = inner.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1450
+ }
1451
+ if (key) {
1452
+ metadata[key] = value;
1453
+ }
1454
+ }
1455
+ return { metadata, content };
1456
+ }
1457
+ function validateManifest(data) {
1458
+ if (typeof data !== "object" || data === null) return null;
1459
+ const obj = data;
1460
+ if (typeof obj.name !== "string") return null;
1461
+ if (typeof obj.version !== "string") return null;
1462
+ if (typeof obj.description !== "string") return null;
1463
+ const features = typeof obj.features === "object" && obj.features !== null ? obj.features : {};
1464
+ return {
1465
+ name: obj.name,
1466
+ version: obj.version,
1467
+ description: obj.description,
1468
+ features: {
1469
+ commands: features.commands === true ? true : void 0,
1470
+ agents: features.agents === true ? true : void 0,
1471
+ skills: features.skills === true ? true : void 0,
1472
+ hooks: features.hooks === true ? true : void 0,
1473
+ mcp: features.mcp === true ? true : void 0
1474
+ }
1475
+ };
1476
+ }
1477
+ function getSortedSubdirs(dirPath) {
1478
+ if (!(0, import_node_fs4.existsSync)(dirPath)) return [];
1479
+ try {
1480
+ const entries = (0, import_node_fs4.readdirSync)(dirPath, { withFileTypes: true });
1481
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
1482
+ } catch {
1483
+ return [];
1484
+ }
1485
+ }
1486
+ var BundlePluginLoader = class {
1487
+ pluginsDir;
1488
+ enabledPlugins;
1489
+ constructor(pluginsDir, enabledPlugins) {
1490
+ this.pluginsDir = pluginsDir;
1491
+ this.enabledPlugins = enabledPlugins ?? {};
1492
+ }
1493
+ /** Load all discovered and enabled bundle plugins (sync). */
1494
+ loadPluginsSync() {
1495
+ return this.discoverAndLoad();
1496
+ }
1497
+ /** Load all discovered and enabled bundle plugins (async wrapper). */
1498
+ async loadAll() {
1499
+ return this.discoverAndLoad();
1500
+ }
1501
+ /**
1502
+ * Discover and load plugins from the cache directory.
1503
+ *
1504
+ * Directory structure: `<pluginsDir>/cache/<marketplace>/<plugin>/<version>/`
1505
+ * For each marketplace/plugin pair, the latest version (lexicographically last) is loaded.
1506
+ */
1507
+ discoverAndLoad() {
1508
+ const cacheDir = (0, import_node_path5.join)(this.pluginsDir, "cache");
1509
+ if (!(0, import_node_fs4.existsSync)(cacheDir)) {
1510
+ return [];
1511
+ }
1512
+ const results = [];
1513
+ const marketplaces = getSortedSubdirs(cacheDir);
1514
+ for (const marketplace of marketplaces) {
1515
+ const marketplaceDir = (0, import_node_path5.join)(cacheDir, marketplace);
1516
+ const plugins = getSortedSubdirs(marketplaceDir);
1517
+ for (const pluginName of plugins) {
1518
+ const pluginDir = (0, import_node_path5.join)(marketplaceDir, pluginName);
1519
+ const versions = getSortedSubdirs(pluginDir);
1520
+ if (versions.length === 0) continue;
1521
+ const latestVersion = versions[versions.length - 1];
1522
+ const versionDir = (0, import_node_path5.join)(pluginDir, latestVersion);
1523
+ const manifestPath = (0, import_node_path5.join)(versionDir, ".claude-plugin", "plugin.json");
1524
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) continue;
1525
+ const manifest = this.readManifest(manifestPath);
1526
+ if (!manifest) continue;
1527
+ const pluginId = `${manifest.name}@${marketplace}`;
1528
+ if (this.isDisabled(pluginId, manifest.name)) continue;
1529
+ const loaded = this.loadPlugin(versionDir, manifest);
1530
+ results.push(loaded);
1531
+ }
1532
+ }
1533
+ return results;
1534
+ }
1535
+ /** Read and validate a plugin.json manifest. Returns null on failure. */
1536
+ readManifest(path) {
1537
+ try {
1538
+ const raw = (0, import_node_fs4.readFileSync)(path, "utf-8");
1539
+ const data = JSON.parse(raw);
1540
+ return validateManifest(data);
1541
+ } catch {
1542
+ return null;
1543
+ }
1544
+ }
1545
+ /**
1546
+ * Check if a plugin is explicitly disabled.
1547
+ * Checks both `name@marketplace` and `name` keys.
1548
+ * Plugins not listed in enabledPlugins are enabled by default.
1549
+ */
1550
+ isDisabled(pluginId, pluginName) {
1551
+ if (pluginId in this.enabledPlugins) {
1552
+ return this.enabledPlugins[pluginId] === false;
1553
+ }
1554
+ if (pluginName in this.enabledPlugins) {
1555
+ return this.enabledPlugins[pluginName] === false;
1556
+ }
1557
+ return false;
1558
+ }
1559
+ /** Load a single plugin's skills, hooks, agents, and MCP config. */
1560
+ loadPlugin(pluginDir, manifest) {
1561
+ return {
1562
+ manifest,
1563
+ skills: this.loadSkills(pluginDir, manifest.name),
1564
+ commands: this.loadCommands(pluginDir, manifest.name),
1565
+ hooks: this.loadHooks(pluginDir),
1566
+ mcpConfig: this.loadMcpConfig(pluginDir),
1567
+ agents: this.loadAgents(pluginDir),
1568
+ pluginDir
1569
+ };
1570
+ }
1571
+ /** Load skills from the plugin's skills/ directory. */
1572
+ loadSkills(pluginDir, pluginName) {
1573
+ const skillsDir = (0, import_node_path5.join)(pluginDir, "skills");
1574
+ if (!(0, import_node_fs4.existsSync)(skillsDir)) return [];
1575
+ const entries = (0, import_node_fs4.readdirSync)(skillsDir, { withFileTypes: true });
1576
+ const skills = [];
1577
+ for (const entry of entries) {
1578
+ if (!entry.isDirectory()) continue;
1579
+ const skillFile = (0, import_node_path5.join)(skillsDir, entry.name, "SKILL.md");
1580
+ if (!(0, import_node_fs4.existsSync)(skillFile)) continue;
1581
+ const raw = (0, import_node_fs4.readFileSync)(skillFile, "utf-8");
1582
+ const { metadata, content } = parseSkillFrontmatter(raw);
1583
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1584
+ const skill = {
1585
+ name: entry.name,
1586
+ description,
1587
+ skillContent: content,
1588
+ ...metadata
1589
+ };
1590
+ skills.push(skill);
1591
+ }
1592
+ return skills;
1593
+ }
1594
+ /** Load commands from the plugin's commands/ directory (flat .md files). */
1595
+ loadCommands(pluginDir, pluginName) {
1596
+ const commandsDir = (0, import_node_path5.join)(pluginDir, "commands");
1597
+ if (!(0, import_node_fs4.existsSync)(commandsDir)) return [];
1598
+ const entries = (0, import_node_fs4.readdirSync)(commandsDir, { withFileTypes: true });
1599
+ const commands = [];
1600
+ for (const entry of entries) {
1601
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1602
+ const raw = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(commandsDir, entry.name), "utf-8");
1603
+ const { metadata, content } = parseSkillFrontmatter(raw);
1604
+ const name = typeof metadata.name === "string" ? metadata.name : entry.name.replace(/\.md$/, "");
1605
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1606
+ commands.push({
1607
+ ...metadata,
1608
+ name: `${pluginName}:${name}`,
1609
+ description,
1610
+ skillContent: content
1611
+ });
1612
+ }
1613
+ return commands;
1614
+ }
1615
+ /** Load hooks from hooks/hooks.json if present. */
1616
+ loadHooks(pluginDir) {
1617
+ const hooksPath = (0, import_node_path5.join)(pluginDir, "hooks", "hooks.json");
1618
+ if (!(0, import_node_fs4.existsSync)(hooksPath)) return {};
1619
+ try {
1620
+ const raw = (0, import_node_fs4.readFileSync)(hooksPath, "utf-8");
1621
+ const data = JSON.parse(raw);
1622
+ if (typeof data === "object" && data !== null) {
1623
+ return data;
1624
+ }
1625
+ return {};
1626
+ } catch {
1627
+ return {};
1628
+ }
1629
+ }
1630
+ /** Load MCP server configuration if present. Checks `.mcp.json` at plugin root first. */
1631
+ loadMcpConfig(pluginDir) {
1632
+ const primaryPath = (0, import_node_path5.join)(pluginDir, ".mcp.json");
1633
+ const fallbackPath = (0, import_node_path5.join)(pluginDir, ".claude-plugin", "mcp.json");
1634
+ const mcpPath = (0, import_node_fs4.existsSync)(primaryPath) ? primaryPath : fallbackPath;
1635
+ if (!(0, import_node_fs4.existsSync)(mcpPath)) return void 0;
1636
+ try {
1637
+ const raw = (0, import_node_fs4.readFileSync)(mcpPath, "utf-8");
1638
+ return JSON.parse(raw);
1639
+ } catch {
1640
+ return void 0;
1641
+ }
1642
+ }
1643
+ /** Load agent definitions from agents/ directory if present. */
1644
+ loadAgents(pluginDir) {
1645
+ const agentsDir = (0, import_node_path5.join)(pluginDir, "agents");
1646
+ if (!(0, import_node_fs4.existsSync)(agentsDir)) return [];
1647
+ try {
1648
+ const entries = (0, import_node_fs4.readdirSync)(agentsDir, { withFileTypes: true });
1649
+ return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
1650
+ } catch {
1651
+ return [];
1652
+ }
1653
+ }
1654
+ };
1655
+
1656
+ // src/plugins/bundle-plugin-installer.ts
1657
+ var import_node_child_process = require("child_process");
1658
+ var import_node_fs5 = require("fs");
1659
+ var import_node_path6 = require("path");
1660
+ var GIT_CLONE_TIMEOUT_MS = 6e4;
1661
+ var BundlePluginInstaller = class {
1662
+ pluginsDir;
1663
+ cacheDir;
1664
+ registryPath;
1665
+ settingsStore;
1666
+ marketplaceClient;
1667
+ exec;
1668
+ constructor(options) {
1669
+ this.pluginsDir = options.pluginsDir;
1670
+ this.cacheDir = (0, import_node_path6.join)(this.pluginsDir, "cache");
1671
+ this.registryPath = (0, import_node_path6.join)(this.pluginsDir, "installed_plugins.json");
1672
+ this.settingsStore = options.settingsStore;
1673
+ this.marketplaceClient = options.marketplaceClient;
1674
+ this.exec = options.exec ?? this.defaultExec;
1675
+ }
1676
+ /**
1677
+ * Install a plugin from a marketplace.
1678
+ *
1679
+ * 1. Read marketplace manifest to find the plugin entry.
1680
+ * 2. Resolve source (relative path, github, or url).
1681
+ * 3. Copy/clone to `cache/<marketplace>/<plugin>/<version>/`.
1682
+ * 4. Record in `installed_plugins.json`.
1683
+ */
1684
+ async install(pluginName, marketplaceName) {
1685
+ const manifest = this.marketplaceClient.fetchManifest(marketplaceName);
1686
+ const entry = manifest.plugins.find((p) => p.name === pluginName);
1687
+ if (!entry) {
1688
+ throw new Error(`Plugin "${pluginName}" not found in marketplace "${marketplaceName}"`);
1689
+ }
1690
+ const version = this.resolveVersion(entry, marketplaceName);
1691
+ const targetDir = (0, import_node_path6.join)(this.cacheDir, marketplaceName, pluginName, version);
1692
+ if ((0, import_node_fs5.existsSync)(targetDir)) {
1693
+ throw new Error(
1694
+ `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
1695
+ );
1696
+ }
1697
+ this.resolveAndInstall(entry.source, marketplaceName, pluginName, targetDir);
1698
+ const pluginId = `${pluginName}@${marketplaceName}`;
1699
+ const registry = this.readRegistry();
1700
+ registry[pluginId] = {
1701
+ pluginName,
1702
+ marketplace: marketplaceName,
1703
+ version,
1704
+ installPath: targetDir,
1705
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1706
+ };
1707
+ this.writeRegistry(registry);
1708
+ }
1709
+ /**
1710
+ * Uninstall a plugin.
1711
+ * Removes from cache and from installed_plugins.json.
1712
+ */
1713
+ async uninstall(pluginId) {
1714
+ const registry = this.readRegistry();
1715
+ const record = registry[pluginId];
1716
+ if (!record) {
1717
+ throw new Error(`Plugin "${pluginId}" is not installed`);
1718
+ }
1719
+ if ((0, import_node_fs5.existsSync)(record.installPath)) {
1720
+ (0, import_node_fs5.rmSync)(record.installPath, { recursive: true, force: true });
1721
+ }
1722
+ delete registry[pluginId];
1723
+ this.writeRegistry(registry);
1724
+ this.settingsStore.removePluginEntry(pluginId);
1725
+ }
1726
+ /** Enable a plugin by setting its enabledPlugins entry to true. */
1727
+ async enable(pluginId) {
1728
+ this.settingsStore.setPluginEnabled(pluginId, true);
1729
+ }
1730
+ /** Disable a plugin by setting its enabledPlugins entry to false. */
1731
+ async disable(pluginId) {
1732
+ this.settingsStore.setPluginEnabled(pluginId, false);
1733
+ }
1734
+ /** Get all installed plugins. */
1735
+ getInstalledPlugins() {
1736
+ return this.readRegistry();
1737
+ }
1738
+ /** Get plugins installed from a specific marketplace. */
1739
+ getPluginsByMarketplace(marketplaceName) {
1740
+ const registry = this.readRegistry();
1741
+ return Object.values(registry).filter((r) => r.marketplace === marketplaceName);
1742
+ }
1743
+ // --- Private helpers ---
1744
+ /** Resolve the version for a plugin entry. */
1745
+ resolveVersion(entry, marketplaceName) {
1746
+ const entryWithVersion = entry;
1747
+ if (typeof entryWithVersion.version === "string" && entryWithVersion.version) {
1748
+ return entryWithVersion.version;
1749
+ }
1750
+ return this.marketplaceClient.getMarketplaceSha(marketplaceName);
1751
+ }
1752
+ /**
1753
+ * Normalize source object — Claude Code manifests use `source` key instead of `type`.
1754
+ * e.g., { source: "url", url: "..." } → { type: "url", url: "..." }
1755
+ */
1756
+ normalizeSource(source) {
1757
+ if (typeof source === "string") return source;
1758
+ const obj = source;
1759
+ if (!obj.type && typeof obj.source === "string") {
1760
+ return { ...obj, type: obj.source };
1761
+ }
1762
+ return source;
1763
+ }
1764
+ /** Resolve the source and install the plugin. */
1765
+ resolveAndInstall(rawSource, marketplaceName, pluginName, targetDir) {
1766
+ (0, import_node_fs5.mkdirSync)(targetDir, { recursive: true });
1767
+ const source = this.normalizeSource(rawSource);
1768
+ try {
1769
+ if (typeof source === "string") {
1770
+ const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
1771
+ const sourcePath = (0, import_node_path6.join)(marketplaceDir, source);
1772
+ if (!(0, import_node_fs5.existsSync)(sourcePath)) {
1773
+ throw new Error(
1774
+ `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
1775
+ );
1776
+ }
1777
+ (0, import_node_fs5.cpSync)(sourcePath, targetDir, { recursive: true });
1778
+ } else if (source.type === "github") {
1779
+ const repoUrl = `https://github.com/${source.repo}.git`;
1780
+ this.cloneToDir(repoUrl, targetDir, pluginName);
1781
+ } else if (source.type === "url" && typeof source.url === "string" && source.url.endsWith(".git")) {
1782
+ this.cloneToDir(source.url, targetDir, pluginName);
1783
+ } else if (source.type === "url") {
1784
+ throw new Error(`URL source "${source.url}" is not a git repository (must end with .git)`);
1785
+ } else {
1786
+ throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
1787
+ }
1788
+ } catch (err) {
1789
+ if ((0, import_node_fs5.existsSync)(targetDir)) {
1790
+ (0, import_node_fs5.rmSync)(targetDir, { recursive: true, force: true });
1791
+ }
1792
+ throw err;
1793
+ }
1794
+ }
1795
+ /** Clone a git repository to the target directory. */
1796
+ cloneToDir(repoUrl, targetDir, pluginName) {
1797
+ (0, import_node_fs5.rmSync)(targetDir, { recursive: true, force: true });
1798
+ const command = `git clone --depth 1 ${repoUrl} ${targetDir}`;
1799
+ try {
1800
+ this.exec(command, { timeout: GIT_CLONE_TIMEOUT_MS, stdio: "pipe" });
1801
+ } catch (error) {
1802
+ const message = error instanceof Error ? error.message : String(error);
1803
+ throw new Error(`Failed to clone plugin "${pluginName}": ${message}`);
1804
+ }
1805
+ }
1806
+ /** Read the installed_plugins.json registry. */
1807
+ readRegistry() {
1808
+ if (!(0, import_node_fs5.existsSync)(this.registryPath)) {
1809
+ return {};
1810
+ }
1811
+ try {
1812
+ const raw = (0, import_node_fs5.readFileSync)(this.registryPath, "utf-8");
1813
+ const data = JSON.parse(raw);
1814
+ if (typeof data === "object" && data !== null) {
1815
+ return data;
1816
+ }
1817
+ return {};
1818
+ } catch {
1819
+ return {};
1820
+ }
1821
+ }
1822
+ /** Write the installed_plugins.json registry. */
1823
+ writeRegistry(registry) {
1824
+ const dir = (0, import_node_path6.dirname)(this.registryPath);
1825
+ if (!(0, import_node_fs5.existsSync)(dir)) {
1826
+ (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
1827
+ }
1828
+ (0, import_node_fs5.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1829
+ }
1830
+ /** Default exec implementation using child_process. */
1831
+ defaultExec(command, options) {
1832
+ return (0, import_node_child_process.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
1833
+ }
1834
+ };
1835
+
1836
+ // src/plugins/marketplace-client.ts
1837
+ var import_node_child_process2 = require("child_process");
1838
+ var import_node_fs6 = require("fs");
1839
+ var import_node_path7 = require("path");
1840
+ var GIT_TIMEOUT_MS = 6e4;
1841
+ var MarketplaceClient = class {
1842
+ pluginsDir;
1843
+ exec;
1844
+ marketplacesDir;
1845
+ registryPath;
1846
+ constructor(options) {
1847
+ this.pluginsDir = options.pluginsDir;
1848
+ this.exec = options.exec ?? this.defaultExec;
1849
+ this.marketplacesDir = (0, import_node_path7.join)(this.pluginsDir, "marketplaces");
1850
+ this.registryPath = (0, import_node_path7.join)(this.pluginsDir, "known_marketplaces.json");
1851
+ }
1852
+ /**
1853
+ * Add a marketplace by cloning its repository.
1854
+ *
1855
+ * 1. Parse source: `owner/repo` string becomes a GitHub source.
1856
+ * 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
1857
+ * 3. Read `.claude-plugin/marketplace.json` for the `name` field.
1858
+ * 4. Register in `known_marketplaces.json`.
1859
+ *
1860
+ * Returns the registered marketplace name from the manifest.
1861
+ */
1862
+ addMarketplace(source) {
1863
+ const tempName = "temp-" + Date.now().toString(36);
1864
+ const tempDir = (0, import_node_path7.join)(this.marketplacesDir, tempName);
1865
+ (0, import_node_fs6.mkdirSync)(this.marketplacesDir, { recursive: true });
1866
+ if (source.type === "local") {
1867
+ if (!(0, import_node_fs6.existsSync)(source.path)) {
1868
+ throw new Error(`Local marketplace path does not exist: ${source.path}`);
1869
+ }
1870
+ (0, import_node_fs6.cpSync)(source.path, tempDir, { recursive: true });
1871
+ } else {
1872
+ const cloneUrl = this.resolveCloneUrl(source);
1873
+ const command = `git clone --depth 1 ${cloneUrl} ${tempDir}`;
1874
+ try {
1875
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1876
+ } catch (error) {
1877
+ const message = error instanceof Error ? error.message : String(error);
1878
+ throw new Error(`Failed to clone marketplace: ${message}`);
1879
+ }
1880
+ }
1881
+ const manifestPath = (0, import_node_path7.join)(tempDir, ".claude-plugin", "marketplace.json");
1882
+ if (!(0, import_node_fs6.existsSync)(manifestPath)) {
1883
+ (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
1884
+ throw new Error(
1885
+ source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
1886
+ );
1887
+ }
1888
+ const manifest = this.readManifestFromPath(manifestPath);
1889
+ const name = manifest.name;
1890
+ if (!name) {
1891
+ (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
1892
+ throw new Error('Marketplace manifest does not contain a "name" field');
1893
+ }
1894
+ const registry = this.readRegistry();
1895
+ if (registry[name]) {
1896
+ (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
1897
+ throw new Error(`Marketplace "${name}" already exists`);
1898
+ }
1899
+ const finalDir = (0, import_node_path7.join)(this.marketplacesDir, name);
1900
+ (0, import_node_fs6.renameSync)(tempDir, finalDir);
1901
+ registry[name] = {
1902
+ source,
1903
+ installLocation: finalDir,
1904
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1905
+ };
1906
+ this.writeRegistry(registry);
1907
+ return name;
1908
+ }
1909
+ /**
1910
+ * Remove a marketplace.
1911
+ * Uninstalls all plugins from that marketplace, then deletes the clone directory
1912
+ * and removes from the registry.
1913
+ */
1914
+ removeMarketplace(name) {
1915
+ const registry = this.readRegistry();
1916
+ const entry = registry[name];
1917
+ if (!entry) {
1918
+ throw new Error(`Marketplace "${name}" not found`);
1919
+ }
1920
+ this.removeInstalledPluginsForMarketplace(name);
1921
+ if ((0, import_node_fs6.existsSync)(entry.installLocation)) {
1922
+ (0, import_node_fs6.rmSync)(entry.installLocation, { recursive: true, force: true });
1923
+ }
1924
+ delete registry[name];
1925
+ this.writeRegistry(registry);
1926
+ }
1927
+ /**
1928
+ * Update a marketplace by running git pull on its clone.
1929
+ * The manifest is re-read from disk on demand (via fetchManifest), so the
1930
+ * updated manifest is automatically available after pull.
1931
+ *
1932
+ * TODO: After pull, detect version changes in installed plugins and offer
1933
+ * to update them (re-install at new version).
1934
+ */
1935
+ updateMarketplace(name) {
1936
+ const registry = this.readRegistry();
1937
+ const entry = registry[name];
1938
+ if (!entry) {
1939
+ throw new Error(`Marketplace "${name}" not found`);
1940
+ }
1941
+ if (!(0, import_node_fs6.existsSync)(entry.installLocation)) {
1942
+ throw new Error(`Marketplace directory for "${name}" does not exist`);
1943
+ }
1944
+ if (entry.source.type === "local") {
1945
+ const localSource = entry.source;
1946
+ if (!(0, import_node_fs6.existsSync)(localSource.path)) {
1947
+ throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
1948
+ }
1949
+ (0, import_node_fs6.rmSync)(entry.installLocation, { recursive: true, force: true });
1950
+ (0, import_node_fs6.cpSync)(localSource.path, entry.installLocation, { recursive: true });
1951
+ } else {
1952
+ const command = `git -C ${entry.installLocation} pull`;
1953
+ try {
1954
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1955
+ } catch (error) {
1956
+ const message = error instanceof Error ? error.message : String(error);
1957
+ throw new Error(`Failed to update marketplace "${name}": ${message}`);
1958
+ }
1959
+ }
1960
+ entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1961
+ this.writeRegistry(registry);
1962
+ }
1963
+ /** List all registered marketplaces. */
1964
+ listMarketplaces() {
1965
+ const registry = this.readRegistry();
1966
+ return Object.entries(registry).map(([name, entry]) => ({
1967
+ name,
1968
+ source: entry.source,
1969
+ lastUpdated: entry.lastUpdated
1970
+ }));
1971
+ }
1972
+ /**
1973
+ * Read the marketplace manifest from a registered marketplace's clone.
1974
+ */
1975
+ fetchManifest(marketplaceName) {
1976
+ const registry = this.readRegistry();
1977
+ const entry = registry[marketplaceName];
1978
+ if (!entry) {
1979
+ throw new Error(`Marketplace "${marketplaceName}" not found`);
1980
+ }
1981
+ const manifestPath = (0, import_node_path7.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
1982
+ if (!(0, import_node_fs6.existsSync)(manifestPath)) {
1983
+ throw new Error(
1984
+ `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
1985
+ );
1986
+ }
1987
+ return this.readManifestFromPath(manifestPath);
1988
+ }
1989
+ /** Get the clone directory path for a registered marketplace. */
1990
+ getMarketplaceDir(name) {
1991
+ const registry = this.readRegistry();
1992
+ const entry = registry[name];
1993
+ if (!entry) {
1994
+ throw new Error(`Marketplace "${name}" not found`);
1995
+ }
1996
+ return entry.installLocation;
1997
+ }
1998
+ /**
1999
+ * Get the current git SHA (first 12 chars) for a marketplace clone.
2000
+ * Used as a version identifier when plugins lack explicit versions.
2001
+ */
2002
+ getMarketplaceSha(name) {
2003
+ const dir = this.getMarketplaceDir(name);
2004
+ try {
2005
+ const result = this.exec(`git -C ${dir} rev-parse HEAD`, {
2006
+ timeout: GIT_TIMEOUT_MS,
2007
+ stdio: "pipe"
2008
+ });
2009
+ return result.toString().trim().slice(0, 12);
2010
+ } catch {
2011
+ return "unknown";
2012
+ }
2013
+ }
2014
+ /** List all available plugins across all marketplaces. */
2015
+ listAvailablePlugins() {
2016
+ const results = [];
2017
+ const marketplaces = this.listMarketplaces();
2018
+ for (const { name } of marketplaces) {
2019
+ try {
2020
+ const manifest = this.fetchManifest(name);
2021
+ for (const plugin of manifest.plugins) {
2022
+ results.push({ ...plugin, marketplace: name });
2023
+ }
2024
+ } catch {
2025
+ }
2026
+ }
2027
+ return results;
2028
+ }
2029
+ // --- Private helpers ---
2030
+ /** Resolve a marketplace source to a git clone URL. */
2031
+ resolveCloneUrl(source) {
2032
+ switch (source.type) {
2033
+ case "github":
2034
+ return `https://github.com/${source.repo}.git`;
2035
+ case "git":
2036
+ return source.url;
2037
+ case "local":
2038
+ throw new Error("Local source type does not use git cloning");
2039
+ case "url":
2040
+ throw new Error("URL marketplace source is not yet supported");
2041
+ }
2042
+ }
2043
+ /**
2044
+ * Remove all installed plugins that belong to a given marketplace.
2045
+ * Reads installed_plugins.json, deletes cache directories for matching plugins,
2046
+ * and updates the registry.
2047
+ */
2048
+ removeInstalledPluginsForMarketplace(marketplaceName) {
2049
+ const installedPath = (0, import_node_path7.join)(this.pluginsDir, "installed_plugins.json");
2050
+ if (!(0, import_node_fs6.existsSync)(installedPath)) return;
2051
+ let registry;
2052
+ try {
2053
+ const raw = (0, import_node_fs6.readFileSync)(installedPath, "utf-8");
2054
+ const data = JSON.parse(raw);
2055
+ if (typeof data !== "object" || data === null) return;
2056
+ registry = data;
2057
+ } catch {
2058
+ return;
2059
+ }
2060
+ let changed = false;
2061
+ for (const [pluginId, record] of Object.entries(registry)) {
2062
+ if (record.marketplace === marketplaceName) {
2063
+ if (record.installPath && (0, import_node_fs6.existsSync)(record.installPath)) {
2064
+ (0, import_node_fs6.rmSync)(record.installPath, { recursive: true, force: true });
2065
+ }
2066
+ delete registry[pluginId];
2067
+ changed = true;
2068
+ }
2069
+ }
2070
+ if (changed) {
2071
+ const dir = (0, import_node_path7.dirname)(installedPath);
2072
+ if (!(0, import_node_fs6.existsSync)(dir)) {
2073
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
2074
+ }
2075
+ (0, import_node_fs6.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
2076
+ }
2077
+ }
2078
+ /** Read and parse a marketplace.json from a file path. */
2079
+ readManifestFromPath(path) {
2080
+ const raw = (0, import_node_fs6.readFileSync)(path, "utf-8");
2081
+ const data = JSON.parse(raw);
2082
+ if (typeof data !== "object" || data === null) {
2083
+ throw new Error("Invalid marketplace manifest: not an object");
2084
+ }
2085
+ const obj = data;
2086
+ if (typeof obj.name !== "string") {
2087
+ throw new Error('Invalid marketplace manifest: missing "name" field');
2088
+ }
2089
+ return data;
2090
+ }
2091
+ /** Read the known_marketplaces.json registry. */
2092
+ readRegistry() {
2093
+ if (!(0, import_node_fs6.existsSync)(this.registryPath)) {
2094
+ return {};
2095
+ }
2096
+ try {
2097
+ const raw = (0, import_node_fs6.readFileSync)(this.registryPath, "utf-8");
2098
+ const data = JSON.parse(raw);
2099
+ if (typeof data === "object" && data !== null) {
2100
+ return data;
2101
+ }
2102
+ return {};
2103
+ } catch {
2104
+ return {};
2105
+ }
2106
+ }
2107
+ /** Write the known_marketplaces.json registry. */
2108
+ writeRegistry(registry) {
2109
+ const dir = (0, import_node_path7.dirname)(this.registryPath);
2110
+ if (!(0, import_node_fs6.existsSync)(dir)) {
2111
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
2112
+ }
2113
+ (0, import_node_fs6.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
2114
+ }
2115
+ /** Default exec implementation using child_process. */
2116
+ defaultExec(command, options) {
2117
+ return (0, import_node_child_process2.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
2118
+ }
2119
+ };
2120
+
2121
+ // src/plugins/plugin-hooks-merger.ts
2122
+ var import_node_path8 = require("path");
2123
+ function buildPluginEnv(plugin) {
2124
+ const dataDir = (0, import_node_path8.join)((0, import_node_path8.dirname)((0, import_node_path8.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
2125
+ return {
2126
+ CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
2127
+ CLAUDE_PLUGIN_PATH: plugin.pluginDir,
2128
+ CLAUDE_PLUGIN_DATA: dataDir
2129
+ };
2130
+ }
2131
+ function resolvePluginRoot(group, pluginDir) {
2132
+ if (Array.isArray(group.hooks)) {
2133
+ return {
2134
+ ...group,
2135
+ hooks: group.hooks.map((h) => {
2136
+ if (typeof h.command === "string") {
2137
+ return {
2138
+ ...h,
2139
+ command: h.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
2140
+ };
2141
+ }
2142
+ return h;
2143
+ })
2144
+ };
2145
+ }
2146
+ return group;
2147
+ }
2148
+ function mergePluginHooks(plugins) {
2149
+ const merged = {};
2150
+ for (const plugin of plugins) {
2151
+ const hooksObj = plugin.hooks;
2152
+ if (!hooksObj) continue;
2153
+ const pluginEnv = buildPluginEnv(plugin);
2154
+ const innerHooks = hooksObj.hooks ?? hooksObj;
2155
+ for (const [event, groups] of Object.entries(innerHooks)) {
2156
+ if (!Array.isArray(groups)) continue;
2157
+ if (!merged[event]) merged[event] = [];
2158
+ const resolved = groups.map((group) => {
2159
+ const r = resolvePluginRoot(group, plugin.pluginDir);
2160
+ r.env = pluginEnv;
2161
+ return r;
2162
+ });
2163
+ merged[event].push(...resolved);
2164
+ }
2165
+ }
2166
+ return merged;
2167
+ }
2168
+ function mergeHooksIntoConfig(configHooks, pluginHooks) {
2169
+ const pluginKeys = Object.keys(pluginHooks);
2170
+ if (pluginKeys.length === 0) return configHooks;
2171
+ const merged = {};
2172
+ for (const [event, groups] of Object.entries(pluginHooks)) {
2173
+ merged[event] = [...groups];
2174
+ }
2175
+ if (configHooks) {
2176
+ for (const [event, groups] of Object.entries(configHooks)) {
2177
+ if (!Array.isArray(groups)) continue;
2178
+ if (!merged[event]) merged[event] = [];
2179
+ merged[event].push(...groups);
2180
+ }
2181
+ }
2182
+ return merged;
2183
+ }
2184
+
2185
+ // src/interactive/interactive-session.ts
2186
+ var import_node_os3 = require("os");
2187
+ var import_node_path9 = require("path");
2188
+ var TOOL_ARG_DISPLAY_MAX = 80;
2189
+ var TAIL_KEEP = 30;
2190
+ var MAX_COMPLETED_TOOLS = 50;
2191
+ var STREAMING_FLUSH_INTERVAL_MS = 16;
2192
+ var InteractiveSession = class {
2193
+ session = null;
2194
+ commandExecutor;
2195
+ listeners = /* @__PURE__ */ new Map();
2196
+ initialized = false;
2197
+ initPromise = null;
2198
+ // Streaming state
2199
+ streamingText = "";
2200
+ flushTimer = null;
2201
+ // Tool state
2202
+ activeTools = [];
2203
+ // Execution state
2204
+ executing = false;
2205
+ pendingPrompt = null;
2206
+ pendingDisplayInput;
2207
+ pendingRawInput;
2208
+ // Full history timeline (chat messages + events)
2209
+ history = [];
2210
+ // Session persistence
2211
+ sessionStore;
2212
+ sessionName;
2213
+ cwd;
2214
+ // Session restore state
2215
+ pendingRestoreMessages = null;
2216
+ resumeSessionId;
2217
+ forkSession;
2218
+ constructor(options) {
2219
+ this.commandExecutor = new SystemCommandExecutor(createSystemCommands());
2220
+ if ("session" in options && options.session) {
2221
+ this.session = options.session;
2222
+ this.initialized = true;
2223
+ } else {
2224
+ const stdOpts = options;
2225
+ this.initPromise = this.initializeAsync(stdOpts);
2226
+ }
2227
+ this.sessionStore = options.sessionStore;
2228
+ this.sessionName = options.sessionName;
2229
+ this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
2230
+ this.resumeSessionId = options.resumeSessionId;
2231
+ this.forkSession = options.forkSession ?? false;
2232
+ if (options.resumeSessionId && this.sessionStore) {
2233
+ const record = this.sessionStore.load(options.resumeSessionId);
2234
+ if (record) {
2235
+ this.history = record.history ?? [];
2236
+ this.sessionName = record.name;
2237
+ if (record.messages) {
2238
+ if (this.session) {
2239
+ for (const msg of record.messages) {
2240
+ const m = msg;
2241
+ if (m.role && m.content) {
2242
+ this.session.injectMessage(m.role, m.content);
2243
+ }
2244
+ }
2245
+ } else {
2246
+ this.pendingRestoreMessages = record.messages;
2247
+ }
2248
+ }
2249
+ }
2250
+ }
2251
+ }
2252
+ async initializeAsync(options) {
2253
+ const cwd = options.cwd;
2254
+ const [config, context, projectInfo] = await Promise.all([
2255
+ loadConfig(cwd),
2256
+ loadContext(cwd),
2257
+ detectProject(cwd)
2258
+ ]);
2259
+ const pluginsDir = (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".robota", "plugins");
2260
+ const pluginLoader = new BundlePluginLoader(pluginsDir);
2261
+ let mergedConfig = config;
2262
+ try {
2263
+ const plugins = pluginLoader.loadPluginsSync();
2264
+ if (plugins.length > 0) {
2265
+ const pluginHooks = mergePluginHooks(plugins);
2266
+ mergedConfig = {
2267
+ ...config,
2268
+ hooks: mergeHooksIntoConfig(
2269
+ config.hooks,
2270
+ pluginHooks
2271
+ )
2272
+ };
2273
+ }
2274
+ } catch {
2275
+ }
2276
+ const paths = projectPaths(cwd);
2277
+ const sessionId = this.resumeSessionId && !this.forkSession ? this.resumeSessionId : void 0;
2278
+ this.session = createSession({
2279
+ config: mergedConfig,
2280
+ context,
2281
+ projectInfo,
2282
+ permissionMode: options.permissionMode,
2283
+ maxTurns: options.maxTurns,
2284
+ terminal: NOOP_TERMINAL,
2285
+ sessionLogger: new import_agent_sessions4.FileSessionLogger(paths.logs),
2286
+ permissionHandler: options.permissionHandler,
2287
+ provider: options.provider,
2288
+ onTextDelta: (delta) => this.handleTextDelta(delta),
2289
+ onToolExecution: (event) => this.handleToolExecution(event),
2290
+ sessionId
2291
+ });
2292
+ if (this.pendingRestoreMessages) {
2293
+ for (const msg of this.pendingRestoreMessages) {
2294
+ if (msg && typeof msg === "object" && "role" in msg && "content" in msg) {
2295
+ this.session.injectMessage(
2296
+ msg.role,
2297
+ msg.content
2298
+ );
2299
+ }
2300
+ }
2301
+ this.pendingRestoreMessages = null;
2302
+ }
2303
+ this.initialized = true;
2304
+ }
2305
+ async ensureInitialized() {
2306
+ if (this.initialized) return;
2307
+ if (this.initPromise) await this.initPromise;
2308
+ }
2309
+ getSessionOrThrow() {
2310
+ if (!this.session)
2311
+ throw new Error("InteractiveSession not initialized. Call submit() or await initialization.");
2312
+ return this.session;
2313
+ }
2314
+ // ── Event system ──────────────────────────────────────────────
2315
+ on(event, handler) {
2316
+ if (!this.listeners.has(event)) {
2317
+ this.listeners.set(event, /* @__PURE__ */ new Set());
2318
+ }
2319
+ this.listeners.get(event).add(handler);
2320
+ }
2321
+ off(event, handler) {
2322
+ this.listeners.get(event)?.delete(handler);
2323
+ }
2324
+ emit(event, ...args) {
2325
+ const handlers = this.listeners.get(event);
2326
+ if (handlers) {
2327
+ for (const handler of handlers) {
2328
+ handler(...args);
2329
+ }
2330
+ }
2331
+ }
2332
+ // ── Public API ────────────────────────────────────────────────
2333
+ /** Submit a prompt. Queues if already executing (max 1 queued). */
2334
+ async submit(input, displayInput, rawInput) {
2335
+ await this.ensureInitialized();
2336
+ if (this.executing) {
2337
+ this.pendingPrompt = input;
2338
+ this.pendingDisplayInput = displayInput;
2339
+ this.pendingRawInput = rawInput;
2340
+ return;
2341
+ }
2342
+ await this.executePrompt(input, displayInput, rawInput);
2343
+ }
2344
+ /** Execute a system command by name. Returns null if not found. */
2345
+ async executeCommand(name, args) {
2346
+ await this.ensureInitialized();
2347
+ return this.commandExecutor.execute(name, this, args);
2348
+ }
2349
+ /** List all registered system commands. */
2350
+ listCommands() {
2351
+ return this.commandExecutor.listCommands().map((cmd) => ({
2352
+ name: cmd.name,
2353
+ description: cmd.description
2354
+ }));
2355
+ }
2356
+ /** Abort current execution and clear queue. */
2357
+ abort() {
2358
+ this.pendingPrompt = null;
2359
+ this.pendingDisplayInput = void 0;
2360
+ this.pendingRawInput = void 0;
2361
+ this.session?.abort();
2362
+ }
2363
+ /** Cancel queued prompt without aborting current execution. */
2364
+ cancelQueue() {
2365
+ this.pendingPrompt = null;
2366
+ this.pendingDisplayInput = void 0;
2367
+ this.pendingRawInput = void 0;
2368
+ }
2369
+ isExecuting() {
2370
+ return this.executing;
2371
+ }
2372
+ getPendingPrompt() {
2373
+ return this.pendingPrompt;
2374
+ }
2375
+ /** Get full history timeline (chat + events) for TUI rendering */
2376
+ getFullHistory() {
2377
+ return this.history;
2378
+ }
2379
+ /** Get chat messages only (backward compatible) */
2380
+ getMessages() {
2381
+ return this.history.filter((e) => e.category === "chat").map((e) => e.data);
2382
+ }
2383
+ getStreamingText() {
2384
+ return this.streamingText;
2385
+ }
2386
+ getActiveTools() {
2387
+ return this.activeTools;
2388
+ }
2389
+ getContextState() {
2390
+ return this.getSessionOrThrow().getContextState();
2391
+ }
2392
+ /** Get session name. */
2393
+ getName() {
2394
+ return this.sessionName;
2395
+ }
2396
+ /** Set session name and persist if store is available. */
2397
+ setName(name) {
2398
+ this.sessionName = name;
2399
+ if (this.sessionStore && this.session) {
2400
+ try {
2401
+ const id = this.getSessionOrThrow().getSessionId();
2402
+ const existing = this.sessionStore.load(id);
2403
+ if (existing) {
2404
+ existing.name = name;
2405
+ existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2406
+ this.sessionStore.save(existing);
2407
+ }
2408
+ } catch {
2409
+ }
2410
+ }
2411
+ }
2412
+ /** Attach a transport adapter to this session. Calls transport.attach(this). */
2413
+ attachTransport(transport) {
2414
+ transport.attach(this);
2415
+ }
2416
+ /** Access underlying Session. For advanced use / testing only. */
2417
+ getSession() {
2418
+ return this.getSessionOrThrow();
2419
+ }
2420
+ // ── Execution ─────────────────────────────────────────────────
2421
+ async executePrompt(input, displayInput, rawInput) {
2422
+ this.executing = true;
2423
+ this.clearStreaming();
2424
+ this.emit("thinking", true);
2425
+ this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createUserMessage)(displayInput ?? input)));
2426
+ const historyBefore = this.getSessionOrThrow().getHistory().length;
2427
+ try {
2428
+ const response = await this.getSessionOrThrow().run(input, rawInput);
2429
+ this.flushStreaming();
2430
+ this.pushToolSummaryMessage();
2431
+ this.clearStreaming();
2432
+ const result = this.buildResult(response || "(empty response)", historyBefore);
2433
+ this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createAssistantMessage)(result.response)));
2434
+ this.emit("complete", result);
2435
+ this.emit("context_update", this.getContextState());
2436
+ } catch (err) {
2437
+ this.flushStreaming();
2438
+ if (isAbortError(err)) {
2439
+ const result = this.buildInterruptedResult(historyBefore);
2440
+ this.pushToolSummaryMessage();
2441
+ this.clearStreaming();
2442
+ if (result.response) {
2443
+ this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createAssistantMessage)(result.response)));
2444
+ }
2445
+ this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)("Interrupted by user.")));
2446
+ this.emit("interrupted", result);
2447
+ } else {
2448
+ this.pushToolSummaryMessage();
2449
+ this.clearStreaming();
2450
+ const errMsg = err instanceof Error ? err.message : String(err);
2451
+ this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)(`Error: ${errMsg}`)));
2452
+ this.emit("error", err instanceof Error ? err : new Error(errMsg));
2453
+ }
2454
+ } finally {
2455
+ this.executing = false;
2456
+ this.emit("thinking", false);
2457
+ if (this.sessionStore && this.session) {
2458
+ try {
2459
+ const sessionId = this.getSessionOrThrow().getSessionId();
2460
+ const existing = this.sessionStore.load(sessionId);
2461
+ this.sessionStore.save({
2462
+ id: sessionId,
2463
+ name: this.sessionName ?? existing?.name,
2464
+ cwd: this.cwd ?? "",
2465
+ createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2466
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2467
+ messages: this.getSessionOrThrow().getHistory(),
2468
+ history: this.history
2469
+ });
2470
+ } catch {
2471
+ }
2472
+ }
2473
+ if (this.pendingPrompt) {
2474
+ const queued = this.pendingPrompt;
2475
+ const queuedDisplay = this.pendingDisplayInput;
2476
+ const queuedRaw = this.pendingRawInput;
2477
+ this.pendingPrompt = null;
2478
+ this.pendingDisplayInput = void 0;
2479
+ this.pendingRawInput = void 0;
2480
+ setTimeout(() => this.executePrompt(queued, queuedDisplay, queuedRaw), 0);
2481
+ }
2482
+ }
2483
+ }
2484
+ // ── Streaming callbacks ───────────────────────────────────────
2485
+ handleTextDelta(delta) {
2486
+ this.streamingText += delta;
2487
+ this.emit("text_delta", delta);
2488
+ if (!this.flushTimer) {
2489
+ this.flushTimer = setTimeout(() => {
2490
+ this.flushTimer = null;
2491
+ }, STREAMING_FLUSH_INTERVAL_MS);
2492
+ }
2493
+ }
2494
+ handleToolExecution(event) {
2495
+ if (event.type === "start") {
2496
+ const firstArg = extractFirstArg(event.toolArgs);
2497
+ const state = { toolName: event.toolName, firstArg, isRunning: true };
2498
+ this.activeTools.push(state);
2499
+ this.emit("tool_start", state);
2500
+ this.history.push({
2501
+ id: (0, import_node_crypto.randomUUID)(),
2502
+ timestamp: /* @__PURE__ */ new Date(),
2503
+ category: "event",
2504
+ type: "tool-start",
2505
+ data: { toolName: event.toolName, firstArg, isRunning: true }
2506
+ });
2507
+ } else {
2508
+ const result = event.denied ? "denied" : event.success === false ? "error" : "success";
2509
+ const idx = this.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
2510
+ if (idx !== -1) {
2511
+ const finished = { ...this.activeTools[idx], isRunning: false, result };
2512
+ this.activeTools[idx] = finished;
2513
+ this.trimCompletedTools();
2514
+ this.emit("tool_end", finished);
2515
+ this.history.push({
2516
+ id: (0, import_node_crypto.randomUUID)(),
2517
+ timestamp: /* @__PURE__ */ new Date(),
2518
+ category: "event",
2519
+ type: "tool-end",
2520
+ data: {
2521
+ toolName: finished.toolName,
2522
+ firstArg: finished.firstArg,
2523
+ isRunning: false,
2524
+ result
2525
+ }
2526
+ });
2527
+ }
2528
+ }
2529
+ }
2530
+ // ── Helpers ───────────────────────────────────────────────────
2531
+ /** Push tool execution summary into messages (before Robota response).
2532
+ * Moves tool info from activeTools (real-time display) to messages (permanent display).
2533
+ * After this, activeTools will be cleared by clearStreaming(). */
2534
+ pushToolSummaryMessage() {
2535
+ if (this.activeTools.length === 0) return;
2536
+ const summary = this.activeTools.map((t) => {
2537
+ const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
2538
+ return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
2539
+ }).join("\n");
2540
+ this.history.push({
2541
+ id: (0, import_node_crypto.randomUUID)(),
2542
+ timestamp: /* @__PURE__ */ new Date(),
2543
+ category: "event",
2544
+ type: "tool-summary",
2545
+ data: {
2546
+ tools: this.activeTools.map((t) => ({
2547
+ toolName: t.toolName,
2548
+ firstArg: t.firstArg,
2549
+ isRunning: t.isRunning,
2550
+ result: t.result
2551
+ })),
2552
+ summary
2553
+ }
2554
+ });
2555
+ }
2556
+ clearStreaming() {
2557
+ this.streamingText = "";
2558
+ this.activeTools = [];
2559
+ if (this.flushTimer) {
2560
+ clearTimeout(this.flushTimer);
2561
+ this.flushTimer = null;
2562
+ }
2563
+ }
2564
+ flushStreaming() {
2565
+ if (this.flushTimer) {
2566
+ clearTimeout(this.flushTimer);
2567
+ this.flushTimer = null;
2568
+ }
2569
+ }
2570
+ buildResult(response, historyBefore) {
2571
+ const toolSummaries = this.extractToolSummaries(historyBefore);
2572
+ return {
2573
+ response,
2574
+ history: this.history,
2575
+ toolSummaries,
2576
+ contextState: this.getContextState()
2577
+ };
2578
+ }
2579
+ buildInterruptedResult(historyBefore) {
2580
+ const history = this.getSessionOrThrow().getHistory();
2581
+ const toolSummaries = this.extractToolSummaries(historyBefore);
2582
+ const parts = [];
2583
+ for (let i = historyBefore; i < history.length; i++) {
2584
+ const msg = history[i];
2585
+ if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
2586
+ }
2587
+ return {
2588
+ response: parts.join("\n\n"),
2589
+ history: this.history,
2590
+ toolSummaries,
2591
+ contextState: this.getContextState()
2592
+ };
2593
+ }
2594
+ extractToolSummaries(historyBefore) {
2595
+ const history = this.getSessionOrThrow().getHistory();
2596
+ const summaries = [];
2597
+ for (let i = historyBefore; i < history.length; i++) {
2598
+ const msg = history[i];
2599
+ if (msg?.role === "assistant" && msg.toolCalls) {
2600
+ for (const tc of msg.toolCalls) {
2601
+ summaries.push({ name: tc.function.name, args: tc.function.arguments });
2602
+ }
2603
+ }
2604
+ }
2605
+ return summaries;
2606
+ }
2607
+ trimCompletedTools() {
2608
+ const completed = this.activeTools.filter((t) => !t.isRunning);
2609
+ if (completed.length > MAX_COMPLETED_TOOLS) {
2610
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
2611
+ let removed = 0;
2612
+ this.activeTools = this.activeTools.filter((t) => {
2613
+ if (!t.isRunning && removed < excess) {
2614
+ removed++;
2615
+ return false;
2616
+ }
2617
+ return true;
2618
+ });
2619
+ }
2620
+ }
2621
+ };
2622
+ function isAbortError(err) {
2623
+ return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
2624
+ }
2625
+ function extractFirstArg(toolArgs) {
2626
+ if (!toolArgs) return "";
2627
+ const firstVal = Object.values(toolArgs)[0];
2628
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
2629
+ return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
2630
+ }
2631
+ var NOOP_TERMINAL = {
2632
+ write: () => {
2633
+ },
2634
+ writeLine: () => {
2635
+ },
2636
+ writeMarkdown: () => {
2637
+ },
2638
+ writeError: () => {
2639
+ },
2640
+ prompt: () => Promise.resolve(""),
2641
+ select: () => Promise.resolve(0),
2642
+ spinner: () => ({ stop: () => {
2643
+ }, update: () => {
2644
+ } })
2645
+ };
2646
+
2647
+ // src/query.ts
2648
+ function createQuery(options) {
2649
+ const session = new InteractiveSession({
2650
+ cwd: options.cwd ?? process.cwd(),
2651
+ provider: options.provider,
2652
+ permissionMode: options.permissionMode ?? "bypassPermissions",
2653
+ maxTurns: options.maxTurns,
2654
+ permissionHandler: options.permissionHandler
2655
+ });
2656
+ if (options.onTextDelta) {
2657
+ session.on("text_delta", options.onTextDelta);
2658
+ }
2659
+ return async (prompt) => {
2660
+ return new Promise((resolve2, reject) => {
2661
+ const onComplete = (result) => {
2662
+ cleanup();
2663
+ resolve2(result.response);
2664
+ };
2665
+ const onInterrupted = (result) => {
2666
+ cleanup();
2667
+ resolve2(result.response);
2668
+ };
2669
+ const onError = (error) => {
2670
+ cleanup();
2671
+ reject(error);
2672
+ };
2673
+ const cleanup = () => {
2674
+ session.off("complete", onComplete);
2675
+ session.off("interrupted", onInterrupted);
2676
+ session.off("error", onError);
2677
+ };
2678
+ session.on("complete", onComplete);
2679
+ session.on("interrupted", onInterrupted);
2680
+ session.on("error", onError);
2681
+ session.submit(prompt).catch((err) => {
2682
+ cleanup();
2683
+ reject(err instanceof Error ? err : new Error(String(err)));
2684
+ });
2685
+ });
2686
+ };
2687
+ }
2688
+
2689
+ // src/commands/command-registry.ts
2690
+ var CommandRegistry = class {
2691
+ sources = [];
2692
+ addSource(source) {
2693
+ this.sources.push(source);
2694
+ }
2695
+ /** Get all commands, optionally filtered by prefix */
2696
+ getCommands(filter) {
2697
+ const all = [];
2698
+ for (const source of this.sources) {
2699
+ all.push(...source.getCommands());
2700
+ }
2701
+ if (!filter) return all;
2702
+ const lower = filter.toLowerCase();
2703
+ return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
2704
+ }
2705
+ /** Resolve a short name to its fully qualified plugin:name form */
2706
+ resolveQualifiedName(shortName) {
2707
+ const matches = this.getCommands().filter(
2708
+ (c) => c.source === "plugin" && c.name.includes(":") && c.name.endsWith(`:${shortName}`)
2709
+ );
2710
+ if (matches.length !== 1) return null;
2711
+ return matches[0].name;
2712
+ }
2713
+ /** Get subcommands for a specific command */
2714
+ getSubcommands(commandName) {
2715
+ const lower = commandName.toLowerCase();
2716
+ for (const source of this.sources) {
2717
+ for (const cmd of source.getCommands()) {
2718
+ if (cmd.name.toLowerCase() === lower && cmd.subcommands) {
2719
+ return cmd.subcommands;
2720
+ }
2721
+ }
2722
+ }
2723
+ return [];
2724
+ }
2725
+ };
2726
+
2727
+ // src/commands/builtin-source.ts
2728
+ var import_agent_core2 = require("@robota-sdk/agent-core");
2729
+ function buildModelSubcommands() {
2730
+ const seen = /* @__PURE__ */ new Set();
2731
+ const commands = [];
2732
+ for (const model of Object.values(import_agent_core2.CLAUDE_MODELS)) {
2733
+ if (seen.has(model.name)) continue;
2734
+ seen.add(model.name);
2735
+ commands.push({
2736
+ name: model.id,
2737
+ description: `${model.name} (${(0, import_agent_core2.formatTokenCount)(model.contextWindow).toUpperCase()})`,
2738
+ source: "builtin"
2739
+ });
2740
+ }
2741
+ return commands;
2742
+ }
2743
+ function createBuiltinCommands() {
2744
+ return [
2745
+ { name: "help", description: "Show available commands", source: "builtin" },
2746
+ { name: "clear", description: "Clear conversation history", source: "builtin" },
2747
+ {
2748
+ name: "mode",
2749
+ description: "Permission mode",
2750
+ source: "builtin",
2751
+ subcommands: [
2752
+ { name: "plan", description: "Plan only, no execution", source: "builtin" },
2753
+ { name: "default", description: "Ask before risky actions", source: "builtin" },
2754
+ { name: "acceptEdits", description: "Auto-approve file edits", source: "builtin" },
2755
+ { name: "bypassPermissions", description: "Skip all permission checks", source: "builtin" }
2756
+ ]
2757
+ },
2758
+ {
2759
+ name: "model",
2760
+ description: "Select AI model",
2761
+ source: "builtin",
2762
+ subcommands: buildModelSubcommands()
2763
+ },
2764
+ {
2765
+ name: "language",
2766
+ description: "Set response language",
2767
+ source: "builtin",
2768
+ subcommands: [
2769
+ { name: "ko", description: "Korean", source: "builtin" },
2770
+ { name: "en", description: "English", source: "builtin" },
2771
+ { name: "ja", description: "Japanese", source: "builtin" },
2772
+ { name: "zh", description: "Chinese", source: "builtin" }
2773
+ ]
2774
+ },
2775
+ { name: "compact", description: "Compress context window", source: "builtin" },
2776
+ { name: "cost", description: "Show session info", source: "builtin" },
2777
+ { name: "context", description: "Context window info", source: "builtin" },
2778
+ { name: "permissions", description: "Permission rules", source: "builtin" },
2779
+ { name: "resume", description: "Resume a previous session", source: "builtin" },
2780
+ { name: "rename", description: "Rename the current session", source: "builtin" },
2781
+ { name: "plugin", description: "Manage plugins", source: "builtin" },
2782
+ { name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
2783
+ { name: "reset", description: "Delete settings and exit", source: "builtin" },
2784
+ { name: "exit", description: "Exit CLI", source: "builtin" }
2785
+ ];
2786
+ }
2787
+ var BuiltinCommandSource = class {
2788
+ name = "builtin";
2789
+ commands;
2790
+ constructor() {
2791
+ this.commands = createBuiltinCommands();
2792
+ }
2793
+ getCommands() {
2794
+ return this.commands;
2795
+ }
2796
+ };
2797
+
2798
+ // src/commands/skill-source.ts
2799
+ var import_node_fs7 = require("fs");
2800
+ var import_node_path10 = require("path");
2801
+ var import_node_os4 = require("os");
2802
+ var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
2803
+ var LIST_KEYS2 = /* @__PURE__ */ new Set(["allowed-tools"]);
2804
+ function kebabToCamel(key) {
2805
+ return key.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
2806
+ }
2807
+ function parseFrontmatter2(content) {
2808
+ const lines = content.split("\n");
2809
+ if (lines[0]?.trim() !== "---") return null;
2810
+ const result = {};
2811
+ for (let i = 1; i < lines.length; i++) {
2812
+ const line = lines[i];
2813
+ if (line.trim() === "---") break;
2814
+ const match = line.match(/^([a-z][a-z0-9-]*):\s*(.+)/);
2815
+ if (!match) continue;
2816
+ const key = match[1];
2817
+ const rawValue = match[2].trim();
2818
+ const camelKey = kebabToCamel(key);
2819
+ if (BOOLEAN_KEYS.has(key)) {
2820
+ result[camelKey] = rawValue === "true";
2821
+ } else if (LIST_KEYS2.has(key)) {
2822
+ result[camelKey] = rawValue.split(",").map((s) => s.trim());
2823
+ } else {
2824
+ result[camelKey] = rawValue;
2825
+ }
2826
+ }
2827
+ return Object.keys(result).length > 0 ? result : null;
2828
+ }
2829
+ function buildCommand(frontmatter, content, fallbackName) {
2830
+ const cmd = {
2831
+ name: frontmatter?.name ?? fallbackName,
2832
+ description: frontmatter?.description ?? `Skill: ${fallbackName}`,
2833
+ source: "skill",
2834
+ skillContent: content
2835
+ };
2836
+ if (frontmatter?.argumentHint !== void 0) cmd.argumentHint = frontmatter.argumentHint;
2837
+ if (frontmatter?.disableModelInvocation !== void 0)
2838
+ cmd.disableModelInvocation = frontmatter.disableModelInvocation;
2839
+ if (frontmatter?.userInvocable !== void 0) cmd.userInvocable = frontmatter.userInvocable;
2840
+ if (frontmatter?.allowedTools !== void 0) cmd.allowedTools = frontmatter.allowedTools;
2841
+ if (frontmatter?.model !== void 0) cmd.model = frontmatter.model;
2842
+ if (frontmatter?.effort !== void 0) cmd.effort = frontmatter.effort;
2843
+ if (frontmatter?.context !== void 0) cmd.context = frontmatter.context;
2844
+ if (frontmatter?.agent !== void 0) cmd.agent = frontmatter.agent;
2845
+ return cmd;
2846
+ }
2847
+ function scanSkillsDir(skillsDir) {
2848
+ if (!(0, import_node_fs7.existsSync)(skillsDir)) return [];
2849
+ const commands = [];
2850
+ const entries = (0, import_node_fs7.readdirSync)(skillsDir, { withFileTypes: true });
2851
+ for (const entry of entries) {
2852
+ if (!entry.isDirectory()) continue;
2853
+ const skillFile = (0, import_node_path10.join)(skillsDir, entry.name, "SKILL.md");
2854
+ if (!(0, import_node_fs7.existsSync)(skillFile)) continue;
2855
+ const content = (0, import_node_fs7.readFileSync)(skillFile, "utf-8");
2856
+ const frontmatter = parseFrontmatter2(content);
2857
+ commands.push(buildCommand(frontmatter, content, entry.name));
2858
+ }
2859
+ return commands;
2860
+ }
2861
+ function scanCommandsDir(commandsDir) {
2862
+ if (!(0, import_node_fs7.existsSync)(commandsDir)) return [];
2863
+ const commands = [];
2864
+ const entries = (0, import_node_fs7.readdirSync)(commandsDir, { withFileTypes: true });
2865
+ for (const entry of entries) {
2866
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2867
+ const filePath = (0, import_node_path10.join)(commandsDir, entry.name);
2868
+ const content = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
2869
+ const frontmatter = parseFrontmatter2(content);
2870
+ const fallbackName = (0, import_node_path10.basename)(entry.name, ".md");
2871
+ commands.push(buildCommand(frontmatter, content, fallbackName));
2872
+ }
2873
+ return commands;
2874
+ }
2875
+ var SkillCommandSource = class {
2876
+ name = "skill";
2877
+ cwd;
2878
+ home;
2879
+ cachedCommands = null;
2880
+ constructor(cwd, home) {
2881
+ this.cwd = cwd;
2882
+ this.home = home ?? (0, import_node_os4.homedir)();
2883
+ }
2884
+ getCommands() {
2885
+ if (this.cachedCommands) return this.cachedCommands;
2886
+ const sources = [
2887
+ scanSkillsDir((0, import_node_path10.join)(this.cwd, ".claude", "skills")),
2888
+ scanCommandsDir((0, import_node_path10.join)(this.cwd, ".claude", "commands")),
2889
+ scanSkillsDir((0, import_node_path10.join)(this.home, ".robota", "skills")),
2890
+ scanSkillsDir((0, import_node_path10.join)(this.cwd, ".agents", "skills"))
2891
+ ];
2892
+ const seen = /* @__PURE__ */ new Set();
2893
+ const merged = [];
2894
+ for (const commands of sources) {
2895
+ for (const cmd of commands) {
2896
+ if (!seen.has(cmd.name)) {
2897
+ seen.add(cmd.name);
2898
+ merged.push(cmd);
2899
+ }
2900
+ }
2901
+ }
2902
+ this.cachedCommands = merged;
2903
+ return this.cachedCommands;
2904
+ }
2905
+ getModelInvocableSkills() {
2906
+ return this.getCommands().filter((cmd) => cmd.disableModelInvocation !== true);
2907
+ }
2908
+ getUserInvocableSkills() {
2909
+ return this.getCommands().filter((cmd) => cmd.userInvocable !== false);
2910
+ }
2911
+ };
2912
+
2913
+ // src/commands/plugin-source.ts
2914
+ var PluginCommandSource = class {
2915
+ name = "plugin";
2916
+ plugins;
2917
+ constructor(plugins) {
2918
+ this.plugins = plugins;
2919
+ }
2920
+ getCommands() {
2921
+ const commands = [];
2922
+ for (const plugin of this.plugins) {
2923
+ for (const skill of plugin.skills) {
2924
+ const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
2925
+ commands.push({
2926
+ name: baseName,
2927
+ description: `(${plugin.manifest.name}) ${skill.description}`,
2928
+ source: "plugin",
2929
+ skillContent: skill.skillContent,
2930
+ pluginDir: plugin.pluginDir
2931
+ });
2932
+ }
2933
+ for (const cmd of plugin.commands) {
2934
+ commands.push({
2935
+ name: cmd.name,
2936
+ description: cmd.description,
2937
+ source: "plugin",
2938
+ skillContent: cmd.skillContent,
2939
+ pluginDir: plugin.pluginDir
2940
+ });
2941
+ }
2942
+ }
2943
+ return commands;
2944
+ }
2945
+ };
2946
+
2947
+ // src/utils/skill-prompt.ts
2948
+ var import_node_child_process3 = require("child_process");
2949
+ function substituteVariables(content, args, context) {
2950
+ const argParts = args ? args.split(/\s+/) : [];
2951
+ let result = content;
2952
+ result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
2953
+ return argParts[Number(index)] ?? "";
2954
+ });
2955
+ result = result.replace(/\$ARGUMENTS/g, args);
2956
+ result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
2957
+ return argParts[Number(digit)] ?? "";
2958
+ });
2959
+ result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
2960
+ result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
2961
+ return result;
2962
+ }
2963
+ async function preprocessShellCommands(content) {
2964
+ const shellPattern = /!`([^`]+)`/g;
2965
+ if (!shellPattern.test(content)) {
2966
+ return content;
2967
+ }
2968
+ shellPattern.lastIndex = 0;
2969
+ let result = content;
2970
+ let match;
2971
+ const matches = [];
2972
+ while ((match = shellPattern.exec(content)) !== null) {
2973
+ matches.push({ full: match[0], command: match[1] });
2974
+ }
2975
+ for (const { full, command } of matches) {
2976
+ let output = "";
2977
+ try {
2978
+ output = (0, import_node_child_process3.execSync)(command, {
2979
+ timeout: 5e3,
2980
+ encoding: "utf-8",
2981
+ stdio: ["pipe", "pipe", "pipe"]
2982
+ }).trimEnd();
2983
+ } catch {
2984
+ output = "";
2985
+ }
2986
+ result = result.replace(full, output);
2987
+ }
2988
+ return result;
2989
+ }
2990
+ async function buildSkillPrompt(input, registry, context) {
2991
+ const parts = input.slice(1).split(/\s+/);
2992
+ const cmd = parts[0]?.toLowerCase() ?? "";
2993
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
2994
+ if (!skillCmd) return null;
2995
+ const args = parts.slice(1).join(" ").trim();
2996
+ const userInstruction = args || skillCmd.description;
2997
+ if (skillCmd.skillContent) {
2998
+ let processed = await preprocessShellCommands(skillCmd.skillContent);
2999
+ processed = substituteVariables(processed, args, context);
3000
+ return `<skill name="${cmd}">
3001
+ ${processed}
3002
+ </skill>
3003
+
3004
+ Execute the "${cmd}" skill: ${userInstruction}`;
3005
+ }
3006
+ return `Use the "${cmd}" skill: ${userInstruction}`;
3007
+ }
3008
+
3009
+ // src/types.ts
3010
+ var import_agent_core3 = require("@robota-sdk/agent-core");
3011
+
3012
+ // src/index.ts
3013
+ var import_agent_core4 = require("@robota-sdk/agent-core");
3014
+ var import_agent_core5 = require("@robota-sdk/agent-core");
3015
+
3016
+ // src/permissions/permission-prompt.ts
3017
+ var import_chalk = __toESM(require("chalk"), 1);
3018
+ var PERMISSION_OPTIONS = ["Allow", "Deny"];
3019
+ var ALLOW_INDEX = 0;
3020
+ function formatArgs(toolArgs) {
3021
+ const entries = Object.entries(toolArgs);
3022
+ if (entries.length === 0) {
3023
+ return "(no arguments)";
3024
+ }
3025
+ return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
3026
+ }
3027
+ async function promptForApproval(terminal, toolName, toolArgs) {
3028
+ terminal.writeLine("");
3029
+ terminal.writeLine(import_chalk.default.yellow(`[Permission Required] Tool: ${toolName}`));
3030
+ terminal.writeLine(import_chalk.default.dim(` ${formatArgs(toolArgs)}`));
3031
+ terminal.writeLine("");
3032
+ const selected = await terminal.select(PERMISSION_OPTIONS, ALLOW_INDEX);
3033
+ return selected === ALLOW_INDEX;
3034
+ }
630
3035
 
631
3036
  // src/index.ts
632
- var import_agent_tools3 = require("@robota-sdk/agent-tools");
633
- var import_agent_tools4 = require("@robota-sdk/agent-tools");
634
- var import_agent_tools5 = require("@robota-sdk/agent-tools");
635
- var import_agent_tools6 = require("@robota-sdk/agent-tools");
636
- var import_agent_tools7 = require("@robota-sdk/agent-tools");
637
- var import_agent_tools8 = require("@robota-sdk/agent-tools");
3037
+ var import_agent_core6 = require("@robota-sdk/agent-core");
638
3038
  // Annotate the CommonJS export names for ESM import in node:
639
3039
  0 && (module.exports = {
640
- DEFAULT_TOOL_DESCRIPTIONS,
641
- FileSessionLogger,
642
- Session,
643
- SessionStore,
644
- SilentSessionLogger,
3040
+ AgentExecutor,
3041
+ BUILT_IN_AGENTS,
3042
+ BuiltinCommandSource,
3043
+ BundlePluginInstaller,
3044
+ BundlePluginLoader,
3045
+ CommandRegistry,
3046
+ InteractiveSession,
3047
+ MarketplaceClient,
3048
+ PluginCommandSource,
3049
+ PluginSettingsStore,
3050
+ PromptExecutor,
3051
+ SkillCommandSource,
645
3052
  TRUST_TO_MODE,
646
- agentTool,
647
- bashTool,
648
- buildSystemPrompt,
649
- createDefaultTools,
650
- createProvider,
651
- createSession,
652
- detectProject,
653
- editTool,
3053
+ assembleSubagentPrompt,
3054
+ buildSkillPrompt,
3055
+ chatEntryToMessage,
3056
+ createAgentTool,
3057
+ createQuery,
3058
+ createSubagentLogger,
3059
+ createSubagentSession,
654
3060
  evaluatePermission,
655
- globTool,
656
- grepTool,
657
- loadConfig,
658
- loadContext,
3061
+ getBuiltInAgent,
3062
+ getForkWorkerSuffix,
3063
+ getMessagesForAPI,
3064
+ getSubagentSuffix,
3065
+ isChatEntry,
3066
+ messageToHistoryEntry,
3067
+ parseFrontmatter,
3068
+ preprocessShellCommands,
659
3069
  projectPaths,
660
3070
  promptForApproval,
661
- query,
662
- readTool,
3071
+ resolveSubagentLogDir,
3072
+ retrieveAgentToolDeps,
663
3073
  runHooks,
664
- setAgentToolDeps,
665
- userPaths,
666
- writeTool
3074
+ storeAgentToolDeps,
3075
+ substituteVariables,
3076
+ userPaths
667
3077
  });