@robota-sdk/agent-sdk 3.0.0-beta.3 → 3.0.0-beta.30

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,43 +30,359 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- FileSessionLogger: () => import_agent_sessions5.FileSessionLogger,
34
- Session: () => import_agent_sessions.Session,
35
- SessionStore: () => import_agent_sessions2.SessionStore,
36
- SilentSessionLogger: () => import_agent_sessions5.SilentSessionLogger,
33
+ AgentExecutor: () => AgentExecutor,
34
+ BundlePluginInstaller: () => BundlePluginInstaller,
35
+ BundlePluginLoader: () => BundlePluginLoader,
36
+ DEFAULT_TOOL_DESCRIPTIONS: () => DEFAULT_TOOL_DESCRIPTIONS,
37
+ FileSessionLogger: () => import_agent_sessions3.FileSessionLogger,
38
+ MarketplaceClient: () => MarketplaceClient,
39
+ PluginSettingsStore: () => PluginSettingsStore,
40
+ PromptExecutor: () => PromptExecutor,
41
+ Session: () => import_agent_sessions2.Session,
42
+ SessionStore: () => import_agent_sessions4.SessionStore,
43
+ SilentSessionLogger: () => import_agent_sessions3.SilentSessionLogger,
37
44
  TRUST_TO_MODE: () => import_agent_core.TRUST_TO_MODE,
38
45
  agentTool: () => agentTool,
39
- bashTool: () => import_agent_tools.bashTool,
46
+ bashTool: () => import_agent_tools3.bashTool,
40
47
  buildSystemPrompt: () => buildSystemPrompt,
48
+ createDefaultTools: () => createDefaultTools,
49
+ createProvider: () => createProvider,
50
+ createSession: () => createSession,
41
51
  detectProject: () => detectProject,
42
- editTool: () => import_agent_tools4.editTool,
52
+ editTool: () => import_agent_tools6.editTool,
43
53
  evaluatePermission: () => import_agent_core2.evaluatePermission,
44
- globTool: () => import_agent_tools5.globTool,
45
- grepTool: () => import_agent_tools6.grepTool,
54
+ globTool: () => import_agent_tools7.globTool,
55
+ grepTool: () => import_agent_tools8.grepTool,
46
56
  loadConfig: () => loadConfig,
47
57
  loadContext: () => loadContext,
48
58
  projectPaths: () => projectPaths,
49
59
  promptForApproval: () => promptForApproval,
50
60
  query: () => query,
51
- readTool: () => import_agent_tools2.readTool,
61
+ readTool: () => import_agent_tools4.readTool,
52
62
  runHooks: () => import_agent_core3.runHooks,
53
63
  setAgentToolDeps: () => setAgentToolDeps,
54
64
  userPaths: () => userPaths,
55
- writeTool: () => import_agent_tools3.writeTool
65
+ writeTool: () => import_agent_tools5.writeTool
56
66
  });
57
67
  module.exports = __toCommonJS(index_exports);
58
68
 
59
69
  // src/types.ts
60
70
  var import_agent_core = require("@robota-sdk/agent-core");
61
71
 
62
- // src/session.ts
72
+ // src/hooks/prompt-executor.ts
73
+ function extractJson(raw) {
74
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
75
+ if (codeBlockMatch) {
76
+ return codeBlockMatch[1].trim();
77
+ }
78
+ return raw.trim();
79
+ }
80
+ var PromptExecutor = class {
81
+ type = "prompt";
82
+ providerFactory;
83
+ defaultModel;
84
+ constructor(options) {
85
+ this.providerFactory = options.providerFactory;
86
+ this.defaultModel = options.defaultModel;
87
+ }
88
+ async execute(definition, input) {
89
+ const promptDef = definition;
90
+ const model = promptDef.model ?? this.defaultModel;
91
+ try {
92
+ const provider = this.providerFactory(model);
93
+ const prompt = `${promptDef.prompt}
94
+
95
+ Context:
96
+ ${JSON.stringify(input)}
97
+
98
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
99
+ const rawResponse = await provider.complete(prompt);
100
+ const jsonStr = extractJson(rawResponse);
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(jsonStr);
104
+ } catch {
105
+ return {
106
+ exitCode: 1,
107
+ stdout: "",
108
+ stderr: `Failed to parse AI response as JSON: ${rawResponse}`
109
+ };
110
+ }
111
+ if (parsed.ok) {
112
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
113
+ }
114
+ return {
115
+ exitCode: 2,
116
+ stdout: "",
117
+ stderr: parsed.reason ?? "Blocked by prompt hook"
118
+ };
119
+ } catch (err) {
120
+ const message = err instanceof Error ? err.message : String(err);
121
+ return { exitCode: 1, stdout: "", stderr: message };
122
+ }
123
+ }
124
+ };
125
+
126
+ // src/hooks/agent-executor.ts
127
+ var DEFAULT_MAX_TURNS = 50;
128
+ var DEFAULT_TIMEOUT_SECONDS = 60;
129
+ function extractJson2(raw) {
130
+ const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
131
+ if (codeBlockMatch) {
132
+ return codeBlockMatch[1].trim();
133
+ }
134
+ return raw.trim();
135
+ }
136
+ var AgentExecutor = class {
137
+ type = "agent";
138
+ sessionFactory;
139
+ constructor(options) {
140
+ this.sessionFactory = options.sessionFactory;
141
+ }
142
+ async execute(definition, input) {
143
+ const agentDef = definition;
144
+ const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
145
+ const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
146
+ try {
147
+ const session = this.sessionFactory({ maxTurns, timeout });
148
+ const prompt = `Hook input:
149
+ ${JSON.stringify(input)}
150
+
151
+ Respond with JSON: { "ok": boolean, "reason"?: string }`;
152
+ const rawResponse = await session.run(prompt);
153
+ const jsonStr = extractJson2(rawResponse);
154
+ let parsed;
155
+ try {
156
+ parsed = JSON.parse(jsonStr);
157
+ } catch {
158
+ return {
159
+ exitCode: 1,
160
+ stdout: "",
161
+ stderr: `Failed to parse agent response as JSON: ${rawResponse}`
162
+ };
163
+ }
164
+ if (parsed.ok) {
165
+ return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
166
+ }
167
+ return {
168
+ exitCode: 2,
169
+ stdout: "",
170
+ stderr: parsed.reason ?? "Blocked by agent hook"
171
+ };
172
+ } catch (err) {
173
+ const message = err instanceof Error ? err.message : String(err);
174
+ return { exitCode: 1, stdout: "", stderr: message };
175
+ }
176
+ }
177
+ };
178
+
179
+ // src/assembly/create-session.ts
63
180
  var import_agent_sessions = require("@robota-sdk/agent-sessions");
64
181
 
65
- // src/index.ts
66
- var import_agent_sessions5 = require("@robota-sdk/agent-sessions");
182
+ // src/context/system-prompt-builder.ts
183
+ var TRUST_LEVEL_DESCRIPTIONS = {
184
+ safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
185
+ moderate: "moderate (default mode \u2014 write and bash tools require approval)",
186
+ full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
187
+ };
188
+ function buildProjectSection(info) {
189
+ const lines = ["## Current Project"];
190
+ if (info.name !== void 0) {
191
+ lines.push(`- **Name:** ${info.name}`);
192
+ }
193
+ if (info.type !== "unknown") {
194
+ lines.push(`- **Type:** ${info.type}`);
195
+ }
196
+ if (info.language !== "unknown") {
197
+ lines.push(`- **Language:** ${info.language}`);
198
+ }
199
+ if (info.packageManager !== void 0) {
200
+ lines.push(`- **Package manager:** ${info.packageManager}`);
201
+ }
202
+ return lines.join("\n");
203
+ }
204
+ function buildToolsSection(descriptions) {
205
+ if (descriptions.length === 0) {
206
+ return "";
207
+ }
208
+ const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
209
+ return lines.join("\n");
210
+ }
211
+ function buildSkillsSection(skills) {
212
+ const invocable = skills.filter((s) => s.disableModelInvocation !== true);
213
+ if (invocable.length === 0) {
214
+ return "";
215
+ }
216
+ const lines = [
217
+ "## Skills",
218
+ "The following skills are available:",
219
+ "",
220
+ ...invocable.map((s) => `- ${s.name}: ${s.description}`)
221
+ ];
222
+ return lines.join("\n");
223
+ }
224
+ function buildSystemPrompt(params) {
225
+ const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
226
+ const sections = [];
227
+ const roleLines = [
228
+ "## Role",
229
+ "You are an AI coding assistant with access to tools that let you read and modify code.",
230
+ "You help developers understand, write, and improve their codebase.",
231
+ "Always be precise, follow existing code conventions, and prefer minimal changes."
232
+ ];
233
+ if (language) {
234
+ roleLines.push(
235
+ `Always respond in ${language}. Use ${language} for all explanations and communications.`
236
+ );
237
+ }
238
+ sections.push(roleLines.join("\n"));
239
+ if (cwd) {
240
+ sections.push(`## Working Directory
241
+ \`${cwd}\``);
242
+ }
243
+ sections.push(buildProjectSection(projectInfo));
244
+ sections.push(
245
+ [
246
+ "## Permission Mode",
247
+ `Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
248
+ ].join("\n")
249
+ );
250
+ if (agentsMd.trim().length > 0) {
251
+ sections.push(["## Agent Instructions", agentsMd].join("\n"));
252
+ }
253
+ if (claudeMd.trim().length > 0) {
254
+ sections.push(["## Project Notes", claudeMd].join("\n"));
255
+ }
256
+ sections.push(
257
+ [
258
+ "## Web Search",
259
+ "You have access to web search. When the user asks to search, look up, or find current/latest information,",
260
+ "you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
261
+ "Always prefer web search for: news, latest versions, current events, live documentation."
262
+ ].join("\n")
263
+ );
264
+ const toolsSection = buildToolsSection(toolDescriptions);
265
+ if (toolsSection.length > 0) {
266
+ sections.push(toolsSection);
267
+ }
268
+ if (params.skills !== void 0 && params.skills.length > 0) {
269
+ const skillsSection = buildSkillsSection(params.skills);
270
+ if (skillsSection.length > 0) {
271
+ sections.push(skillsSection);
272
+ }
273
+ }
274
+ return sections.join("\n\n");
275
+ }
276
+
277
+ // src/assembly/create-tools.ts
278
+ var import_agent_tools = require("@robota-sdk/agent-tools");
279
+ var DEFAULT_TOOL_DESCRIPTIONS = [
280
+ "Bash \u2014 execute shell commands",
281
+ "Read \u2014 read file contents with line numbers",
282
+ "Write \u2014 write content to a file",
283
+ "Edit \u2014 replace a string in a file",
284
+ "Glob \u2014 find files matching a pattern",
285
+ "Grep \u2014 search file contents with regex",
286
+ "WebSearch \u2014 search the internet (Anthropic built-in)"
287
+ ];
288
+ function createDefaultTools() {
289
+ return [
290
+ import_agent_tools.bashTool,
291
+ import_agent_tools.readTool,
292
+ import_agent_tools.writeTool,
293
+ import_agent_tools.editTool,
294
+ import_agent_tools.globTool,
295
+ import_agent_tools.grepTool,
296
+ import_agent_tools.webFetchTool,
297
+ import_agent_tools.webSearchTool
298
+ ];
299
+ }
300
+
301
+ // src/assembly/create-provider.ts
302
+ var import_agent_provider_anthropic = require("@robota-sdk/agent-provider-anthropic");
303
+ function createProvider(config) {
304
+ const apiKey = config.provider.apiKey ?? process.env["ANTHROPIC_API_KEY"];
305
+ if (!apiKey) {
306
+ throw new Error(
307
+ "ANTHROPIC_API_KEY is not set. Set the environment variable or configure provider.apiKey in ~/.robota/settings.json"
308
+ );
309
+ }
310
+ return new import_agent_provider_anthropic.AnthropicProvider({ apiKey });
311
+ }
312
+
313
+ // src/assembly/create-session.ts
314
+ function createSession(options) {
315
+ const provider = options.provider ?? createProvider(options.config);
316
+ const defaultTools = createDefaultTools();
317
+ const tools = [...defaultTools, ...options.additionalTools ?? []];
318
+ const buildPrompt = options.systemPromptBuilder ?? buildSystemPrompt;
319
+ const systemMessage = buildPrompt({
320
+ agentsMd: options.context.agentsMd,
321
+ claudeMd: options.context.claudeMd,
322
+ toolDescriptions: options.toolDescriptions ?? DEFAULT_TOOL_DESCRIPTIONS,
323
+ trustLevel: options.config.defaultTrustLevel,
324
+ projectInfo: options.projectInfo ?? { type: "unknown", language: "unknown" },
325
+ cwd: process.cwd(),
326
+ language: options.config.language
327
+ });
328
+ const defaultAllow = [
329
+ "Read(.agents/**)",
330
+ "Read(.claude/**)",
331
+ "Read(.robota/**)",
332
+ "Glob(.agents/**)",
333
+ "Glob(.claude/**)",
334
+ "Glob(.robota/**)"
335
+ ];
336
+ const mergedPermissions = {
337
+ allow: [...defaultAllow, ...options.config.permissions.allow ?? []],
338
+ deny: options.config.permissions.deny ?? []
339
+ };
340
+ const hookTypeExecutors = [];
341
+ if (options.providerFactory) {
342
+ hookTypeExecutors.push(
343
+ new PromptExecutor({
344
+ providerFactory: options.providerFactory,
345
+ defaultModel: options.config.provider.model
346
+ })
347
+ );
348
+ }
349
+ if (options.sessionFactory) {
350
+ hookTypeExecutors.push(
351
+ new AgentExecutor({
352
+ sessionFactory: options.sessionFactory
353
+ })
354
+ );
355
+ }
356
+ if (options.additionalHookExecutors) {
357
+ hookTypeExecutors.push(...options.additionalHookExecutors);
358
+ }
359
+ return new import_agent_sessions.Session({
360
+ tools,
361
+ provider,
362
+ systemMessage,
363
+ terminal: options.terminal,
364
+ permissions: mergedPermissions,
365
+ hooks: options.config.hooks,
366
+ permissionMode: options.permissionMode,
367
+ defaultTrustLevel: options.config.defaultTrustLevel,
368
+ model: options.config.provider.model,
369
+ maxTurns: options.maxTurns,
370
+ sessionStore: options.sessionStore,
371
+ permissionHandler: options.permissionHandler,
372
+ onTextDelta: options.onTextDelta,
373
+ onToolExecution: options.onToolExecution,
374
+ promptForApproval: options.promptForApproval,
375
+ onCompact: options.onCompact,
376
+ compactInstructions: options.compactInstructions ?? options.context.compactInstructions,
377
+ sessionLogger: options.sessionLogger,
378
+ hookTypeExecutors: hookTypeExecutors.length > 0 ? hookTypeExecutors : void 0
379
+ });
380
+ }
67
381
 
68
- // src/session-store.ts
382
+ // src/index.ts
69
383
  var import_agent_sessions2 = require("@robota-sdk/agent-sessions");
384
+ var import_agent_sessions3 = require("@robota-sdk/agent-sessions");
385
+ var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
70
386
 
71
387
  // src/config/config-loader.ts
72
388
  var import_fs = require("fs");
@@ -86,10 +402,34 @@ var PermissionsSchema = import_zod.z.object({
86
402
  deny: import_zod.z.array(import_zod.z.string()).optional()
87
403
  });
88
404
  var EnvSchema = import_zod.z.record(import_zod.z.string()).optional();
89
- var HookDefinitionSchema = import_zod.z.object({
405
+ var CommandHookDefinitionSchema = import_zod.z.object({
90
406
  type: import_zod.z.literal("command"),
91
- command: import_zod.z.string()
407
+ command: import_zod.z.string(),
408
+ timeout: import_zod.z.number().optional()
409
+ });
410
+ var HttpHookDefinitionSchema = import_zod.z.object({
411
+ type: import_zod.z.literal("http"),
412
+ url: import_zod.z.string(),
413
+ headers: import_zod.z.record(import_zod.z.string()).optional(),
414
+ timeout: import_zod.z.number().optional()
415
+ });
416
+ var PromptHookDefinitionSchema = import_zod.z.object({
417
+ type: import_zod.z.literal("prompt"),
418
+ prompt: import_zod.z.string(),
419
+ model: import_zod.z.string().optional()
92
420
  });
421
+ var AgentHookDefinitionSchema = import_zod.z.object({
422
+ type: import_zod.z.literal("agent"),
423
+ agent: import_zod.z.string(),
424
+ maxTurns: import_zod.z.number().optional(),
425
+ timeout: import_zod.z.number().optional()
426
+ });
427
+ var HookDefinitionSchema = import_zod.z.discriminatedUnion("type", [
428
+ CommandHookDefinitionSchema,
429
+ HttpHookDefinitionSchema,
430
+ PromptHookDefinitionSchema,
431
+ AgentHookDefinitionSchema
432
+ ]);
93
433
  var HookGroupSchema = import_zod.z.object({
94
434
  matcher: import_zod.z.string(),
95
435
  hooks: import_zod.z.array(HookDefinitionSchema)
@@ -98,15 +438,36 @@ var HooksSchema = import_zod.z.object({
98
438
  PreToolUse: import_zod.z.array(HookGroupSchema).optional(),
99
439
  PostToolUse: import_zod.z.array(HookGroupSchema).optional(),
100
440
  SessionStart: import_zod.z.array(HookGroupSchema).optional(),
101
- Stop: import_zod.z.array(HookGroupSchema).optional()
441
+ Stop: import_zod.z.array(HookGroupSchema).optional(),
442
+ PreCompact: import_zod.z.array(HookGroupSchema).optional(),
443
+ PostCompact: import_zod.z.array(HookGroupSchema).optional(),
444
+ UserPromptSubmit: import_zod.z.array(HookGroupSchema).optional(),
445
+ Notification: import_zod.z.array(HookGroupSchema).optional()
102
446
  }).optional();
447
+ var EnabledPluginsSchema = import_zod.z.record(import_zod.z.boolean()).optional();
448
+ var MarketplaceSourceSchema = import_zod.z.object({
449
+ source: import_zod.z.object({
450
+ type: import_zod.z.enum(["github", "git", "local", "url"]),
451
+ repo: import_zod.z.string().optional(),
452
+ url: import_zod.z.string().optional(),
453
+ path: import_zod.z.string().optional(),
454
+ ref: import_zod.z.string().optional()
455
+ })
456
+ });
457
+ var ExtraKnownMarketplacesSchema = import_zod.z.record(MarketplaceSourceSchema).optional();
103
458
  var SettingsSchema = import_zod.z.object({
104
459
  /** Trust level used when no --permission-mode flag is given */
105
460
  defaultTrustLevel: import_zod.z.enum(["safe", "moderate", "full"]).optional(),
461
+ /** Response language (e.g., "ko", "en", "ja"). Injected into system prompt. */
462
+ language: import_zod.z.string().optional(),
106
463
  provider: ProviderSchema.optional(),
107
464
  permissions: PermissionsSchema.optional(),
108
465
  env: EnvSchema,
109
- hooks: HooksSchema
466
+ hooks: HooksSchema,
467
+ /** Plugin enablement map: plugin name -> enabled/disabled */
468
+ enabledPlugins: EnabledPluginsSchema,
469
+ /** Extra marketplace URLs for BundlePlugin discovery */
470
+ extraKnownMarketplaces: ExtraKnownMarketplacesSchema
110
471
  });
111
472
 
112
473
  // src/config/config-loader.ts
@@ -130,8 +491,15 @@ function readJsonFile(filePath) {
130
491
  if (!(0, import_fs.existsSync)(filePath)) {
131
492
  return void 0;
132
493
  }
133
- const raw = (0, import_fs.readFileSync)(filePath, "utf-8");
134
- return JSON.parse(raw);
494
+ const raw = (0, import_fs.readFileSync)(filePath, "utf-8").trim();
495
+ if (raw.length === 0) {
496
+ return void 0;
497
+ }
498
+ try {
499
+ return JSON.parse(raw);
500
+ } catch {
501
+ return void 0;
502
+ }
135
503
  }
136
504
  function resolveEnvRef(value) {
137
505
  const ENV_PREFIX = "$ENV:";
@@ -166,13 +534,16 @@ function mergeSettings(layers) {
166
534
  env: {
167
535
  ...merged.env ?? {},
168
536
  ...layer.env ?? {}
169
- }
537
+ },
538
+ enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
539
+ extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
170
540
  };
171
541
  }, {});
172
542
  }
173
543
  function toResolvedConfig(merged) {
174
544
  return {
175
545
  defaultTrustLevel: merged.defaultTrustLevel ?? DEFAULTS.defaultTrustLevel,
546
+ language: merged.language,
176
547
  provider: {
177
548
  name: merged.provider?.name ?? DEFAULTS.provider.name,
178
549
  model: merged.provider?.model ?? DEFAULTS.provider.model,
@@ -183,25 +554,39 @@ function toResolvedConfig(merged) {
183
554
  deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
184
555
  },
185
556
  env: merged.env ?? DEFAULTS.env,
186
- hooks: merged.hooks ?? void 0
557
+ hooks: merged.hooks ?? void 0,
558
+ enabledPlugins: merged.enabledPlugins ?? void 0,
559
+ extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
187
560
  };
188
561
  }
562
+ function getSettingsPaths(cwd) {
563
+ const home = getHomeDir();
564
+ return [
565
+ (0, import_path.join)(home, ".robota", "settings.json"),
566
+ // 1. user (lowest)
567
+ (0, import_path.join)(cwd, ".robota", "settings.json"),
568
+ // 2. project
569
+ (0, import_path.join)(cwd, ".robota", "settings.local.json"),
570
+ // 3. project-local
571
+ (0, import_path.join)(cwd, ".claude", "settings.json"),
572
+ // 4. project, Claude Code compat
573
+ (0, import_path.join)(cwd, ".claude", "settings.local.json")
574
+ // 5. project-local (highest)
575
+ ];
576
+ }
189
577
  async function loadConfig(cwd) {
190
- const userSettingsPath = (0, import_path.join)(getHomeDir(), ".robota", "settings.json");
191
- const projectSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.json");
192
- const localSettingsPath = (0, import_path.join)(cwd, ".robota", "settings.local.json");
193
- const rawLayers = [
194
- readJsonFile(userSettingsPath),
195
- readJsonFile(projectSettingsPath),
196
- readJsonFile(localSettingsPath)
197
- ].filter((v) => v !== void 0);
198
- const parsedLayers = rawLayers.map((raw, index) => {
578
+ const allPaths = getSettingsPaths(cwd);
579
+ const rawEntries = [];
580
+ for (const filePath of allPaths) {
581
+ const raw = readJsonFile(filePath);
582
+ if (raw !== void 0) {
583
+ rawEntries.push({ raw, path: filePath });
584
+ }
585
+ }
586
+ const parsedLayers = rawEntries.map(({ raw, path }) => {
199
587
  const result = SettingsSchema.safeParse(raw);
200
588
  if (!result.success) {
201
- const paths = [userSettingsPath, projectSettingsPath, localSettingsPath].filter(
202
- (_, i) => rawLayers[i] !== void 0
203
- );
204
- throw new Error(`Invalid settings in ${paths[index] ?? "unknown"}: ${result.error.message}`);
589
+ throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
205
590
  }
206
591
  return resolveEnvRefs(result.data);
207
592
  });
@@ -331,77 +716,6 @@ async function detectProject(cwd) {
331
716
  };
332
717
  }
333
718
 
334
- // src/context/system-prompt-builder.ts
335
- var TRUST_LEVEL_DESCRIPTIONS = {
336
- safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
337
- moderate: "moderate (default mode \u2014 write and bash tools require approval)",
338
- full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
339
- };
340
- function buildProjectSection(info) {
341
- const lines = ["## Current Project"];
342
- if (info.name !== void 0) {
343
- lines.push(`- **Name:** ${info.name}`);
344
- }
345
- if (info.type !== "unknown") {
346
- lines.push(`- **Type:** ${info.type}`);
347
- }
348
- if (info.language !== "unknown") {
349
- lines.push(`- **Language:** ${info.language}`);
350
- }
351
- if (info.packageManager !== void 0) {
352
- lines.push(`- **Package manager:** ${info.packageManager}`);
353
- }
354
- return lines.join("\n");
355
- }
356
- function buildToolsSection(descriptions) {
357
- if (descriptions.length === 0) {
358
- return "";
359
- }
360
- const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
361
- return lines.join("\n");
362
- }
363
- function buildSystemPrompt(params) {
364
- const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo } = params;
365
- const sections = [];
366
- sections.push(
367
- [
368
- "## Role",
369
- "You are an AI coding assistant with access to tools that let you read and modify code.",
370
- "You help developers understand, write, and improve their codebase.",
371
- "Always be precise, follow existing code conventions, and prefer minimal changes."
372
- ].join("\n")
373
- );
374
- sections.push(buildProjectSection(projectInfo));
375
- sections.push(
376
- [
377
- "## Permission Mode",
378
- `Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
379
- ].join("\n")
380
- );
381
- if (agentsMd.trim().length > 0) {
382
- sections.push(["## Agent Instructions", agentsMd].join("\n"));
383
- }
384
- if (claudeMd.trim().length > 0) {
385
- sections.push(["## Project Notes", claudeMd].join("\n"));
386
- }
387
- sections.push(
388
- [
389
- "## Web Search",
390
- "You have access to web search. When the user asks to search, look up, or find current/latest information,",
391
- "you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
392
- "Always prefer web search for: news, latest versions, current events, live documentation."
393
- ].join("\n")
394
- );
395
- const toolsSection = buildToolsSection(toolDescriptions);
396
- if (toolsSection.length > 0) {
397
- sections.push(toolsSection);
398
- }
399
- return sections.join("\n\n");
400
- }
401
-
402
- // src/query.ts
403
- var import_agent_sessions3 = require("@robota-sdk/agent-sessions");
404
-
405
719
  // src/permissions/permission-prompt.ts
406
720
  var import_chalk = __toESM(require("chalk"), 1);
407
721
  var PERMISSION_OPTIONS = ["Allow", "Deny"];
@@ -445,7 +759,7 @@ async function query(prompt, options) {
445
759
  }, update: () => {
446
760
  } })
447
761
  };
448
- const session = new import_agent_sessions3.Session({
762
+ const session = createSession({
449
763
  config,
450
764
  context,
451
765
  terminal: noopTerminal,
@@ -457,16 +771,13 @@ async function query(prompt, options) {
457
771
  onTextDelta: options?.onTextDelta,
458
772
  onCompact: options?.onCompact,
459
773
  compactInstructions: context.compactInstructions,
460
- systemPromptBuilder: buildSystemPrompt,
461
774
  promptForApproval
462
775
  });
463
776
  return session.run(prompt);
464
777
  }
465
778
 
466
- // src/permissions/permission-gate.ts
779
+ // src/index.ts
467
780
  var import_agent_core2 = require("@robota-sdk/agent-core");
468
-
469
- // src/hooks/hook-runner.ts
470
781
  var import_agent_core3 = require("@robota-sdk/agent-core");
471
782
 
472
783
  // src/paths.ts
@@ -489,28 +800,782 @@ function userPaths() {
489
800
  };
490
801
  }
491
802
 
492
- // src/tools/bash-tool.ts
493
- var import_agent_tools = require("@robota-sdk/agent-tools");
494
-
495
- // src/tools/read-tool.ts
496
- var import_agent_tools2 = require("@robota-sdk/agent-tools");
497
-
498
- // src/tools/write-tool.ts
499
- var import_agent_tools3 = require("@robota-sdk/agent-tools");
803
+ // src/plugins/plugin-settings-store.ts
804
+ var import_node_fs = require("fs");
805
+ var import_node_path2 = require("path");
806
+ var PluginSettingsStore = class {
807
+ settingsPath;
808
+ constructor(settingsPath) {
809
+ this.settingsPath = settingsPath;
810
+ }
811
+ /** Read the full settings file from disk. */
812
+ readAll() {
813
+ if (!(0, import_node_fs.existsSync)(this.settingsPath)) {
814
+ return {};
815
+ }
816
+ try {
817
+ const raw = (0, import_node_fs.readFileSync)(this.settingsPath, "utf-8");
818
+ const data = JSON.parse(raw);
819
+ if (typeof data === "object" && data !== null) {
820
+ return data;
821
+ }
822
+ return {};
823
+ } catch {
824
+ return {};
825
+ }
826
+ }
827
+ /** Write the full settings file to disk. */
828
+ writeAll(settings) {
829
+ const dir = (0, import_node_path2.dirname)(this.settingsPath);
830
+ if (!(0, import_node_fs.existsSync)(dir)) {
831
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
832
+ }
833
+ (0, import_node_fs.writeFileSync)(this.settingsPath, JSON.stringify(settings, null, 2), "utf-8");
834
+ }
835
+ // --- enabledPlugins ---
836
+ /** Get the enabledPlugins map. */
837
+ getEnabledPlugins() {
838
+ const settings = this.readAll();
839
+ const ep = settings.enabledPlugins;
840
+ if (typeof ep === "object" && ep !== null) {
841
+ return ep;
842
+ }
843
+ return {};
844
+ }
845
+ /** Set a single plugin's enabled state. */
846
+ setPluginEnabled(pluginId, enabled) {
847
+ const settings = this.readAll();
848
+ const ep = this.getEnabledPluginsFrom(settings);
849
+ ep[pluginId] = enabled;
850
+ settings.enabledPlugins = ep;
851
+ this.writeAll(settings);
852
+ }
853
+ /** Remove a plugin from enabledPlugins. */
854
+ removePluginEntry(pluginId) {
855
+ const settings = this.readAll();
856
+ const ep = this.getEnabledPluginsFrom(settings);
857
+ delete ep[pluginId];
858
+ settings.enabledPlugins = ep;
859
+ this.writeAll(settings);
860
+ }
861
+ // --- extraKnownMarketplaces ---
862
+ /** Get all persisted marketplace sources. */
863
+ getMarketplaceSources() {
864
+ const settings = this.readAll();
865
+ const extra = settings.extraKnownMarketplaces;
866
+ if (typeof extra === "object" && extra !== null) {
867
+ return extra;
868
+ }
869
+ return {};
870
+ }
871
+ /** Add or update a marketplace source. */
872
+ setMarketplaceSource(name, source) {
873
+ const settings = this.readAll();
874
+ const extra = this.getMarketplaceSourcesFrom(settings);
875
+ extra[name] = { source };
876
+ settings.extraKnownMarketplaces = extra;
877
+ this.writeAll(settings);
878
+ }
879
+ /** Remove a marketplace source. */
880
+ removeMarketplaceSource(name) {
881
+ const settings = this.readAll();
882
+ const extra = this.getMarketplaceSourcesFrom(settings);
883
+ delete extra[name];
884
+ settings.extraKnownMarketplaces = extra;
885
+ this.writeAll(settings);
886
+ }
887
+ // --- helpers ---
888
+ getEnabledPluginsFrom(settings) {
889
+ const ep = settings.enabledPlugins;
890
+ if (typeof ep === "object" && ep !== null) {
891
+ return ep;
892
+ }
893
+ return {};
894
+ }
895
+ getMarketplaceSourcesFrom(settings) {
896
+ const extra = settings.extraKnownMarketplaces;
897
+ if (typeof extra === "object" && extra !== null) {
898
+ return extra;
899
+ }
900
+ return {};
901
+ }
902
+ };
500
903
 
501
- // src/tools/edit-tool.ts
502
- var import_agent_tools4 = require("@robota-sdk/agent-tools");
904
+ // src/plugins/bundle-plugin-loader.ts
905
+ var import_node_fs2 = require("fs");
906
+ var import_node_path3 = require("path");
907
+ function parseSkillFrontmatter(raw) {
908
+ const trimmed = raw.trimStart();
909
+ if (!trimmed.startsWith("---")) {
910
+ return { metadata: {}, content: raw };
911
+ }
912
+ const endIndex = trimmed.indexOf("---", 3);
913
+ if (endIndex === -1) {
914
+ return { metadata: {}, content: raw };
915
+ }
916
+ const frontmatterBlock = trimmed.slice(3, endIndex).trim();
917
+ const content = trimmed.slice(endIndex + 3).trimStart();
918
+ const metadata = {};
919
+ for (const line of frontmatterBlock.split("\n")) {
920
+ const colonIndex = line.indexOf(":");
921
+ if (colonIndex === -1) continue;
922
+ const key = line.slice(0, colonIndex).trim();
923
+ let value = line.slice(colonIndex + 1).trim();
924
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
925
+ const inner = value.slice(1, -1);
926
+ value = inner.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
927
+ }
928
+ if (key) {
929
+ metadata[key] = value;
930
+ }
931
+ }
932
+ return { metadata, content };
933
+ }
934
+ function validateManifest(data) {
935
+ if (typeof data !== "object" || data === null) return null;
936
+ const obj = data;
937
+ if (typeof obj.name !== "string") return null;
938
+ if (typeof obj.version !== "string") return null;
939
+ if (typeof obj.description !== "string") return null;
940
+ const features = typeof obj.features === "object" && obj.features !== null ? obj.features : {};
941
+ return {
942
+ name: obj.name,
943
+ version: obj.version,
944
+ description: obj.description,
945
+ features: {
946
+ commands: features.commands === true ? true : void 0,
947
+ agents: features.agents === true ? true : void 0,
948
+ skills: features.skills === true ? true : void 0,
949
+ hooks: features.hooks === true ? true : void 0,
950
+ mcp: features.mcp === true ? true : void 0
951
+ }
952
+ };
953
+ }
954
+ function getSortedSubdirs(dirPath) {
955
+ if (!(0, import_node_fs2.existsSync)(dirPath)) return [];
956
+ try {
957
+ const entries = (0, import_node_fs2.readdirSync)(dirPath, { withFileTypes: true });
958
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
959
+ } catch {
960
+ return [];
961
+ }
962
+ }
963
+ var BundlePluginLoader = class {
964
+ pluginsDir;
965
+ enabledPlugins;
966
+ constructor(pluginsDir, enabledPlugins) {
967
+ this.pluginsDir = pluginsDir;
968
+ this.enabledPlugins = enabledPlugins ?? {};
969
+ }
970
+ /** Load all discovered and enabled bundle plugins (sync). */
971
+ loadPluginsSync() {
972
+ return this.discoverAndLoad();
973
+ }
974
+ /** Load all discovered and enabled bundle plugins (async wrapper). */
975
+ async loadAll() {
976
+ return this.discoverAndLoad();
977
+ }
978
+ /**
979
+ * Discover and load plugins from the cache directory.
980
+ *
981
+ * Directory structure: `<pluginsDir>/cache/<marketplace>/<plugin>/<version>/`
982
+ * For each marketplace/plugin pair, the latest version (lexicographically last) is loaded.
983
+ */
984
+ discoverAndLoad() {
985
+ const cacheDir = (0, import_node_path3.join)(this.pluginsDir, "cache");
986
+ if (!(0, import_node_fs2.existsSync)(cacheDir)) {
987
+ return [];
988
+ }
989
+ const results = [];
990
+ const marketplaces = getSortedSubdirs(cacheDir);
991
+ for (const marketplace of marketplaces) {
992
+ const marketplaceDir = (0, import_node_path3.join)(cacheDir, marketplace);
993
+ const plugins = getSortedSubdirs(marketplaceDir);
994
+ for (const pluginName of plugins) {
995
+ const pluginDir = (0, import_node_path3.join)(marketplaceDir, pluginName);
996
+ const versions = getSortedSubdirs(pluginDir);
997
+ if (versions.length === 0) continue;
998
+ const latestVersion = versions[versions.length - 1];
999
+ const versionDir = (0, import_node_path3.join)(pluginDir, latestVersion);
1000
+ const manifestPath = (0, import_node_path3.join)(versionDir, ".claude-plugin", "plugin.json");
1001
+ if (!(0, import_node_fs2.existsSync)(manifestPath)) continue;
1002
+ const manifest = this.readManifest(manifestPath);
1003
+ if (!manifest) continue;
1004
+ const pluginId = `${manifest.name}@${marketplace}`;
1005
+ if (this.isDisabled(pluginId, manifest.name)) continue;
1006
+ const loaded = this.loadPlugin(versionDir, manifest);
1007
+ results.push(loaded);
1008
+ }
1009
+ }
1010
+ return results;
1011
+ }
1012
+ /** Read and validate a plugin.json manifest. Returns null on failure. */
1013
+ readManifest(path) {
1014
+ try {
1015
+ const raw = (0, import_node_fs2.readFileSync)(path, "utf-8");
1016
+ const data = JSON.parse(raw);
1017
+ return validateManifest(data);
1018
+ } catch {
1019
+ return null;
1020
+ }
1021
+ }
1022
+ /**
1023
+ * Check if a plugin is explicitly disabled.
1024
+ * Checks both `name@marketplace` and `name` keys.
1025
+ * Plugins not listed in enabledPlugins are enabled by default.
1026
+ */
1027
+ isDisabled(pluginId, pluginName) {
1028
+ if (pluginId in this.enabledPlugins) {
1029
+ return this.enabledPlugins[pluginId] === false;
1030
+ }
1031
+ if (pluginName in this.enabledPlugins) {
1032
+ return this.enabledPlugins[pluginName] === false;
1033
+ }
1034
+ return false;
1035
+ }
1036
+ /** Load a single plugin's skills, hooks, agents, and MCP config. */
1037
+ loadPlugin(pluginDir, manifest) {
1038
+ return {
1039
+ manifest,
1040
+ skills: this.loadSkills(pluginDir, manifest.name),
1041
+ commands: this.loadCommands(pluginDir, manifest.name),
1042
+ hooks: this.loadHooks(pluginDir),
1043
+ mcpConfig: this.loadMcpConfig(pluginDir),
1044
+ agents: this.loadAgents(pluginDir),
1045
+ pluginDir
1046
+ };
1047
+ }
1048
+ /** Load skills from the plugin's skills/ directory. */
1049
+ loadSkills(pluginDir, pluginName) {
1050
+ const skillsDir = (0, import_node_path3.join)(pluginDir, "skills");
1051
+ if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
1052
+ const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
1053
+ const skills = [];
1054
+ for (const entry of entries) {
1055
+ if (!entry.isDirectory()) continue;
1056
+ const skillFile = (0, import_node_path3.join)(skillsDir, entry.name, "SKILL.md");
1057
+ if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
1058
+ const raw = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
1059
+ const { metadata, content } = parseSkillFrontmatter(raw);
1060
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1061
+ const skill = {
1062
+ name: entry.name,
1063
+ description,
1064
+ skillContent: content,
1065
+ ...metadata
1066
+ };
1067
+ skills.push(skill);
1068
+ }
1069
+ return skills;
1070
+ }
1071
+ /** Load commands from the plugin's commands/ directory (flat .md files). */
1072
+ loadCommands(pluginDir, pluginName) {
1073
+ const commandsDir = (0, import_node_path3.join)(pluginDir, "commands");
1074
+ if (!(0, import_node_fs2.existsSync)(commandsDir)) return [];
1075
+ const entries = (0, import_node_fs2.readdirSync)(commandsDir, { withFileTypes: true });
1076
+ const commands = [];
1077
+ for (const entry of entries) {
1078
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1079
+ const raw = (0, import_node_fs2.readFileSync)((0, import_node_path3.join)(commandsDir, entry.name), "utf-8");
1080
+ const { metadata, content } = parseSkillFrontmatter(raw);
1081
+ const name = typeof metadata.name === "string" ? metadata.name : entry.name.replace(/\.md$/, "");
1082
+ const description = typeof metadata.description === "string" ? metadata.description : "";
1083
+ commands.push({
1084
+ ...metadata,
1085
+ name: `${pluginName}:${name}`,
1086
+ description,
1087
+ skillContent: content
1088
+ });
1089
+ }
1090
+ return commands;
1091
+ }
1092
+ /** Load hooks from hooks/hooks.json if present. */
1093
+ loadHooks(pluginDir) {
1094
+ const hooksPath = (0, import_node_path3.join)(pluginDir, "hooks", "hooks.json");
1095
+ if (!(0, import_node_fs2.existsSync)(hooksPath)) return {};
1096
+ try {
1097
+ const raw = (0, import_node_fs2.readFileSync)(hooksPath, "utf-8");
1098
+ const data = JSON.parse(raw);
1099
+ if (typeof data === "object" && data !== null) {
1100
+ return data;
1101
+ }
1102
+ return {};
1103
+ } catch {
1104
+ return {};
1105
+ }
1106
+ }
1107
+ /** Load MCP server configuration if present. Checks `.mcp.json` at plugin root first. */
1108
+ loadMcpConfig(pluginDir) {
1109
+ const primaryPath = (0, import_node_path3.join)(pluginDir, ".mcp.json");
1110
+ const fallbackPath = (0, import_node_path3.join)(pluginDir, ".claude-plugin", "mcp.json");
1111
+ const mcpPath = (0, import_node_fs2.existsSync)(primaryPath) ? primaryPath : fallbackPath;
1112
+ if (!(0, import_node_fs2.existsSync)(mcpPath)) return void 0;
1113
+ try {
1114
+ const raw = (0, import_node_fs2.readFileSync)(mcpPath, "utf-8");
1115
+ return JSON.parse(raw);
1116
+ } catch {
1117
+ return void 0;
1118
+ }
1119
+ }
1120
+ /** Load agent definitions from agents/ directory if present. */
1121
+ loadAgents(pluginDir) {
1122
+ const agentsDir = (0, import_node_path3.join)(pluginDir, "agents");
1123
+ if (!(0, import_node_fs2.existsSync)(agentsDir)) return [];
1124
+ try {
1125
+ const entries = (0, import_node_fs2.readdirSync)(agentsDir, { withFileTypes: true });
1126
+ return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
1127
+ } catch {
1128
+ return [];
1129
+ }
1130
+ }
1131
+ };
503
1132
 
504
- // src/tools/glob-tool.ts
505
- var import_agent_tools5 = require("@robota-sdk/agent-tools");
1133
+ // src/plugins/bundle-plugin-installer.ts
1134
+ var import_node_child_process = require("child_process");
1135
+ var import_node_fs3 = require("fs");
1136
+ var import_node_path4 = require("path");
1137
+ var GIT_CLONE_TIMEOUT_MS = 6e4;
1138
+ var BundlePluginInstaller = class {
1139
+ pluginsDir;
1140
+ cacheDir;
1141
+ registryPath;
1142
+ settingsStore;
1143
+ marketplaceClient;
1144
+ exec;
1145
+ constructor(options) {
1146
+ this.pluginsDir = options.pluginsDir;
1147
+ this.cacheDir = (0, import_node_path4.join)(this.pluginsDir, "cache");
1148
+ this.registryPath = (0, import_node_path4.join)(this.pluginsDir, "installed_plugins.json");
1149
+ this.settingsStore = options.settingsStore;
1150
+ this.marketplaceClient = options.marketplaceClient;
1151
+ this.exec = options.exec ?? this.defaultExec;
1152
+ }
1153
+ /**
1154
+ * Install a plugin from a marketplace.
1155
+ *
1156
+ * 1. Read marketplace manifest to find the plugin entry.
1157
+ * 2. Resolve source (relative path, github, or url).
1158
+ * 3. Copy/clone to `cache/<marketplace>/<plugin>/<version>/`.
1159
+ * 4. Record in `installed_plugins.json`.
1160
+ */
1161
+ async install(pluginName, marketplaceName) {
1162
+ const manifest = this.marketplaceClient.fetchManifest(marketplaceName);
1163
+ const entry = manifest.plugins.find((p) => p.name === pluginName);
1164
+ if (!entry) {
1165
+ throw new Error(`Plugin "${pluginName}" not found in marketplace "${marketplaceName}"`);
1166
+ }
1167
+ const version = this.resolveVersion(entry, marketplaceName);
1168
+ const targetDir = (0, import_node_path4.join)(this.cacheDir, marketplaceName, pluginName, version);
1169
+ if ((0, import_node_fs3.existsSync)(targetDir)) {
1170
+ throw new Error(
1171
+ `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
1172
+ );
1173
+ }
1174
+ this.resolveAndInstall(entry.source, marketplaceName, pluginName, targetDir);
1175
+ const pluginId = `${pluginName}@${marketplaceName}`;
1176
+ const registry = this.readRegistry();
1177
+ registry[pluginId] = {
1178
+ pluginName,
1179
+ marketplace: marketplaceName,
1180
+ version,
1181
+ installPath: targetDir,
1182
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
1183
+ };
1184
+ this.writeRegistry(registry);
1185
+ }
1186
+ /**
1187
+ * Uninstall a plugin.
1188
+ * Removes from cache and from installed_plugins.json.
1189
+ */
1190
+ async uninstall(pluginId) {
1191
+ const registry = this.readRegistry();
1192
+ const record = registry[pluginId];
1193
+ if (!record) {
1194
+ throw new Error(`Plugin "${pluginId}" is not installed`);
1195
+ }
1196
+ if ((0, import_node_fs3.existsSync)(record.installPath)) {
1197
+ (0, import_node_fs3.rmSync)(record.installPath, { recursive: true, force: true });
1198
+ }
1199
+ delete registry[pluginId];
1200
+ this.writeRegistry(registry);
1201
+ this.settingsStore.removePluginEntry(pluginId);
1202
+ }
1203
+ /** Enable a plugin by setting its enabledPlugins entry to true. */
1204
+ async enable(pluginId) {
1205
+ this.settingsStore.setPluginEnabled(pluginId, true);
1206
+ }
1207
+ /** Disable a plugin by setting its enabledPlugins entry to false. */
1208
+ async disable(pluginId) {
1209
+ this.settingsStore.setPluginEnabled(pluginId, false);
1210
+ }
1211
+ /** Get all installed plugins. */
1212
+ getInstalledPlugins() {
1213
+ return this.readRegistry();
1214
+ }
1215
+ /** Get plugins installed from a specific marketplace. */
1216
+ getPluginsByMarketplace(marketplaceName) {
1217
+ const registry = this.readRegistry();
1218
+ return Object.values(registry).filter((r) => r.marketplace === marketplaceName);
1219
+ }
1220
+ // --- Private helpers ---
1221
+ /** Resolve the version for a plugin entry. */
1222
+ resolveVersion(entry, marketplaceName) {
1223
+ const entryWithVersion = entry;
1224
+ if (typeof entryWithVersion.version === "string" && entryWithVersion.version) {
1225
+ return entryWithVersion.version;
1226
+ }
1227
+ return this.marketplaceClient.getMarketplaceSha(marketplaceName);
1228
+ }
1229
+ /** Resolve the source and install the plugin. */
1230
+ resolveAndInstall(source, marketplaceName, pluginName, targetDir) {
1231
+ (0, import_node_fs3.mkdirSync)(targetDir, { recursive: true });
1232
+ if (typeof source === "string") {
1233
+ const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
1234
+ const sourcePath = (0, import_node_path4.join)(marketplaceDir, source);
1235
+ if (!(0, import_node_fs3.existsSync)(sourcePath)) {
1236
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1237
+ throw new Error(
1238
+ `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
1239
+ );
1240
+ }
1241
+ (0, import_node_fs3.cpSync)(sourcePath, targetDir, { recursive: true });
1242
+ } else if (source.type === "github") {
1243
+ const repoUrl = `https://github.com/${source.repo}.git`;
1244
+ this.cloneToDir(repoUrl, targetDir, pluginName);
1245
+ } else if (source.type === "url") {
1246
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1247
+ throw new Error("URL source installation is not yet supported");
1248
+ }
1249
+ }
1250
+ /** Clone a git repository to the target directory. */
1251
+ cloneToDir(repoUrl, targetDir, pluginName) {
1252
+ (0, import_node_fs3.rmSync)(targetDir, { recursive: true, force: true });
1253
+ const command = `git clone --depth 1 ${repoUrl} ${targetDir}`;
1254
+ try {
1255
+ this.exec(command, { timeout: GIT_CLONE_TIMEOUT_MS, stdio: "pipe" });
1256
+ } catch (error) {
1257
+ const message = error instanceof Error ? error.message : String(error);
1258
+ throw new Error(`Failed to clone plugin "${pluginName}": ${message}`);
1259
+ }
1260
+ }
1261
+ /** Read the installed_plugins.json registry. */
1262
+ readRegistry() {
1263
+ if (!(0, import_node_fs3.existsSync)(this.registryPath)) {
1264
+ return {};
1265
+ }
1266
+ try {
1267
+ const raw = (0, import_node_fs3.readFileSync)(this.registryPath, "utf-8");
1268
+ const data = JSON.parse(raw);
1269
+ if (typeof data === "object" && data !== null) {
1270
+ return data;
1271
+ }
1272
+ return {};
1273
+ } catch {
1274
+ return {};
1275
+ }
1276
+ }
1277
+ /** Write the installed_plugins.json registry. */
1278
+ writeRegistry(registry) {
1279
+ const dir = (0, import_node_path4.dirname)(this.registryPath);
1280
+ if (!(0, import_node_fs3.existsSync)(dir)) {
1281
+ (0, import_node_fs3.mkdirSync)(dir, { recursive: true });
1282
+ }
1283
+ (0, import_node_fs3.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1284
+ }
1285
+ /** Default exec implementation using child_process. */
1286
+ defaultExec(command, options) {
1287
+ return (0, import_node_child_process.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
1288
+ }
1289
+ };
506
1290
 
507
- // src/tools/grep-tool.ts
508
- var import_agent_tools6 = require("@robota-sdk/agent-tools");
1291
+ // src/plugins/marketplace-client.ts
1292
+ var import_node_child_process2 = require("child_process");
1293
+ var import_node_fs4 = require("fs");
1294
+ var import_node_path5 = require("path");
1295
+ var GIT_TIMEOUT_MS = 6e4;
1296
+ var MarketplaceClient = class {
1297
+ pluginsDir;
1298
+ exec;
1299
+ marketplacesDir;
1300
+ registryPath;
1301
+ constructor(options) {
1302
+ this.pluginsDir = options.pluginsDir;
1303
+ this.exec = options.exec ?? this.defaultExec;
1304
+ this.marketplacesDir = (0, import_node_path5.join)(this.pluginsDir, "marketplaces");
1305
+ this.registryPath = (0, import_node_path5.join)(this.pluginsDir, "known_marketplaces.json");
1306
+ }
1307
+ /**
1308
+ * Add a marketplace by cloning its repository.
1309
+ *
1310
+ * 1. Parse source: `owner/repo` string becomes a GitHub source.
1311
+ * 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
1312
+ * 3. Read `.claude-plugin/marketplace.json` for the `name` field.
1313
+ * 4. Register in `known_marketplaces.json`.
1314
+ *
1315
+ * Returns the registered marketplace name from the manifest.
1316
+ */
1317
+ addMarketplace(source) {
1318
+ const tempName = "temp-" + Date.now().toString(36);
1319
+ const tempDir = (0, import_node_path5.join)(this.marketplacesDir, tempName);
1320
+ (0, import_node_fs4.mkdirSync)(this.marketplacesDir, { recursive: true });
1321
+ if (source.type === "local") {
1322
+ if (!(0, import_node_fs4.existsSync)(source.path)) {
1323
+ throw new Error(`Local marketplace path does not exist: ${source.path}`);
1324
+ }
1325
+ (0, import_node_fs4.cpSync)(source.path, tempDir, { recursive: true });
1326
+ } else {
1327
+ const cloneUrl = this.resolveCloneUrl(source);
1328
+ const command = `git clone --depth 1 ${cloneUrl} ${tempDir}`;
1329
+ try {
1330
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1331
+ } catch (error) {
1332
+ const message = error instanceof Error ? error.message : String(error);
1333
+ throw new Error(`Failed to clone marketplace: ${message}`);
1334
+ }
1335
+ }
1336
+ const manifestPath = (0, import_node_path5.join)(tempDir, ".claude-plugin", "marketplace.json");
1337
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) {
1338
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1339
+ throw new Error(
1340
+ source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
1341
+ );
1342
+ }
1343
+ const manifest = this.readManifestFromPath(manifestPath);
1344
+ const name = manifest.name;
1345
+ if (!name) {
1346
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1347
+ throw new Error('Marketplace manifest does not contain a "name" field');
1348
+ }
1349
+ const registry = this.readRegistry();
1350
+ if (registry[name]) {
1351
+ (0, import_node_fs4.rmSync)(tempDir, { recursive: true, force: true });
1352
+ throw new Error(`Marketplace "${name}" already exists`);
1353
+ }
1354
+ const finalDir = (0, import_node_path5.join)(this.marketplacesDir, name);
1355
+ (0, import_node_fs4.renameSync)(tempDir, finalDir);
1356
+ registry[name] = {
1357
+ source,
1358
+ installLocation: finalDir,
1359
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1360
+ };
1361
+ this.writeRegistry(registry);
1362
+ return name;
1363
+ }
1364
+ /**
1365
+ * Remove a marketplace.
1366
+ * Uninstalls all plugins from that marketplace, then deletes the clone directory
1367
+ * and removes from the registry.
1368
+ */
1369
+ removeMarketplace(name) {
1370
+ const registry = this.readRegistry();
1371
+ const entry = registry[name];
1372
+ if (!entry) {
1373
+ throw new Error(`Marketplace "${name}" not found`);
1374
+ }
1375
+ this.removeInstalledPluginsForMarketplace(name);
1376
+ if ((0, import_node_fs4.existsSync)(entry.installLocation)) {
1377
+ (0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
1378
+ }
1379
+ delete registry[name];
1380
+ this.writeRegistry(registry);
1381
+ }
1382
+ /**
1383
+ * Update a marketplace by running git pull on its clone.
1384
+ * The manifest is re-read from disk on demand (via fetchManifest), so the
1385
+ * updated manifest is automatically available after pull.
1386
+ *
1387
+ * TODO: After pull, detect version changes in installed plugins and offer
1388
+ * to update them (re-install at new version).
1389
+ */
1390
+ updateMarketplace(name) {
1391
+ const registry = this.readRegistry();
1392
+ const entry = registry[name];
1393
+ if (!entry) {
1394
+ throw new Error(`Marketplace "${name}" not found`);
1395
+ }
1396
+ if (!(0, import_node_fs4.existsSync)(entry.installLocation)) {
1397
+ throw new Error(`Marketplace directory for "${name}" does not exist`);
1398
+ }
1399
+ if (entry.source.type === "local") {
1400
+ const localSource = entry.source;
1401
+ if (!(0, import_node_fs4.existsSync)(localSource.path)) {
1402
+ throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
1403
+ }
1404
+ (0, import_node_fs4.rmSync)(entry.installLocation, { recursive: true, force: true });
1405
+ (0, import_node_fs4.cpSync)(localSource.path, entry.installLocation, { recursive: true });
1406
+ } else {
1407
+ const command = `git -C ${entry.installLocation} pull`;
1408
+ try {
1409
+ this.exec(command, { timeout: GIT_TIMEOUT_MS, stdio: "pipe" });
1410
+ } catch (error) {
1411
+ const message = error instanceof Error ? error.message : String(error);
1412
+ throw new Error(`Failed to update marketplace "${name}": ${message}`);
1413
+ }
1414
+ }
1415
+ entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1416
+ this.writeRegistry(registry);
1417
+ }
1418
+ /** List all registered marketplaces. */
1419
+ listMarketplaces() {
1420
+ const registry = this.readRegistry();
1421
+ return Object.entries(registry).map(([name, entry]) => ({
1422
+ name,
1423
+ source: entry.source,
1424
+ lastUpdated: entry.lastUpdated
1425
+ }));
1426
+ }
1427
+ /**
1428
+ * Read the marketplace manifest from a registered marketplace's clone.
1429
+ */
1430
+ fetchManifest(marketplaceName) {
1431
+ const registry = this.readRegistry();
1432
+ const entry = registry[marketplaceName];
1433
+ if (!entry) {
1434
+ throw new Error(`Marketplace "${marketplaceName}" not found`);
1435
+ }
1436
+ const manifestPath = (0, import_node_path5.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
1437
+ if (!(0, import_node_fs4.existsSync)(manifestPath)) {
1438
+ throw new Error(
1439
+ `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
1440
+ );
1441
+ }
1442
+ return this.readManifestFromPath(manifestPath);
1443
+ }
1444
+ /** Get the clone directory path for a registered marketplace. */
1445
+ getMarketplaceDir(name) {
1446
+ const registry = this.readRegistry();
1447
+ const entry = registry[name];
1448
+ if (!entry) {
1449
+ throw new Error(`Marketplace "${name}" not found`);
1450
+ }
1451
+ return entry.installLocation;
1452
+ }
1453
+ /**
1454
+ * Get the current git SHA (first 12 chars) for a marketplace clone.
1455
+ * Used as a version identifier when plugins lack explicit versions.
1456
+ */
1457
+ getMarketplaceSha(name) {
1458
+ const dir = this.getMarketplaceDir(name);
1459
+ try {
1460
+ const result = this.exec(`git -C ${dir} rev-parse HEAD`, {
1461
+ timeout: GIT_TIMEOUT_MS,
1462
+ stdio: "pipe"
1463
+ });
1464
+ return result.toString().trim().slice(0, 12);
1465
+ } catch {
1466
+ return "unknown";
1467
+ }
1468
+ }
1469
+ /** List all available plugins across all marketplaces. */
1470
+ listAvailablePlugins() {
1471
+ const results = [];
1472
+ const marketplaces = this.listMarketplaces();
1473
+ for (const { name } of marketplaces) {
1474
+ try {
1475
+ const manifest = this.fetchManifest(name);
1476
+ for (const plugin of manifest.plugins) {
1477
+ results.push({ ...plugin, marketplace: name });
1478
+ }
1479
+ } catch {
1480
+ }
1481
+ }
1482
+ return results;
1483
+ }
1484
+ // --- Private helpers ---
1485
+ /** Resolve a marketplace source to a git clone URL. */
1486
+ resolveCloneUrl(source) {
1487
+ switch (source.type) {
1488
+ case "github":
1489
+ return `https://github.com/${source.repo}.git`;
1490
+ case "git":
1491
+ return source.url;
1492
+ case "local":
1493
+ throw new Error("Local source type does not use git cloning");
1494
+ case "url":
1495
+ throw new Error("URL marketplace source is not yet supported");
1496
+ }
1497
+ }
1498
+ /**
1499
+ * Remove all installed plugins that belong to a given marketplace.
1500
+ * Reads installed_plugins.json, deletes cache directories for matching plugins,
1501
+ * and updates the registry.
1502
+ */
1503
+ removeInstalledPluginsForMarketplace(marketplaceName) {
1504
+ const installedPath = (0, import_node_path5.join)(this.pluginsDir, "installed_plugins.json");
1505
+ if (!(0, import_node_fs4.existsSync)(installedPath)) return;
1506
+ let registry;
1507
+ try {
1508
+ const raw = (0, import_node_fs4.readFileSync)(installedPath, "utf-8");
1509
+ const data = JSON.parse(raw);
1510
+ if (typeof data !== "object" || data === null) return;
1511
+ registry = data;
1512
+ } catch {
1513
+ return;
1514
+ }
1515
+ let changed = false;
1516
+ for (const [pluginId, record] of Object.entries(registry)) {
1517
+ if (record.marketplace === marketplaceName) {
1518
+ if (record.installPath && (0, import_node_fs4.existsSync)(record.installPath)) {
1519
+ (0, import_node_fs4.rmSync)(record.installPath, { recursive: true, force: true });
1520
+ }
1521
+ delete registry[pluginId];
1522
+ changed = true;
1523
+ }
1524
+ }
1525
+ if (changed) {
1526
+ const dir = (0, import_node_path5.dirname)(installedPath);
1527
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1528
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1529
+ }
1530
+ (0, import_node_fs4.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
1531
+ }
1532
+ }
1533
+ /** Read and parse a marketplace.json from a file path. */
1534
+ readManifestFromPath(path) {
1535
+ const raw = (0, import_node_fs4.readFileSync)(path, "utf-8");
1536
+ const data = JSON.parse(raw);
1537
+ if (typeof data !== "object" || data === null) {
1538
+ throw new Error("Invalid marketplace manifest: not an object");
1539
+ }
1540
+ const obj = data;
1541
+ if (typeof obj.name !== "string") {
1542
+ throw new Error('Invalid marketplace manifest: missing "name" field');
1543
+ }
1544
+ return data;
1545
+ }
1546
+ /** Read the known_marketplaces.json registry. */
1547
+ readRegistry() {
1548
+ if (!(0, import_node_fs4.existsSync)(this.registryPath)) {
1549
+ return {};
1550
+ }
1551
+ try {
1552
+ const raw = (0, import_node_fs4.readFileSync)(this.registryPath, "utf-8");
1553
+ const data = JSON.parse(raw);
1554
+ if (typeof data === "object" && data !== null) {
1555
+ return data;
1556
+ }
1557
+ return {};
1558
+ } catch {
1559
+ return {};
1560
+ }
1561
+ }
1562
+ /** Write the known_marketplaces.json registry. */
1563
+ writeRegistry(registry) {
1564
+ const dir = (0, import_node_path5.dirname)(this.registryPath);
1565
+ if (!(0, import_node_fs4.existsSync)(dir)) {
1566
+ (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
1567
+ }
1568
+ (0, import_node_fs4.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1569
+ }
1570
+ /** Default exec implementation using child_process. */
1571
+ defaultExec(command, options) {
1572
+ return (0, import_node_child_process2.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
1573
+ }
1574
+ };
509
1575
 
510
1576
  // src/tools/agent-tool.ts
511
1577
  var import_zod2 = require("zod");
512
- var import_agent_tools7 = require("@robota-sdk/agent-tools");
513
- var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
1578
+ var import_agent_tools2 = require("@robota-sdk/agent-tools");
514
1579
  function asZodSchema(schema) {
515
1580
  return schema;
516
1581
  }
@@ -531,26 +1596,26 @@ async function runAgent(args) {
531
1596
  };
532
1597
  return JSON.stringify(result);
533
1598
  }
534
- const subSession = new import_agent_sessions4.Session({
1599
+ const noopTerminal = {
1600
+ write: () => {
1601
+ },
1602
+ writeLine: () => {
1603
+ },
1604
+ writeMarkdown: () => {
1605
+ },
1606
+ writeError: () => {
1607
+ },
1608
+ prompt: () => Promise.resolve(""),
1609
+ select: () => Promise.resolve(0),
1610
+ spinner: () => ({ stop: () => {
1611
+ }, update: () => {
1612
+ } })
1613
+ };
1614
+ const subSession = createSession({
535
1615
  config: agentToolDeps.config,
536
1616
  context: agentToolDeps.context,
537
1617
  projectInfo: agentToolDeps.projectInfo,
538
- // No terminal needed — sub-agents don't prompt for permissions
539
- terminal: {
540
- write: () => {
541
- },
542
- writeLine: () => {
543
- },
544
- writeMarkdown: () => {
545
- },
546
- writeError: () => {
547
- },
548
- prompt: () => Promise.resolve(""),
549
- select: () => Promise.resolve(0),
550
- spinner: () => ({ stop: () => {
551
- }, update: () => {
552
- } })
553
- },
1618
+ terminal: noopTerminal,
554
1619
  // Sub-agents bypass permissions — they inherit parent's trust
555
1620
  permissionMode: "bypassPermissions"
556
1621
  });
@@ -571,7 +1636,7 @@ async function runAgent(args) {
571
1636
  return JSON.stringify(result);
572
1637
  }
573
1638
  }
574
- var agentTool = (0, import_agent_tools7.createZodFunctionTool)(
1639
+ var agentTool = (0, import_agent_tools2.createZodFunctionTool)(
575
1640
  "Agent",
576
1641
  "Spawn a sub-agent with isolated context to handle a task. The sub-agent has its own conversation history and can use all tools.",
577
1642
  asZodSchema(AgentSchema),
@@ -579,9 +1644,24 @@ var agentTool = (0, import_agent_tools7.createZodFunctionTool)(
579
1644
  return runAgent(params);
580
1645
  }
581
1646
  );
1647
+
1648
+ // src/index.ts
1649
+ var import_agent_tools3 = require("@robota-sdk/agent-tools");
1650
+ var import_agent_tools4 = require("@robota-sdk/agent-tools");
1651
+ var import_agent_tools5 = require("@robota-sdk/agent-tools");
1652
+ var import_agent_tools6 = require("@robota-sdk/agent-tools");
1653
+ var import_agent_tools7 = require("@robota-sdk/agent-tools");
1654
+ var import_agent_tools8 = require("@robota-sdk/agent-tools");
582
1655
  // Annotate the CommonJS export names for ESM import in node:
583
1656
  0 && (module.exports = {
1657
+ AgentExecutor,
1658
+ BundlePluginInstaller,
1659
+ BundlePluginLoader,
1660
+ DEFAULT_TOOL_DESCRIPTIONS,
584
1661
  FileSessionLogger,
1662
+ MarketplaceClient,
1663
+ PluginSettingsStore,
1664
+ PromptExecutor,
585
1665
  Session,
586
1666
  SessionStore,
587
1667
  SilentSessionLogger,
@@ -589,6 +1669,9 @@ var agentTool = (0, import_agent_tools7.createZodFunctionTool)(
589
1669
  agentTool,
590
1670
  bashTool,
591
1671
  buildSystemPrompt,
1672
+ createDefaultTools,
1673
+ createProvider,
1674
+ createSession,
592
1675
  detectProject,
593
1676
  editTool,
594
1677
  evaluatePermission,