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