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

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.
@@ -70,6 +70,386 @@ __export(index_exports, {
70
70
  });
71
71
  module.exports = __toCommonJS(index_exports);
72
72
 
73
+ // src/interactive/interactive-session.ts
74
+ var import_agent_core = require("@robota-sdk/agent-core");
75
+
76
+ // src/commands/system-command.ts
77
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
78
+ function createSystemCommands() {
79
+ return [
80
+ {
81
+ name: "help",
82
+ description: "Show available commands",
83
+ execute: (_session, _args) => ({
84
+ message: [
85
+ "Available commands:",
86
+ " help \u2014 Show this help",
87
+ " clear \u2014 Clear conversation",
88
+ " compact [instr] \u2014 Compact context (optional focus instructions)",
89
+ " mode [m] \u2014 Show/change permission mode",
90
+ " model <id> \u2014 Change AI model",
91
+ " language <code> \u2014 Set response language (ko, en, ja, zh)",
92
+ " cost \u2014 Show session info",
93
+ " context \u2014 Context window info",
94
+ " permissions \u2014 Permission rules",
95
+ " resume \u2014 Resume a previous session",
96
+ " rename <name> \u2014 Rename the current session",
97
+ " reset \u2014 Delete settings and exit"
98
+ ].join("\n"),
99
+ success: true
100
+ })
101
+ },
102
+ {
103
+ name: "clear",
104
+ description: "Clear conversation history",
105
+ execute: (session, _args) => {
106
+ const underlying = session.getSession();
107
+ underlying.clearHistory();
108
+ return { message: "Conversation cleared.", success: true };
109
+ }
110
+ },
111
+ {
112
+ name: "compact",
113
+ description: "Compress context window",
114
+ execute: async (session, args) => {
115
+ const underlying = session.getSession();
116
+ const instructions = args.trim() || void 0;
117
+ const before = underlying.getContextState().usedPercentage;
118
+ await underlying.compact(instructions);
119
+ const after = underlying.getContextState().usedPercentage;
120
+ return {
121
+ message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
122
+ success: true,
123
+ data: { before, after }
124
+ };
125
+ }
126
+ },
127
+ {
128
+ name: "mode",
129
+ description: "Show/change permission mode",
130
+ execute: (session, args) => {
131
+ const underlying = session.getSession();
132
+ const arg = args.trim().split(/\s+/)[0];
133
+ if (!arg) {
134
+ return {
135
+ message: `Current mode: ${underlying.getPermissionMode()}`,
136
+ success: true,
137
+ data: { mode: underlying.getPermissionMode() }
138
+ };
139
+ }
140
+ if (VALID_MODES.includes(arg)) {
141
+ underlying.setPermissionMode(arg);
142
+ return {
143
+ message: `Permission mode set to: ${arg}`,
144
+ success: true,
145
+ data: { mode: arg }
146
+ };
147
+ }
148
+ return {
149
+ message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
150
+ success: false
151
+ };
152
+ }
153
+ },
154
+ {
155
+ name: "model",
156
+ description: "Change AI model",
157
+ execute: (_session, args) => {
158
+ const modelId = args.trim().split(/\s+/)[0];
159
+ if (!modelId) {
160
+ return { message: "Usage: model <model-id>", success: false };
161
+ }
162
+ return {
163
+ message: `Model change requested: ${modelId}`,
164
+ success: true,
165
+ data: { modelId }
166
+ };
167
+ }
168
+ },
169
+ {
170
+ name: "language",
171
+ description: "Set response language",
172
+ execute: (_session, args) => {
173
+ const lang = args.trim().split(/\s+/)[0];
174
+ if (!lang) {
175
+ return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
176
+ }
177
+ return {
178
+ message: `Language set to "${lang}".`,
179
+ success: true,
180
+ data: { language: lang }
181
+ };
182
+ }
183
+ },
184
+ {
185
+ name: "cost",
186
+ description: "Show session info",
187
+ execute: (session, _args) => {
188
+ const underlying = session.getSession();
189
+ const sessionId = underlying.getSessionId();
190
+ const messageCount = underlying.getMessageCount();
191
+ return {
192
+ message: `Session: ${sessionId}
193
+ Messages: ${messageCount}`,
194
+ success: true,
195
+ data: { sessionId, messageCount }
196
+ };
197
+ }
198
+ },
199
+ {
200
+ name: "context",
201
+ description: "Context window info",
202
+ execute: (session, _args) => {
203
+ const ctx = session.getContextState();
204
+ return {
205
+ message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
206
+ success: true,
207
+ data: {
208
+ usedTokens: ctx.usedTokens,
209
+ maxTokens: ctx.maxTokens,
210
+ percentage: ctx.usedPercentage
211
+ }
212
+ };
213
+ }
214
+ },
215
+ {
216
+ name: "permissions",
217
+ description: "Show permission rules",
218
+ execute: (session, _args) => {
219
+ const underlying = session.getSession();
220
+ const mode = underlying.getPermissionMode();
221
+ const sessionAllowed = underlying.getSessionAllowedTools();
222
+ const lines = [`Permission mode: ${mode}`];
223
+ if (sessionAllowed.length > 0) {
224
+ lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
225
+ } else {
226
+ lines.push("No session-approved tools.");
227
+ }
228
+ return {
229
+ message: lines.join("\n"),
230
+ success: true,
231
+ data: { mode, sessionAllowed }
232
+ };
233
+ }
234
+ },
235
+ {
236
+ name: "resume",
237
+ description: "Resume a previous session",
238
+ execute: (_session, _args) => ({
239
+ message: "Opening session picker...",
240
+ success: true,
241
+ data: { triggerResumePicker: true }
242
+ })
243
+ },
244
+ {
245
+ name: "rename",
246
+ description: "Rename the current session",
247
+ execute: (_session, args) => {
248
+ const name = args.trim();
249
+ if (!name) {
250
+ return { message: "Usage: rename <name>", success: false };
251
+ }
252
+ return {
253
+ message: `Session renamed to "${name}".`,
254
+ success: true,
255
+ data: { name }
256
+ };
257
+ }
258
+ },
259
+ {
260
+ name: "reset",
261
+ description: "Delete settings",
262
+ execute: (_session, _args) => {
263
+ return {
264
+ message: "Reset requested.",
265
+ success: true,
266
+ data: { resetRequested: true }
267
+ };
268
+ }
269
+ }
270
+ ];
271
+ }
272
+ var SystemCommandExecutor = class {
273
+ commands;
274
+ constructor(commands) {
275
+ this.commands = /* @__PURE__ */ new Map();
276
+ for (const cmd of commands ?? createSystemCommands()) {
277
+ this.commands.set(cmd.name, cmd);
278
+ }
279
+ }
280
+ /** Register an additional command. */
281
+ register(command) {
282
+ this.commands.set(command.name, command);
283
+ }
284
+ /** Execute a command by name. Returns null if command not found. */
285
+ async execute(name, session, args) {
286
+ const cmd = this.commands.get(name);
287
+ if (!cmd) return null;
288
+ return await cmd.execute(session, args);
289
+ }
290
+ /** List all registered commands. */
291
+ listCommands() {
292
+ return [...this.commands.values()];
293
+ }
294
+ /** Check if a command exists. */
295
+ hasCommand(name) {
296
+ return this.commands.has(name);
297
+ }
298
+ };
299
+
300
+ // src/interactive/interactive-session-execution.ts
301
+ function isAbortError(err) {
302
+ return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
303
+ }
304
+ function extractToolSummaries(history, historyBefore) {
305
+ const summaries = [];
306
+ for (let i = historyBefore; i < history.length; i++) {
307
+ const msg = history[i];
308
+ if (msg?.role === "assistant" && msg.toolCalls) {
309
+ for (const tc of msg.toolCalls) {
310
+ summaries.push({ name: tc.function.name, args: tc.function.arguments });
311
+ }
312
+ }
313
+ }
314
+ return summaries;
315
+ }
316
+ function buildResult(response, sessionHistory, interactiveHistory, historyBefore, contextState) {
317
+ const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
318
+ return {
319
+ response,
320
+ history: interactiveHistory,
321
+ toolSummaries,
322
+ contextState
323
+ };
324
+ }
325
+ function buildInterruptedResult(sessionHistory, interactiveHistory, historyBefore, contextState) {
326
+ const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
327
+ const parts = [];
328
+ for (let i = historyBefore; i < sessionHistory.length; i++) {
329
+ const msg = sessionHistory[i];
330
+ if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
331
+ }
332
+ return {
333
+ response: parts.join("\n\n"),
334
+ history: interactiveHistory,
335
+ toolSummaries,
336
+ contextState
337
+ };
338
+ }
339
+ function persistSession(sessionStore, session, sessionName, cwd, history) {
340
+ try {
341
+ const sessionId = session.getSessionId();
342
+ const existing = sessionStore.load(sessionId);
343
+ sessionStore.save({
344
+ id: sessionId,
345
+ name: sessionName ?? existing?.name,
346
+ cwd,
347
+ createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
348
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
349
+ messages: session.getHistory(),
350
+ history
351
+ });
352
+ } catch {
353
+ }
354
+ }
355
+ var NOOP_TERMINAL = {
356
+ write: () => {
357
+ },
358
+ writeLine: () => {
359
+ },
360
+ writeMarkdown: () => {
361
+ },
362
+ writeError: () => {
363
+ },
364
+ prompt: () => Promise.resolve(""),
365
+ select: () => Promise.resolve(0),
366
+ spinner: () => ({ stop: () => {
367
+ }, update: () => {
368
+ } })
369
+ };
370
+
371
+ // src/interactive/interactive-session-streaming.ts
372
+ var import_node_crypto = require("crypto");
373
+ var TOOL_ARG_DISPLAY_MAX = 80;
374
+ var TAIL_KEEP = 30;
375
+ var MAX_COMPLETED_TOOLS = 50;
376
+ var STREAMING_FLUSH_INTERVAL_MS = 16;
377
+ function extractFirstArg(toolArgs) {
378
+ if (!toolArgs) return "";
379
+ const firstVal = Object.values(toolArgs)[0];
380
+ const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
381
+ return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
382
+ }
383
+ function pushToolSummaryToHistory(state) {
384
+ if (state.activeTools.length === 0) return;
385
+ const summary = state.activeTools.map((t) => {
386
+ const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
387
+ return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
388
+ }).join("\n");
389
+ state.history.push({
390
+ id: (0, import_node_crypto.randomUUID)(),
391
+ timestamp: /* @__PURE__ */ new Date(),
392
+ category: "event",
393
+ type: "tool-summary",
394
+ data: {
395
+ tools: state.activeTools.map((t) => ({
396
+ toolName: t.toolName,
397
+ firstArg: t.firstArg,
398
+ isRunning: t.isRunning,
399
+ result: t.result
400
+ })),
401
+ summary
402
+ }
403
+ });
404
+ }
405
+ function trimCompletedTools(activeTools) {
406
+ const completed = activeTools.filter((t) => !t.isRunning);
407
+ if (completed.length <= MAX_COMPLETED_TOOLS) return activeTools;
408
+ const excess = completed.length - MAX_COMPLETED_TOOLS;
409
+ let removed = 0;
410
+ return activeTools.filter((t) => {
411
+ if (!t.isRunning && removed < excess) {
412
+ removed++;
413
+ return false;
414
+ }
415
+ return true;
416
+ });
417
+ }
418
+ function applyToolStart(state, event) {
419
+ const firstArg = extractFirstArg(event.toolArgs);
420
+ const toolState = { toolName: event.toolName, firstArg, isRunning: true };
421
+ state.activeTools.push(toolState);
422
+ state.history.push({
423
+ id: (0, import_node_crypto.randomUUID)(),
424
+ timestamp: /* @__PURE__ */ new Date(),
425
+ category: "event",
426
+ type: "tool-start",
427
+ data: { toolName: event.toolName, firstArg, isRunning: true }
428
+ });
429
+ return toolState;
430
+ }
431
+ function applyToolEnd(state, event) {
432
+ const result = event.denied ? "denied" : event.success === false ? "error" : "success";
433
+ const idx = state.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
434
+ if (idx === -1) return null;
435
+ const finished = { ...state.activeTools[idx], isRunning: false, result };
436
+ state.activeTools[idx] = finished;
437
+ state.activeTools = trimCompletedTools(state.activeTools);
438
+ state.history.push({
439
+ id: (0, import_node_crypto.randomUUID)(),
440
+ timestamp: /* @__PURE__ */ new Date(),
441
+ category: "event",
442
+ type: "tool-end",
443
+ data: {
444
+ toolName: finished.toolName,
445
+ firstArg: finished.firstArg,
446
+ isRunning: false,
447
+ result
448
+ }
449
+ });
450
+ return finished;
451
+ }
452
+
73
453
  // src/hooks/prompt-executor.ts
74
454
  function extractJson(raw) {
75
455
  const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
@@ -740,7 +1120,7 @@ function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
740
1120
  return (0, import_node_path2.join)(baseLogsDir, parentSessionId, "subagents");
741
1121
  }
742
1122
 
743
- // src/interactive/interactive-session.ts
1123
+ // src/interactive/interactive-session-init.ts
744
1124
  var import_agent_sessions4 = require("@robota-sdk/agent-sessions");
745
1125
 
746
1126
  // src/paths.ts
@@ -1050,278 +1430,50 @@ function detectPackageManager(cwd) {
1050
1430
  return "bun";
1051
1431
  }
1052
1432
  if ((0, import_fs3.existsSync)((0, import_path3.join)(cwd, "package-lock.json"))) {
1053
- return "npm";
1054
- }
1055
- return void 0;
1056
- }
1057
- async function detectProject(cwd) {
1058
- const pkgJsonPath = (0, import_path3.join)(cwd, "package.json");
1059
- const tsconfigPath = (0, import_path3.join)(cwd, "tsconfig.json");
1060
- const pyprojectPath = (0, import_path3.join)(cwd, "pyproject.toml");
1061
- const cargoPath = (0, import_path3.join)(cwd, "Cargo.toml");
1062
- const goModPath = (0, import_path3.join)(cwd, "go.mod");
1063
- if ((0, import_fs3.existsSync)(pkgJsonPath)) {
1064
- const pkgJson = tryReadJson(pkgJsonPath);
1065
- const language = (0, import_fs3.existsSync)(tsconfigPath) ? "typescript" : "javascript";
1066
- const packageManager = detectPackageManager(cwd);
1067
- return {
1068
- type: "node",
1069
- name: pkgJson?.name,
1070
- packageManager,
1071
- language
1072
- };
1073
- }
1074
- if ((0, import_fs3.existsSync)(pyprojectPath) || (0, import_fs3.existsSync)((0, import_path3.join)(cwd, "setup.py"))) {
1075
- return {
1076
- type: "python",
1077
- language: "python"
1078
- };
1079
- }
1080
- if ((0, import_fs3.existsSync)(cargoPath)) {
1081
- return {
1082
- type: "rust",
1083
- language: "rust"
1084
- };
1085
- }
1086
- if ((0, import_fs3.existsSync)(goModPath)) {
1087
- return {
1088
- type: "go",
1089
- language: "go"
1090
- };
1091
- }
1092
- return {
1093
- type: "unknown",
1094
- language: "unknown"
1095
- };
1096
- }
1097
-
1098
- // src/interactive/interactive-session.ts
1099
- var import_agent_core = require("@robota-sdk/agent-core");
1100
- var import_node_crypto = require("crypto");
1101
-
1102
- // src/commands/system-command.ts
1103
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
1104
- function createSystemCommands() {
1105
- return [
1106
- {
1107
- name: "help",
1108
- description: "Show available commands",
1109
- execute: (_session, _args) => ({
1110
- message: [
1111
- "Available commands:",
1112
- " help \u2014 Show this help",
1113
- " clear \u2014 Clear conversation",
1114
- " compact [instr] \u2014 Compact context (optional focus instructions)",
1115
- " mode [m] \u2014 Show/change permission mode",
1116
- " model <id> \u2014 Change AI model",
1117
- " language <code> \u2014 Set response language (ko, en, ja, zh)",
1118
- " cost \u2014 Show session info",
1119
- " context \u2014 Context window info",
1120
- " permissions \u2014 Permission rules",
1121
- " resume \u2014 Resume a previous session",
1122
- " rename <name> \u2014 Rename the current session",
1123
- " reset \u2014 Delete settings and exit"
1124
- ].join("\n"),
1125
- success: true
1126
- })
1127
- },
1128
- {
1129
- name: "clear",
1130
- description: "Clear conversation history",
1131
- execute: (session, _args) => {
1132
- const underlying = session.getSession();
1133
- underlying.clearHistory();
1134
- return { message: "Conversation cleared.", success: true };
1135
- }
1136
- },
1137
- {
1138
- name: "compact",
1139
- description: "Compress context window",
1140
- execute: async (session, args) => {
1141
- const underlying = session.getSession();
1142
- const instructions = args.trim() || void 0;
1143
- const before = underlying.getContextState().usedPercentage;
1144
- await underlying.compact(instructions);
1145
- const after = underlying.getContextState().usedPercentage;
1146
- return {
1147
- message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
1148
- success: true,
1149
- data: { before, after }
1150
- };
1151
- }
1152
- },
1153
- {
1154
- name: "mode",
1155
- description: "Show/change permission mode",
1156
- execute: (session, args) => {
1157
- const underlying = session.getSession();
1158
- const arg = args.trim().split(/\s+/)[0];
1159
- if (!arg) {
1160
- return {
1161
- message: `Current mode: ${underlying.getPermissionMode()}`,
1162
- success: true,
1163
- data: { mode: underlying.getPermissionMode() }
1164
- };
1165
- }
1166
- if (VALID_MODES.includes(arg)) {
1167
- underlying.setPermissionMode(arg);
1168
- return {
1169
- message: `Permission mode set to: ${arg}`,
1170
- success: true,
1171
- data: { mode: arg }
1172
- };
1173
- }
1174
- return {
1175
- message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
1176
- success: false
1177
- };
1178
- }
1179
- },
1180
- {
1181
- name: "model",
1182
- description: "Change AI model",
1183
- execute: (_session, args) => {
1184
- const modelId = args.trim().split(/\s+/)[0];
1185
- if (!modelId) {
1186
- return { message: "Usage: model <model-id>", success: false };
1187
- }
1188
- return {
1189
- message: `Model change requested: ${modelId}`,
1190
- success: true,
1191
- data: { modelId }
1192
- };
1193
- }
1194
- },
1195
- {
1196
- name: "language",
1197
- description: "Set response language",
1198
- execute: (_session, args) => {
1199
- const lang = args.trim().split(/\s+/)[0];
1200
- if (!lang) {
1201
- return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
1202
- }
1203
- return {
1204
- message: `Language set to "${lang}".`,
1205
- success: true,
1206
- data: { language: lang }
1207
- };
1208
- }
1209
- },
1210
- {
1211
- name: "cost",
1212
- description: "Show session info",
1213
- execute: (session, _args) => {
1214
- const underlying = session.getSession();
1215
- const sessionId = underlying.getSessionId();
1216
- const messageCount = underlying.getMessageCount();
1217
- return {
1218
- message: `Session: ${sessionId}
1219
- Messages: ${messageCount}`,
1220
- success: true,
1221
- data: { sessionId, messageCount }
1222
- };
1223
- }
1224
- },
1225
- {
1226
- name: "context",
1227
- description: "Context window info",
1228
- execute: (session, _args) => {
1229
- const ctx = session.getContextState();
1230
- return {
1231
- message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
1232
- success: true,
1233
- data: {
1234
- usedTokens: ctx.usedTokens,
1235
- maxTokens: ctx.maxTokens,
1236
- percentage: ctx.usedPercentage
1237
- }
1238
- };
1239
- }
1240
- },
1241
- {
1242
- name: "permissions",
1243
- description: "Show permission rules",
1244
- execute: (session, _args) => {
1245
- const underlying = session.getSession();
1246
- const mode = underlying.getPermissionMode();
1247
- const sessionAllowed = underlying.getSessionAllowedTools();
1248
- const lines = [`Permission mode: ${mode}`];
1249
- if (sessionAllowed.length > 0) {
1250
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
1251
- } else {
1252
- lines.push("No session-approved tools.");
1253
- }
1254
- return {
1255
- message: lines.join("\n"),
1256
- success: true,
1257
- data: { mode, sessionAllowed }
1258
- };
1259
- }
1260
- },
1261
- {
1262
- name: "resume",
1263
- description: "Resume a previous session",
1264
- execute: (_session, _args) => ({
1265
- message: "Opening session picker...",
1266
- success: true,
1267
- data: { triggerResumePicker: true }
1268
- })
1269
- },
1270
- {
1271
- name: "rename",
1272
- description: "Rename the current session",
1273
- execute: (_session, args) => {
1274
- const name = args.trim();
1275
- if (!name) {
1276
- return { message: "Usage: rename <name>", success: false };
1277
- }
1278
- return {
1279
- message: `Session renamed to "${name}".`,
1280
- success: true,
1281
- data: { name }
1282
- };
1283
- }
1284
- },
1285
- {
1286
- name: "reset",
1287
- description: "Delete settings",
1288
- execute: (_session, _args) => {
1289
- return {
1290
- message: "Reset requested.",
1291
- success: true,
1292
- data: { resetRequested: true }
1293
- };
1294
- }
1295
- }
1296
- ];
1297
- }
1298
- var SystemCommandExecutor = class {
1299
- commands;
1300
- constructor(commands) {
1301
- this.commands = /* @__PURE__ */ new Map();
1302
- for (const cmd of commands ?? createSystemCommands()) {
1303
- this.commands.set(cmd.name, cmd);
1304
- }
1305
- }
1306
- /** Register an additional command. */
1307
- register(command) {
1308
- this.commands.set(command.name, command);
1433
+ return "npm";
1309
1434
  }
1310
- /** Execute a command by name. Returns null if command not found. */
1311
- async execute(name, session, args) {
1312
- const cmd = this.commands.get(name);
1313
- if (!cmd) return null;
1314
- return await cmd.execute(session, args);
1435
+ return void 0;
1436
+ }
1437
+ async function detectProject(cwd) {
1438
+ const pkgJsonPath = (0, import_path3.join)(cwd, "package.json");
1439
+ const tsconfigPath = (0, import_path3.join)(cwd, "tsconfig.json");
1440
+ const pyprojectPath = (0, import_path3.join)(cwd, "pyproject.toml");
1441
+ const cargoPath = (0, import_path3.join)(cwd, "Cargo.toml");
1442
+ const goModPath = (0, import_path3.join)(cwd, "go.mod");
1443
+ if ((0, import_fs3.existsSync)(pkgJsonPath)) {
1444
+ const pkgJson = tryReadJson(pkgJsonPath);
1445
+ const language = (0, import_fs3.existsSync)(tsconfigPath) ? "typescript" : "javascript";
1446
+ const packageManager = detectPackageManager(cwd);
1447
+ return {
1448
+ type: "node",
1449
+ name: pkgJson?.name,
1450
+ packageManager,
1451
+ language
1452
+ };
1315
1453
  }
1316
- /** List all registered commands. */
1317
- listCommands() {
1318
- return [...this.commands.values()];
1454
+ if ((0, import_fs3.existsSync)(pyprojectPath) || (0, import_fs3.existsSync)((0, import_path3.join)(cwd, "setup.py"))) {
1455
+ return {
1456
+ type: "python",
1457
+ language: "python"
1458
+ };
1319
1459
  }
1320
- /** Check if a command exists. */
1321
- hasCommand(name) {
1322
- return this.commands.has(name);
1460
+ if ((0, import_fs3.existsSync)(cargoPath)) {
1461
+ return {
1462
+ type: "rust",
1463
+ language: "rust"
1464
+ };
1323
1465
  }
1324
- };
1466
+ if ((0, import_fs3.existsSync)(goModPath)) {
1467
+ return {
1468
+ type: "go",
1469
+ language: "go"
1470
+ };
1471
+ }
1472
+ return {
1473
+ type: "unknown",
1474
+ language: "unknown"
1475
+ };
1476
+ }
1325
1477
 
1326
1478
  // src/plugins/plugin-settings-store.ts
1327
1479
  var import_node_fs3 = require("fs");
@@ -1425,8 +1577,11 @@ var PluginSettingsStore = class {
1425
1577
  };
1426
1578
 
1427
1579
  // src/plugins/bundle-plugin-loader.ts
1428
- var import_node_fs4 = require("fs");
1580
+ var import_node_fs5 = require("fs");
1429
1581
  var import_node_path5 = require("path");
1582
+
1583
+ // src/plugins/bundle-plugin-utils.ts
1584
+ var import_node_fs4 = require("fs");
1430
1585
  function parseSkillFrontmatter(raw) {
1431
1586
  const trimmed = raw.trimStart();
1432
1587
  if (!trimmed.startsWith("---")) {
@@ -1483,6 +1638,8 @@ function getSortedSubdirs(dirPath) {
1483
1638
  return [];
1484
1639
  }
1485
1640
  }
1641
+
1642
+ // src/plugins/bundle-plugin-loader.ts
1486
1643
  var BundlePluginLoader = class {
1487
1644
  pluginsDir;
1488
1645
  enabledPlugins;
@@ -1506,7 +1663,7 @@ var BundlePluginLoader = class {
1506
1663
  */
1507
1664
  discoverAndLoad() {
1508
1665
  const cacheDir = (0, import_node_path5.join)(this.pluginsDir, "cache");
1509
- if (!(0, import_node_fs4.existsSync)(cacheDir)) {
1666
+ if (!(0, import_node_fs5.existsSync)(cacheDir)) {
1510
1667
  return [];
1511
1668
  }
1512
1669
  const results = [];
@@ -1521,7 +1678,7 @@ var BundlePluginLoader = class {
1521
1678
  const latestVersion = versions[versions.length - 1];
1522
1679
  const versionDir = (0, import_node_path5.join)(pluginDir, latestVersion);
1523
1680
  const manifestPath = (0, import_node_path5.join)(versionDir, ".claude-plugin", "plugin.json");
1524
- if (!(0, import_node_fs4.existsSync)(manifestPath)) continue;
1681
+ if (!(0, import_node_fs5.existsSync)(manifestPath)) continue;
1525
1682
  const manifest = this.readManifest(manifestPath);
1526
1683
  if (!manifest) continue;
1527
1684
  const pluginId = `${manifest.name}@${marketplace}`;
@@ -1535,7 +1692,7 @@ var BundlePluginLoader = class {
1535
1692
  /** Read and validate a plugin.json manifest. Returns null on failure. */
1536
1693
  readManifest(path) {
1537
1694
  try {
1538
- const raw = (0, import_node_fs4.readFileSync)(path, "utf-8");
1695
+ const raw = (0, import_node_fs5.readFileSync)(path, "utf-8");
1539
1696
  const data = JSON.parse(raw);
1540
1697
  return validateManifest(data);
1541
1698
  } catch {
@@ -1571,14 +1728,14 @@ var BundlePluginLoader = class {
1571
1728
  /** Load skills from the plugin's skills/ directory. */
1572
1729
  loadSkills(pluginDir, pluginName) {
1573
1730
  const skillsDir = (0, import_node_path5.join)(pluginDir, "skills");
1574
- if (!(0, import_node_fs4.existsSync)(skillsDir)) return [];
1575
- const entries = (0, import_node_fs4.readdirSync)(skillsDir, { withFileTypes: true });
1731
+ if (!(0, import_node_fs5.existsSync)(skillsDir)) return [];
1732
+ const entries = (0, import_node_fs5.readdirSync)(skillsDir, { withFileTypes: true });
1576
1733
  const skills = [];
1577
1734
  for (const entry of entries) {
1578
1735
  if (!entry.isDirectory()) continue;
1579
1736
  const skillFile = (0, import_node_path5.join)(skillsDir, entry.name, "SKILL.md");
1580
- if (!(0, import_node_fs4.existsSync)(skillFile)) continue;
1581
- const raw = (0, import_node_fs4.readFileSync)(skillFile, "utf-8");
1737
+ if (!(0, import_node_fs5.existsSync)(skillFile)) continue;
1738
+ const raw = (0, import_node_fs5.readFileSync)(skillFile, "utf-8");
1582
1739
  const { metadata, content } = parseSkillFrontmatter(raw);
1583
1740
  const description = typeof metadata.description === "string" ? metadata.description : "";
1584
1741
  const skill = {
@@ -1594,12 +1751,12 @@ var BundlePluginLoader = class {
1594
1751
  /** Load commands from the plugin's commands/ directory (flat .md files). */
1595
1752
  loadCommands(pluginDir, pluginName) {
1596
1753
  const commandsDir = (0, import_node_path5.join)(pluginDir, "commands");
1597
- if (!(0, import_node_fs4.existsSync)(commandsDir)) return [];
1598
- const entries = (0, import_node_fs4.readdirSync)(commandsDir, { withFileTypes: true });
1754
+ if (!(0, import_node_fs5.existsSync)(commandsDir)) return [];
1755
+ const entries = (0, import_node_fs5.readdirSync)(commandsDir, { withFileTypes: true });
1599
1756
  const commands = [];
1600
1757
  for (const entry of entries) {
1601
1758
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1602
- const raw = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(commandsDir, entry.name), "utf-8");
1759
+ const raw = (0, import_node_fs5.readFileSync)((0, import_node_path5.join)(commandsDir, entry.name), "utf-8");
1603
1760
  const { metadata, content } = parseSkillFrontmatter(raw);
1604
1761
  const name = typeof metadata.name === "string" ? metadata.name : entry.name.replace(/\.md$/, "");
1605
1762
  const description = typeof metadata.description === "string" ? metadata.description : "";
@@ -1615,9 +1772,9 @@ var BundlePluginLoader = class {
1615
1772
  /** Load hooks from hooks/hooks.json if present. */
1616
1773
  loadHooks(pluginDir) {
1617
1774
  const hooksPath = (0, import_node_path5.join)(pluginDir, "hooks", "hooks.json");
1618
- if (!(0, import_node_fs4.existsSync)(hooksPath)) return {};
1775
+ if (!(0, import_node_fs5.existsSync)(hooksPath)) return {};
1619
1776
  try {
1620
- const raw = (0, import_node_fs4.readFileSync)(hooksPath, "utf-8");
1777
+ const raw = (0, import_node_fs5.readFileSync)(hooksPath, "utf-8");
1621
1778
  const data = JSON.parse(raw);
1622
1779
  if (typeof data === "object" && data !== null) {
1623
1780
  return data;
@@ -1631,10 +1788,10 @@ var BundlePluginLoader = class {
1631
1788
  loadMcpConfig(pluginDir) {
1632
1789
  const primaryPath = (0, import_node_path5.join)(pluginDir, ".mcp.json");
1633
1790
  const fallbackPath = (0, import_node_path5.join)(pluginDir, ".claude-plugin", "mcp.json");
1634
- const mcpPath = (0, import_node_fs4.existsSync)(primaryPath) ? primaryPath : fallbackPath;
1635
- if (!(0, import_node_fs4.existsSync)(mcpPath)) return void 0;
1791
+ const mcpPath = (0, import_node_fs5.existsSync)(primaryPath) ? primaryPath : fallbackPath;
1792
+ if (!(0, import_node_fs5.existsSync)(mcpPath)) return void 0;
1636
1793
  try {
1637
- const raw = (0, import_node_fs4.readFileSync)(mcpPath, "utf-8");
1794
+ const raw = (0, import_node_fs5.readFileSync)(mcpPath, "utf-8");
1638
1795
  return JSON.parse(raw);
1639
1796
  } catch {
1640
1797
  return void 0;
@@ -1643,9 +1800,9 @@ var BundlePluginLoader = class {
1643
1800
  /** Load agent definitions from agents/ directory if present. */
1644
1801
  loadAgents(pluginDir) {
1645
1802
  const agentsDir = (0, import_node_path5.join)(pluginDir, "agents");
1646
- if (!(0, import_node_fs4.existsSync)(agentsDir)) return [];
1803
+ if (!(0, import_node_fs5.existsSync)(agentsDir)) return [];
1647
1804
  try {
1648
- const entries = (0, import_node_fs4.readdirSync)(agentsDir, { withFileTypes: true });
1805
+ const entries = (0, import_node_fs5.readdirSync)(agentsDir, { withFileTypes: true });
1649
1806
  return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
1650
1807
  } catch {
1651
1808
  return [];
@@ -1655,7 +1812,7 @@ var BundlePluginLoader = class {
1655
1812
 
1656
1813
  // src/plugins/bundle-plugin-installer.ts
1657
1814
  var import_node_child_process = require("child_process");
1658
- var import_node_fs5 = require("fs");
1815
+ var import_node_fs6 = require("fs");
1659
1816
  var import_node_path6 = require("path");
1660
1817
  var GIT_CLONE_TIMEOUT_MS = 6e4;
1661
1818
  var BundlePluginInstaller = class {
@@ -1689,7 +1846,7 @@ var BundlePluginInstaller = class {
1689
1846
  }
1690
1847
  const version = this.resolveVersion(entry, marketplaceName);
1691
1848
  const targetDir = (0, import_node_path6.join)(this.cacheDir, marketplaceName, pluginName, version);
1692
- if ((0, import_node_fs5.existsSync)(targetDir)) {
1849
+ if ((0, import_node_fs6.existsSync)(targetDir)) {
1693
1850
  throw new Error(
1694
1851
  `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
1695
1852
  );
@@ -1716,8 +1873,8 @@ var BundlePluginInstaller = class {
1716
1873
  if (!record) {
1717
1874
  throw new Error(`Plugin "${pluginId}" is not installed`);
1718
1875
  }
1719
- if ((0, import_node_fs5.existsSync)(record.installPath)) {
1720
- (0, import_node_fs5.rmSync)(record.installPath, { recursive: true, force: true });
1876
+ if ((0, import_node_fs6.existsSync)(record.installPath)) {
1877
+ (0, import_node_fs6.rmSync)(record.installPath, { recursive: true, force: true });
1721
1878
  }
1722
1879
  delete registry[pluginId];
1723
1880
  this.writeRegistry(registry);
@@ -1763,18 +1920,18 @@ var BundlePluginInstaller = class {
1763
1920
  }
1764
1921
  /** Resolve the source and install the plugin. */
1765
1922
  resolveAndInstall(rawSource, marketplaceName, pluginName, targetDir) {
1766
- (0, import_node_fs5.mkdirSync)(targetDir, { recursive: true });
1923
+ (0, import_node_fs6.mkdirSync)(targetDir, { recursive: true });
1767
1924
  const source = this.normalizeSource(rawSource);
1768
1925
  try {
1769
1926
  if (typeof source === "string") {
1770
1927
  const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
1771
1928
  const sourcePath = (0, import_node_path6.join)(marketplaceDir, source);
1772
- if (!(0, import_node_fs5.existsSync)(sourcePath)) {
1929
+ if (!(0, import_node_fs6.existsSync)(sourcePath)) {
1773
1930
  throw new Error(
1774
1931
  `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
1775
1932
  );
1776
1933
  }
1777
- (0, import_node_fs5.cpSync)(sourcePath, targetDir, { recursive: true });
1934
+ (0, import_node_fs6.cpSync)(sourcePath, targetDir, { recursive: true });
1778
1935
  } else if (source.type === "github") {
1779
1936
  const repoUrl = `https://github.com/${source.repo}.git`;
1780
1937
  this.cloneToDir(repoUrl, targetDir, pluginName);
@@ -1786,15 +1943,15 @@ var BundlePluginInstaller = class {
1786
1943
  throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
1787
1944
  }
1788
1945
  } catch (err) {
1789
- if ((0, import_node_fs5.existsSync)(targetDir)) {
1790
- (0, import_node_fs5.rmSync)(targetDir, { recursive: true, force: true });
1946
+ if ((0, import_node_fs6.existsSync)(targetDir)) {
1947
+ (0, import_node_fs6.rmSync)(targetDir, { recursive: true, force: true });
1791
1948
  }
1792
1949
  throw err;
1793
1950
  }
1794
1951
  }
1795
1952
  /** Clone a git repository to the target directory. */
1796
1953
  cloneToDir(repoUrl, targetDir, pluginName) {
1797
- (0, import_node_fs5.rmSync)(targetDir, { recursive: true, force: true });
1954
+ (0, import_node_fs6.rmSync)(targetDir, { recursive: true, force: true });
1798
1955
  const command = `git clone --depth 1 ${repoUrl} ${targetDir}`;
1799
1956
  try {
1800
1957
  this.exec(command, { timeout: GIT_CLONE_TIMEOUT_MS, stdio: "pipe" });
@@ -1805,11 +1962,11 @@ var BundlePluginInstaller = class {
1805
1962
  }
1806
1963
  /** Read the installed_plugins.json registry. */
1807
1964
  readRegistry() {
1808
- if (!(0, import_node_fs5.existsSync)(this.registryPath)) {
1965
+ if (!(0, import_node_fs6.existsSync)(this.registryPath)) {
1809
1966
  return {};
1810
1967
  }
1811
1968
  try {
1812
- const raw = (0, import_node_fs5.readFileSync)(this.registryPath, "utf-8");
1969
+ const raw = (0, import_node_fs6.readFileSync)(this.registryPath, "utf-8");
1813
1970
  const data = JSON.parse(raw);
1814
1971
  if (typeof data === "object" && data !== null) {
1815
1972
  return data;
@@ -1822,10 +1979,10 @@ var BundlePluginInstaller = class {
1822
1979
  /** Write the installed_plugins.json registry. */
1823
1980
  writeRegistry(registry) {
1824
1981
  const dir = (0, import_node_path6.dirname)(this.registryPath);
1825
- if (!(0, import_node_fs5.existsSync)(dir)) {
1826
- (0, import_node_fs5.mkdirSync)(dir, { recursive: true });
1982
+ if (!(0, import_node_fs6.existsSync)(dir)) {
1983
+ (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
1827
1984
  }
1828
- (0, import_node_fs5.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1985
+ (0, import_node_fs6.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
1829
1986
  }
1830
1987
  /** Default exec implementation using child_process. */
1831
1988
  defaultExec(command, options) {
@@ -1835,8 +1992,66 @@ var BundlePluginInstaller = class {
1835
1992
 
1836
1993
  // src/plugins/marketplace-client.ts
1837
1994
  var import_node_child_process2 = require("child_process");
1838
- var import_node_fs6 = require("fs");
1995
+ var import_node_fs8 = require("fs");
1996
+ var import_node_path8 = require("path");
1997
+
1998
+ // src/plugins/marketplace-registry.ts
1999
+ var import_node_fs7 = require("fs");
1839
2000
  var import_node_path7 = require("path");
2001
+ function readRegistry(registryPath) {
2002
+ if (!(0, import_node_fs7.existsSync)(registryPath)) {
2003
+ return {};
2004
+ }
2005
+ try {
2006
+ const raw = (0, import_node_fs7.readFileSync)(registryPath, "utf-8");
2007
+ const data = JSON.parse(raw);
2008
+ if (typeof data === "object" && data !== null) {
2009
+ return data;
2010
+ }
2011
+ return {};
2012
+ } catch {
2013
+ return {};
2014
+ }
2015
+ }
2016
+ function writeRegistry(registryPath, registry) {
2017
+ const dir = (0, import_node_path7.dirname)(registryPath);
2018
+ if (!(0, import_node_fs7.existsSync)(dir)) {
2019
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
2020
+ }
2021
+ (0, import_node_fs7.writeFileSync)(registryPath, JSON.stringify(registry, null, 2), "utf-8");
2022
+ }
2023
+ function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
2024
+ const installedPath = (0, import_node_path7.join)(pluginsDir, "installed_plugins.json");
2025
+ if (!(0, import_node_fs7.existsSync)(installedPath)) return;
2026
+ let registry;
2027
+ try {
2028
+ const raw = (0, import_node_fs7.readFileSync)(installedPath, "utf-8");
2029
+ const data = JSON.parse(raw);
2030
+ if (typeof data !== "object" || data === null) return;
2031
+ registry = data;
2032
+ } catch {
2033
+ return;
2034
+ }
2035
+ let changed = false;
2036
+ for (const [pluginId, record] of Object.entries(registry)) {
2037
+ if (record.marketplace === marketplaceName) {
2038
+ if (record.installPath && (0, import_node_fs7.existsSync)(record.installPath)) {
2039
+ (0, import_node_fs7.rmSync)(record.installPath, { recursive: true, force: true });
2040
+ }
2041
+ delete registry[pluginId];
2042
+ changed = true;
2043
+ }
2044
+ }
2045
+ if (changed) {
2046
+ const dir = (0, import_node_path7.dirname)(installedPath);
2047
+ if (!(0, import_node_fs7.existsSync)(dir)) {
2048
+ (0, import_node_fs7.mkdirSync)(dir, { recursive: true });
2049
+ }
2050
+ (0, import_node_fs7.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
2051
+ }
2052
+ }
2053
+
2054
+ // src/plugins/marketplace-client.ts
1840
2055
  var GIT_TIMEOUT_MS = 6e4;
1841
2056
  var MarketplaceClient = class {
1842
2057
  pluginsDir;
@@ -1846,28 +2061,27 @@ var MarketplaceClient = class {
1846
2061
  constructor(options) {
1847
2062
  this.pluginsDir = options.pluginsDir;
1848
2063
  this.exec = options.exec ?? this.defaultExec;
1849
- this.marketplacesDir = (0, import_node_path7.join)(this.pluginsDir, "marketplaces");
1850
- this.registryPath = (0, import_node_path7.join)(this.pluginsDir, "known_marketplaces.json");
2064
+ this.marketplacesDir = (0, import_node_path8.join)(this.pluginsDir, "marketplaces");
2065
+ this.registryPath = (0, import_node_path8.join)(this.pluginsDir, "known_marketplaces.json");
1851
2066
  }
1852
2067
  /**
1853
2068
  * Add a marketplace by cloning its repository.
1854
2069
  *
1855
- * 1. Parse source: `owner/repo` string becomes a GitHub source.
1856
- * 2. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
1857
- * 3. Read `.claude-plugin/marketplace.json` for the `name` field.
1858
- * 4. Register in `known_marketplaces.json`.
2070
+ * 1. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
2071
+ * 2. Read `.claude-plugin/marketplace.json` for the `name` field.
2072
+ * 3. Register in `known_marketplaces.json`.
1859
2073
  *
1860
2074
  * Returns the registered marketplace name from the manifest.
1861
2075
  */
1862
2076
  addMarketplace(source) {
1863
2077
  const tempName = "temp-" + Date.now().toString(36);
1864
- const tempDir = (0, import_node_path7.join)(this.marketplacesDir, tempName);
1865
- (0, import_node_fs6.mkdirSync)(this.marketplacesDir, { recursive: true });
2078
+ const tempDir = (0, import_node_path8.join)(this.marketplacesDir, tempName);
2079
+ (0, import_node_fs8.mkdirSync)(this.marketplacesDir, { recursive: true });
1866
2080
  if (source.type === "local") {
1867
- if (!(0, import_node_fs6.existsSync)(source.path)) {
2081
+ if (!(0, import_node_fs8.existsSync)(source.path)) {
1868
2082
  throw new Error(`Local marketplace path does not exist: ${source.path}`);
1869
2083
  }
1870
- (0, import_node_fs6.cpSync)(source.path, tempDir, { recursive: true });
2084
+ (0, import_node_fs8.cpSync)(source.path, tempDir, { recursive: true });
1871
2085
  } else {
1872
2086
  const cloneUrl = this.resolveCloneUrl(source);
1873
2087
  const command = `git clone --depth 1 ${cloneUrl} ${tempDir}`;
@@ -1878,9 +2092,9 @@ var MarketplaceClient = class {
1878
2092
  throw new Error(`Failed to clone marketplace: ${message}`);
1879
2093
  }
1880
2094
  }
1881
- const manifestPath = (0, import_node_path7.join)(tempDir, ".claude-plugin", "marketplace.json");
1882
- if (!(0, import_node_fs6.existsSync)(manifestPath)) {
1883
- (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
2095
+ const manifestPath = (0, import_node_path8.join)(tempDir, ".claude-plugin", "marketplace.json");
2096
+ if (!(0, import_node_fs8.existsSync)(manifestPath)) {
2097
+ (0, import_node_fs8.rmSync)(tempDir, { recursive: true, force: true });
1884
2098
  throw new Error(
1885
2099
  source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
1886
2100
  );
@@ -1888,22 +2102,22 @@ var MarketplaceClient = class {
1888
2102
  const manifest = this.readManifestFromPath(manifestPath);
1889
2103
  const name = manifest.name;
1890
2104
  if (!name) {
1891
- (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
2105
+ (0, import_node_fs8.rmSync)(tempDir, { recursive: true, force: true });
1892
2106
  throw new Error('Marketplace manifest does not contain a "name" field');
1893
2107
  }
1894
- const registry = this.readRegistry();
2108
+ const registry = readRegistry(this.registryPath);
1895
2109
  if (registry[name]) {
1896
- (0, import_node_fs6.rmSync)(tempDir, { recursive: true, force: true });
2110
+ (0, import_node_fs8.rmSync)(tempDir, { recursive: true, force: true });
1897
2111
  throw new Error(`Marketplace "${name}" already exists`);
1898
2112
  }
1899
- const finalDir = (0, import_node_path7.join)(this.marketplacesDir, name);
1900
- (0, import_node_fs6.renameSync)(tempDir, finalDir);
2113
+ const finalDir = (0, import_node_path8.join)(this.marketplacesDir, name);
2114
+ (0, import_node_fs8.renameSync)(tempDir, finalDir);
1901
2115
  registry[name] = {
1902
2116
  source,
1903
2117
  installLocation: finalDir,
1904
2118
  lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1905
2119
  };
1906
- this.writeRegistry(registry);
2120
+ writeRegistry(this.registryPath, registry);
1907
2121
  return name;
1908
2122
  }
1909
2123
  /**
@@ -1912,42 +2126,39 @@ var MarketplaceClient = class {
1912
2126
  * and removes from the registry.
1913
2127
  */
1914
2128
  removeMarketplace(name) {
1915
- const registry = this.readRegistry();
2129
+ const registry = readRegistry(this.registryPath);
1916
2130
  const entry = registry[name];
1917
2131
  if (!entry) {
1918
2132
  throw new Error(`Marketplace "${name}" not found`);
1919
2133
  }
1920
- this.removeInstalledPluginsForMarketplace(name);
1921
- if ((0, import_node_fs6.existsSync)(entry.installLocation)) {
1922
- (0, import_node_fs6.rmSync)(entry.installLocation, { recursive: true, force: true });
2134
+ removeInstalledPluginsForMarketplace(this.pluginsDir, name);
2135
+ if ((0, import_node_fs8.existsSync)(entry.installLocation)) {
2136
+ (0, import_node_fs8.rmSync)(entry.installLocation, { recursive: true, force: true });
1923
2137
  }
1924
2138
  delete registry[name];
1925
- this.writeRegistry(registry);
2139
+ writeRegistry(this.registryPath, registry);
1926
2140
  }
1927
2141
  /**
1928
2142
  * Update a marketplace by running git pull on its clone.
1929
2143
  * The manifest is re-read from disk on demand (via fetchManifest), so the
1930
2144
  * updated manifest is automatically available after pull.
1931
- *
1932
- * TODO: After pull, detect version changes in installed plugins and offer
1933
- * to update them (re-install at new version).
1934
2145
  */
1935
2146
  updateMarketplace(name) {
1936
- const registry = this.readRegistry();
2147
+ const registry = readRegistry(this.registryPath);
1937
2148
  const entry = registry[name];
1938
2149
  if (!entry) {
1939
2150
  throw new Error(`Marketplace "${name}" not found`);
1940
2151
  }
1941
- if (!(0, import_node_fs6.existsSync)(entry.installLocation)) {
2152
+ if (!(0, import_node_fs8.existsSync)(entry.installLocation)) {
1942
2153
  throw new Error(`Marketplace directory for "${name}" does not exist`);
1943
2154
  }
1944
2155
  if (entry.source.type === "local") {
1945
2156
  const localSource = entry.source;
1946
- if (!(0, import_node_fs6.existsSync)(localSource.path)) {
2157
+ if (!(0, import_node_fs8.existsSync)(localSource.path)) {
1947
2158
  throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
1948
2159
  }
1949
- (0, import_node_fs6.rmSync)(entry.installLocation, { recursive: true, force: true });
1950
- (0, import_node_fs6.cpSync)(localSource.path, entry.installLocation, { recursive: true });
2160
+ (0, import_node_fs8.rmSync)(entry.installLocation, { recursive: true, force: true });
2161
+ (0, import_node_fs8.cpSync)(localSource.path, entry.installLocation, { recursive: true });
1951
2162
  } else {
1952
2163
  const command = `git -C ${entry.installLocation} pull`;
1953
2164
  try {
@@ -1958,28 +2169,26 @@ var MarketplaceClient = class {
1958
2169
  }
1959
2170
  }
1960
2171
  entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
1961
- this.writeRegistry(registry);
2172
+ writeRegistry(this.registryPath, registry);
1962
2173
  }
1963
2174
  /** List all registered marketplaces. */
1964
2175
  listMarketplaces() {
1965
- const registry = this.readRegistry();
2176
+ const registry = readRegistry(this.registryPath);
1966
2177
  return Object.entries(registry).map(([name, entry]) => ({
1967
2178
  name,
1968
2179
  source: entry.source,
1969
2180
  lastUpdated: entry.lastUpdated
1970
2181
  }));
1971
2182
  }
1972
- /**
1973
- * Read the marketplace manifest from a registered marketplace's clone.
1974
- */
2183
+ /** Read the marketplace manifest from a registered marketplace's clone. */
1975
2184
  fetchManifest(marketplaceName) {
1976
- const registry = this.readRegistry();
2185
+ const registry = readRegistry(this.registryPath);
1977
2186
  const entry = registry[marketplaceName];
1978
2187
  if (!entry) {
1979
2188
  throw new Error(`Marketplace "${marketplaceName}" not found`);
1980
2189
  }
1981
- const manifestPath = (0, import_node_path7.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
1982
- if (!(0, import_node_fs6.existsSync)(manifestPath)) {
2190
+ const manifestPath = (0, import_node_path8.join)(entry.installLocation, ".claude-plugin", "marketplace.json");
2191
+ if (!(0, import_node_fs8.existsSync)(manifestPath)) {
1983
2192
  throw new Error(
1984
2193
  `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
1985
2194
  );
@@ -1988,7 +2197,7 @@ var MarketplaceClient = class {
1988
2197
  }
1989
2198
  /** Get the clone directory path for a registered marketplace. */
1990
2199
  getMarketplaceDir(name) {
1991
- const registry = this.readRegistry();
2200
+ const registry = readRegistry(this.registryPath);
1992
2201
  const entry = registry[name];
1993
2202
  if (!entry) {
1994
2203
  throw new Error(`Marketplace "${name}" not found`);
@@ -2040,44 +2249,9 @@ var MarketplaceClient = class {
2040
2249
  throw new Error("URL marketplace source is not yet supported");
2041
2250
  }
2042
2251
  }
2043
- /**
2044
- * Remove all installed plugins that belong to a given marketplace.
2045
- * Reads installed_plugins.json, deletes cache directories for matching plugins,
2046
- * and updates the registry.
2047
- */
2048
- removeInstalledPluginsForMarketplace(marketplaceName) {
2049
- const installedPath = (0, import_node_path7.join)(this.pluginsDir, "installed_plugins.json");
2050
- if (!(0, import_node_fs6.existsSync)(installedPath)) return;
2051
- let registry;
2052
- try {
2053
- const raw = (0, import_node_fs6.readFileSync)(installedPath, "utf-8");
2054
- const data = JSON.parse(raw);
2055
- if (typeof data !== "object" || data === null) return;
2056
- registry = data;
2057
- } catch {
2058
- return;
2059
- }
2060
- let changed = false;
2061
- for (const [pluginId, record] of Object.entries(registry)) {
2062
- if (record.marketplace === marketplaceName) {
2063
- if (record.installPath && (0, import_node_fs6.existsSync)(record.installPath)) {
2064
- (0, import_node_fs6.rmSync)(record.installPath, { recursive: true, force: true });
2065
- }
2066
- delete registry[pluginId];
2067
- changed = true;
2068
- }
2069
- }
2070
- if (changed) {
2071
- const dir = (0, import_node_path7.dirname)(installedPath);
2072
- if (!(0, import_node_fs6.existsSync)(dir)) {
2073
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
2074
- }
2075
- (0, import_node_fs6.writeFileSync)(installedPath, JSON.stringify(registry, null, 2), "utf-8");
2076
- }
2077
- }
2078
2252
  /** Read and parse a marketplace.json from a file path. */
2079
2253
  readManifestFromPath(path) {
2080
- const raw = (0, import_node_fs6.readFileSync)(path, "utf-8");
2254
+ const raw = (0, import_node_fs8.readFileSync)(path, "utf-8");
2081
2255
  const data = JSON.parse(raw);
2082
2256
  if (typeof data !== "object" || data === null) {
2083
2257
  throw new Error("Invalid marketplace manifest: not an object");
@@ -2088,30 +2262,6 @@ var MarketplaceClient = class {
2088
2262
  }
2089
2263
  return data;
2090
2264
  }
2091
- /** Read the known_marketplaces.json registry. */
2092
- readRegistry() {
2093
- if (!(0, import_node_fs6.existsSync)(this.registryPath)) {
2094
- return {};
2095
- }
2096
- try {
2097
- const raw = (0, import_node_fs6.readFileSync)(this.registryPath, "utf-8");
2098
- const data = JSON.parse(raw);
2099
- if (typeof data === "object" && data !== null) {
2100
- return data;
2101
- }
2102
- return {};
2103
- } catch {
2104
- return {};
2105
- }
2106
- }
2107
- /** Write the known_marketplaces.json registry. */
2108
- writeRegistry(registry) {
2109
- const dir = (0, import_node_path7.dirname)(this.registryPath);
2110
- if (!(0, import_node_fs6.existsSync)(dir)) {
2111
- (0, import_node_fs6.mkdirSync)(dir, { recursive: true });
2112
- }
2113
- (0, import_node_fs6.writeFileSync)(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
2114
- }
2115
2265
  /** Default exec implementation using child_process. */
2116
2266
  defaultExec(command, options) {
2117
2267
  return (0, import_node_child_process2.execSync)(command, { timeout: options.timeout, stdio: "pipe" });
@@ -2119,9 +2269,9 @@ var MarketplaceClient = class {
2119
2269
  };
2120
2270
 
2121
2271
  // src/plugins/plugin-hooks-merger.ts
2122
- var import_node_path8 = require("path");
2272
+ var import_node_path9 = require("path");
2123
2273
  function buildPluginEnv(plugin) {
2124
- const dataDir = (0, import_node_path8.join)((0, import_node_path8.dirname)((0, import_node_path8.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
2274
+ const dataDir = (0, import_node_path9.join)((0, import_node_path9.dirname)((0, import_node_path9.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
2125
2275
  return {
2126
2276
  CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
2127
2277
  CLAUDE_PLUGIN_PATH: plugin.pluginDir,
@@ -2182,41 +2332,111 @@ function mergeHooksIntoConfig(configHooks, pluginHooks) {
2182
2332
  return merged;
2183
2333
  }
2184
2334
 
2185
- // src/interactive/interactive-session.ts
2335
+ // src/interactive/interactive-session-init.ts
2186
2336
  var import_node_os3 = require("os");
2187
- var import_node_path9 = require("path");
2188
- var TOOL_ARG_DISPLAY_MAX = 80;
2189
- var TAIL_KEEP = 30;
2190
- var MAX_COMPLETED_TOOLS = 50;
2191
- var STREAMING_FLUSH_INTERVAL_MS = 16;
2337
+ var import_node_path10 = require("path");
2338
+ async function createInteractiveSession(options) {
2339
+ const cwd = options.cwd;
2340
+ const [config, context, projectInfo] = await Promise.all([
2341
+ loadConfig(cwd),
2342
+ loadContext(cwd),
2343
+ detectProject(cwd)
2344
+ ]);
2345
+ const pluginsDir = (0, import_node_path10.join)((0, import_node_os3.homedir)(), ".robota", "plugins");
2346
+ const pluginLoader = new BundlePluginLoader(pluginsDir);
2347
+ let mergedConfig = config;
2348
+ try {
2349
+ const plugins = pluginLoader.loadPluginsSync();
2350
+ if (plugins.length > 0) {
2351
+ const pluginHooks = mergePluginHooks(plugins);
2352
+ mergedConfig = {
2353
+ ...config,
2354
+ hooks: mergeHooksIntoConfig(
2355
+ config.hooks,
2356
+ pluginHooks
2357
+ )
2358
+ };
2359
+ }
2360
+ } catch {
2361
+ }
2362
+ const paths = projectPaths(cwd);
2363
+ const sessionId = options.resumeSessionId && !options.forkSession ? options.resumeSessionId : void 0;
2364
+ return createSession({
2365
+ config: mergedConfig,
2366
+ context,
2367
+ projectInfo,
2368
+ permissionMode: options.permissionMode,
2369
+ maxTurns: options.maxTurns,
2370
+ terminal: NOOP_TERMINAL,
2371
+ sessionLogger: new import_agent_sessions4.FileSessionLogger(paths.logs),
2372
+ permissionHandler: options.permissionHandler,
2373
+ provider: options.provider,
2374
+ onTextDelta: options.onTextDelta,
2375
+ onToolExecution: options.onToolExecution,
2376
+ sessionId
2377
+ });
2378
+ }
2379
+ function injectSavedMessage(session, msg) {
2380
+ if (!msg || typeof msg !== "object") return;
2381
+ const m = msg;
2382
+ if (!m.role || !m.content) return;
2383
+ const role = m.role;
2384
+ if (role === "tool") {
2385
+ const toolCallId = m.toolCallId ?? "";
2386
+ const name = m.name ?? void 0;
2387
+ session.injectMessage("tool", m.content, { toolCallId, name });
2388
+ } else if (role === "user" || role === "assistant" || role === "system") {
2389
+ session.injectMessage(role, m.content);
2390
+ }
2391
+ }
2392
+ function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingSession) {
2393
+ const record = sessionStore.load(resumeSessionId);
2394
+ if (!record) {
2395
+ return { history: [], sessionName: void 0, pendingRestoreMessages: null };
2396
+ }
2397
+ const history = record.history ?? [];
2398
+ const sessionName = record.name;
2399
+ let pendingRestoreMessages = null;
2400
+ if (!forkSession && record.messages) {
2401
+ if (existingSession) {
2402
+ for (const msg of record.messages) {
2403
+ injectSavedMessage(existingSession, msg);
2404
+ }
2405
+ } else {
2406
+ pendingRestoreMessages = record.messages;
2407
+ }
2408
+ }
2409
+ return { history, sessionName, pendingRestoreMessages };
2410
+ }
2411
+
2412
+ // src/interactive/interactive-session.ts
2192
2413
  var InteractiveSession = class {
2193
2414
  session = null;
2194
2415
  commandExecutor;
2195
2416
  listeners = /* @__PURE__ */ new Map();
2196
2417
  initialized = false;
2197
2418
  initPromise = null;
2198
- // Streaming state
2199
2419
  streamingText = "";
2200
2420
  flushTimer = null;
2201
- // Tool state
2202
2421
  activeTools = [];
2203
- // Execution state
2204
2422
  executing = false;
2205
2423
  pendingPrompt = null;
2206
2424
  pendingDisplayInput;
2207
2425
  pendingRawInput;
2208
- // Full history timeline (chat messages + events)
2209
2426
  history = [];
2210
- // Session persistence
2211
2427
  sessionStore;
2212
2428
  sessionName;
2213
2429
  cwd;
2214
- // Session restore state
2215
2430
  pendingRestoreMessages = null;
2216
2431
  resumeSessionId;
2217
2432
  forkSession;
2218
2433
  constructor(options) {
2219
2434
  this.commandExecutor = new SystemCommandExecutor(createSystemCommands());
2435
+ this.sessionStore = options.sessionStore;
2436
+ this.sessionName = options.sessionName;
2437
+ this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
2438
+ this.resumeSessionId = options.resumeSessionId;
2439
+ this.forkSession = options.forkSession ?? false;
2220
2440
  if ("session" in options && options.session) {
2221
2441
  this.session = options.session;
2222
2442
  this.initialized = true;
@@ -2224,98 +2444,46 @@ var InteractiveSession = class {
2224
2444
  const stdOpts = options;
2225
2445
  this.initPromise = this.initializeAsync(stdOpts);
2226
2446
  }
2227
- this.sessionStore = options.sessionStore;
2228
- this.sessionName = options.sessionName;
2229
- this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
2230
- this.resumeSessionId = options.resumeSessionId;
2231
- this.forkSession = options.forkSession ?? false;
2232
2447
  if (options.resumeSessionId && this.sessionStore) {
2233
- const record = this.sessionStore.load(options.resumeSessionId);
2234
- if (record) {
2235
- this.history = record.history ?? [];
2236
- this.sessionName = record.name;
2237
- if (record.messages) {
2238
- if (this.session) {
2239
- for (const msg of record.messages) {
2240
- const m = msg;
2241
- if (m.role && m.content) {
2242
- this.session.injectMessage(m.role, m.content);
2243
- }
2244
- }
2245
- } else {
2246
- this.pendingRestoreMessages = record.messages;
2247
- }
2248
- }
2249
- }
2448
+ const restored = loadSessionRecord(
2449
+ this.sessionStore,
2450
+ options.resumeSessionId,
2451
+ this.forkSession,
2452
+ this.session
2453
+ );
2454
+ if (restored.history.length > 0) this.history = restored.history;
2455
+ if (restored.sessionName) this.sessionName = restored.sessionName;
2456
+ this.pendingRestoreMessages = restored.pendingRestoreMessages;
2250
2457
  }
2251
2458
  }
2252
2459
  async initializeAsync(options) {
2253
- const cwd = options.cwd;
2254
- const [config, context, projectInfo] = await Promise.all([
2255
- loadConfig(cwd),
2256
- loadContext(cwd),
2257
- detectProject(cwd)
2258
- ]);
2259
- const pluginsDir = (0, import_node_path9.join)((0, import_node_os3.homedir)(), ".robota", "plugins");
2260
- const pluginLoader = new BundlePluginLoader(pluginsDir);
2261
- let mergedConfig = config;
2262
- try {
2263
- const plugins = pluginLoader.loadPluginsSync();
2264
- if (plugins.length > 0) {
2265
- const pluginHooks = mergePluginHooks(plugins);
2266
- mergedConfig = {
2267
- ...config,
2268
- hooks: mergeHooksIntoConfig(
2269
- config.hooks,
2270
- pluginHooks
2271
- )
2272
- };
2273
- }
2274
- } catch {
2275
- }
2276
- const paths = projectPaths(cwd);
2277
- const sessionId = this.resumeSessionId && !this.forkSession ? this.resumeSessionId : void 0;
2278
- this.session = createSession({
2279
- config: mergedConfig,
2280
- context,
2281
- projectInfo,
2460
+ this.session = await createInteractiveSession({
2461
+ cwd: options.cwd,
2462
+ provider: options.provider,
2282
2463
  permissionMode: options.permissionMode,
2283
2464
  maxTurns: options.maxTurns,
2284
- terminal: NOOP_TERMINAL,
2285
- sessionLogger: new import_agent_sessions4.FileSessionLogger(paths.logs),
2286
2465
  permissionHandler: options.permissionHandler,
2287
- provider: options.provider,
2466
+ resumeSessionId: this.resumeSessionId,
2467
+ forkSession: this.forkSession,
2288
2468
  onTextDelta: (delta) => this.handleTextDelta(delta),
2289
- onToolExecution: (event) => this.handleToolExecution(event),
2290
- sessionId
2469
+ onToolExecution: (event) => this.handleToolExecution(event)
2291
2470
  });
2292
2471
  if (this.pendingRestoreMessages) {
2293
- for (const msg of this.pendingRestoreMessages) {
2294
- if (msg && typeof msg === "object" && "role" in msg && "content" in msg) {
2295
- this.session.injectMessage(
2296
- msg.role,
2297
- msg.content
2298
- );
2299
- }
2300
- }
2472
+ for (const msg of this.pendingRestoreMessages) injectSavedMessage(this.session, msg);
2301
2473
  this.pendingRestoreMessages = null;
2302
2474
  }
2303
2475
  this.initialized = true;
2304
2476
  }
2305
2477
  async ensureInitialized() {
2306
- if (this.initialized) return;
2307
- if (this.initPromise) await this.initPromise;
2478
+ if (!this.initialized && this.initPromise) await this.initPromise;
2308
2479
  }
2309
2480
  getSessionOrThrow() {
2310
2481
  if (!this.session)
2311
2482
  throw new Error("InteractiveSession not initialized. Call submit() or await initialization.");
2312
2483
  return this.session;
2313
2484
  }
2314
- // ── Event system ──────────────────────────────────────────────
2315
2485
  on(event, handler) {
2316
- if (!this.listeners.has(event)) {
2317
- this.listeners.set(event, /* @__PURE__ */ new Set());
2318
- }
2486
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
2319
2487
  this.listeners.get(event).add(handler);
2320
2488
  }
2321
2489
  off(event, handler) {
@@ -2323,14 +2491,8 @@ var InteractiveSession = class {
2323
2491
  }
2324
2492
  emit(event, ...args) {
2325
2493
  const handlers = this.listeners.get(event);
2326
- if (handlers) {
2327
- for (const handler of handlers) {
2328
- handler(...args);
2329
- }
2330
- }
2494
+ if (handlers) for (const handler of handlers) handler(...args);
2331
2495
  }
2332
- // ── Public API ────────────────────────────────────────────────
2333
- /** Submit a prompt. Queues if already executing (max 1 queued). */
2334
2496
  async submit(input, displayInput, rawInput) {
2335
2497
  await this.ensureInitialized();
2336
2498
  if (this.executing) {
@@ -2341,27 +2503,24 @@ var InteractiveSession = class {
2341
2503
  }
2342
2504
  await this.executePrompt(input, displayInput, rawInput);
2343
2505
  }
2344
- /** Execute a system command by name. Returns null if not found. */
2345
2506
  async executeCommand(name, args) {
2346
2507
  await this.ensureInitialized();
2347
2508
  return this.commandExecutor.execute(name, this, args);
2348
2509
  }
2349
- /** List all registered system commands. */
2350
2510
  listCommands() {
2351
2511
  return this.commandExecutor.listCommands().map((cmd) => ({
2352
2512
  name: cmd.name,
2353
2513
  description: cmd.description
2354
2514
  }));
2355
2515
  }
2356
- /** Abort current execution and clear queue. */
2357
2516
  abort() {
2358
- this.pendingPrompt = null;
2359
- this.pendingDisplayInput = void 0;
2360
- this.pendingRawInput = void 0;
2517
+ this.clearPendingQueue();
2361
2518
  this.session?.abort();
2362
2519
  }
2363
- /** Cancel queued prompt without aborting current execution. */
2364
2520
  cancelQueue() {
2521
+ this.clearPendingQueue();
2522
+ }
2523
+ clearPendingQueue() {
2365
2524
  this.pendingPrompt = null;
2366
2525
  this.pendingDisplayInput = void 0;
2367
2526
  this.pendingRawInput = void 0;
@@ -2372,11 +2531,9 @@ var InteractiveSession = class {
2372
2531
  getPendingPrompt() {
2373
2532
  return this.pendingPrompt;
2374
2533
  }
2375
- /** Get full history timeline (chat + events) for TUI rendering */
2376
2534
  getFullHistory() {
2377
2535
  return this.history;
2378
2536
  }
2379
- /** Get chat messages only (backward compatible) */
2380
2537
  getMessages() {
2381
2538
  return this.history.filter((e) => e.category === "chat").map((e) => e.data);
2382
2539
  }
@@ -2389,11 +2546,12 @@ var InteractiveSession = class {
2389
2546
  getContextState() {
2390
2547
  return this.getSessionOrThrow().getContextState();
2391
2548
  }
2392
- /** Get session name. */
2393
2549
  getName() {
2394
2550
  return this.sessionName;
2395
2551
  }
2396
- /** Set session name and persist if store is available. */
2552
+ getSession() {
2553
+ return this.getSessionOrThrow();
2554
+ }
2397
2555
  setName(name) {
2398
2556
  this.sessionName = name;
2399
2557
  if (this.sessionStore && this.session) {
@@ -2409,15 +2567,9 @@ var InteractiveSession = class {
2409
2567
  }
2410
2568
  }
2411
2569
  }
2412
- /** Attach a transport adapter to this session. Calls transport.attach(this). */
2413
2570
  attachTransport(transport) {
2414
2571
  transport.attach(this);
2415
2572
  }
2416
- /** Access underlying Session. For advanced use / testing only. */
2417
- getSession() {
2418
- return this.getSessionOrThrow();
2419
- }
2420
- // ── Execution ─────────────────────────────────────────────────
2421
2573
  async executePrompt(input, displayInput, rawInput) {
2422
2574
  this.executing = true;
2423
2575
  this.clearStreaming();
@@ -2427,25 +2579,35 @@ var InteractiveSession = class {
2427
2579
  try {
2428
2580
  const response = await this.getSessionOrThrow().run(input, rawInput);
2429
2581
  this.flushStreaming();
2430
- this.pushToolSummaryMessage();
2582
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2431
2583
  this.clearStreaming();
2432
- const result = this.buildResult(response || "(empty response)", historyBefore);
2584
+ const result = buildResult(
2585
+ response || "(empty response)",
2586
+ this.getSessionOrThrow().getHistory(),
2587
+ this.history,
2588
+ historyBefore,
2589
+ this.getContextState()
2590
+ );
2433
2591
  this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createAssistantMessage)(result.response)));
2434
2592
  this.emit("complete", result);
2435
2593
  this.emit("context_update", this.getContextState());
2436
2594
  } catch (err) {
2437
2595
  this.flushStreaming();
2438
2596
  if (isAbortError(err)) {
2439
- const result = this.buildInterruptedResult(historyBefore);
2440
- this.pushToolSummaryMessage();
2597
+ const result = buildInterruptedResult(
2598
+ this.getSessionOrThrow().getHistory(),
2599
+ this.history,
2600
+ historyBefore,
2601
+ this.getContextState()
2602
+ );
2603
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2441
2604
  this.clearStreaming();
2442
- if (result.response) {
2605
+ if (result.response)
2443
2606
  this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createAssistantMessage)(result.response)));
2444
- }
2445
2607
  this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)("Interrupted by user.")));
2446
2608
  this.emit("interrupted", result);
2447
2609
  } else {
2448
- this.pushToolSummaryMessage();
2610
+ pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
2449
2611
  this.clearStreaming();
2450
2612
  const errMsg = err instanceof Error ? err.message : String(err);
2451
2613
  this.history.push((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)(`Error: ${errMsg}`)));
@@ -2455,33 +2617,23 @@ var InteractiveSession = class {
2455
2617
  this.executing = false;
2456
2618
  this.emit("thinking", false);
2457
2619
  if (this.sessionStore && this.session) {
2458
- try {
2459
- const sessionId = this.getSessionOrThrow().getSessionId();
2460
- const existing = this.sessionStore.load(sessionId);
2461
- this.sessionStore.save({
2462
- id: sessionId,
2463
- name: this.sessionName ?? existing?.name,
2464
- cwd: this.cwd ?? "",
2465
- createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
2466
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2467
- messages: this.getSessionOrThrow().getHistory(),
2468
- history: this.history
2469
- });
2470
- } catch {
2471
- }
2620
+ persistSession(
2621
+ this.sessionStore,
2622
+ this.session,
2623
+ this.sessionName,
2624
+ this.cwd ?? "",
2625
+ this.history
2626
+ );
2472
2627
  }
2473
2628
  if (this.pendingPrompt) {
2474
2629
  const queued = this.pendingPrompt;
2475
2630
  const queuedDisplay = this.pendingDisplayInput;
2476
2631
  const queuedRaw = this.pendingRawInput;
2477
- this.pendingPrompt = null;
2478
- this.pendingDisplayInput = void 0;
2479
- this.pendingRawInput = void 0;
2632
+ this.clearPendingQueue();
2480
2633
  setTimeout(() => this.executePrompt(queued, queuedDisplay, queuedRaw), 0);
2481
2634
  }
2482
2635
  }
2483
2636
  }
2484
- // ── Streaming callbacks ───────────────────────────────────────
2485
2637
  handleTextDelta(delta) {
2486
2638
  this.streamingText += delta;
2487
2639
  this.emit("text_delta", delta);
@@ -2492,67 +2644,17 @@ var InteractiveSession = class {
2492
2644
  }
2493
2645
  }
2494
2646
  handleToolExecution(event) {
2647
+ const streamingState = { activeTools: this.activeTools, history: this.history };
2495
2648
  if (event.type === "start") {
2496
- const firstArg = extractFirstArg(event.toolArgs);
2497
- const state = { toolName: event.toolName, firstArg, isRunning: true };
2498
- this.activeTools.push(state);
2499
- this.emit("tool_start", state);
2500
- this.history.push({
2501
- id: (0, import_node_crypto.randomUUID)(),
2502
- timestamp: /* @__PURE__ */ new Date(),
2503
- category: "event",
2504
- type: "tool-start",
2505
- data: { toolName: event.toolName, firstArg, isRunning: true }
2506
- });
2649
+ const toolState = applyToolStart(streamingState, event);
2650
+ this.activeTools = streamingState.activeTools;
2651
+ this.emit("tool_start", toolState);
2507
2652
  } else {
2508
- const result = event.denied ? "denied" : event.success === false ? "error" : "success";
2509
- const idx = this.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
2510
- if (idx !== -1) {
2511
- const finished = { ...this.activeTools[idx], isRunning: false, result };
2512
- this.activeTools[idx] = finished;
2513
- this.trimCompletedTools();
2514
- this.emit("tool_end", finished);
2515
- this.history.push({
2516
- id: (0, import_node_crypto.randomUUID)(),
2517
- timestamp: /* @__PURE__ */ new Date(),
2518
- category: "event",
2519
- type: "tool-end",
2520
- data: {
2521
- toolName: finished.toolName,
2522
- firstArg: finished.firstArg,
2523
- isRunning: false,
2524
- result
2525
- }
2526
- });
2527
- }
2653
+ const finished = applyToolEnd(streamingState, event);
2654
+ this.activeTools = streamingState.activeTools;
2655
+ if (finished) this.emit("tool_end", finished);
2528
2656
  }
2529
2657
  }
2530
- // ── Helpers ───────────────────────────────────────────────────
2531
- /** Push tool execution summary into messages (before Robota response).
2532
- * Moves tool info from activeTools (real-time display) to messages (permanent display).
2533
- * After this, activeTools will be cleared by clearStreaming(). */
2534
- pushToolSummaryMessage() {
2535
- if (this.activeTools.length === 0) return;
2536
- const summary = this.activeTools.map((t) => {
2537
- const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
2538
- return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
2539
- }).join("\n");
2540
- this.history.push({
2541
- id: (0, import_node_crypto.randomUUID)(),
2542
- timestamp: /* @__PURE__ */ new Date(),
2543
- category: "event",
2544
- type: "tool-summary",
2545
- data: {
2546
- tools: this.activeTools.map((t) => ({
2547
- toolName: t.toolName,
2548
- firstArg: t.firstArg,
2549
- isRunning: t.isRunning,
2550
- result: t.result
2551
- })),
2552
- summary
2553
- }
2554
- });
2555
- }
2556
2658
  clearStreaming() {
2557
2659
  this.streamingText = "";
2558
2660
  this.activeTools = [];
@@ -2567,81 +2669,6 @@ var InteractiveSession = class {
2567
2669
  this.flushTimer = null;
2568
2670
  }
2569
2671
  }
2570
- buildResult(response, historyBefore) {
2571
- const toolSummaries = this.extractToolSummaries(historyBefore);
2572
- return {
2573
- response,
2574
- history: this.history,
2575
- toolSummaries,
2576
- contextState: this.getContextState()
2577
- };
2578
- }
2579
- buildInterruptedResult(historyBefore) {
2580
- const history = this.getSessionOrThrow().getHistory();
2581
- const toolSummaries = this.extractToolSummaries(historyBefore);
2582
- const parts = [];
2583
- for (let i = historyBefore; i < history.length; i++) {
2584
- const msg = history[i];
2585
- if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
2586
- }
2587
- return {
2588
- response: parts.join("\n\n"),
2589
- history: this.history,
2590
- toolSummaries,
2591
- contextState: this.getContextState()
2592
- };
2593
- }
2594
- extractToolSummaries(historyBefore) {
2595
- const history = this.getSessionOrThrow().getHistory();
2596
- const summaries = [];
2597
- for (let i = historyBefore; i < history.length; i++) {
2598
- const msg = history[i];
2599
- if (msg?.role === "assistant" && msg.toolCalls) {
2600
- for (const tc of msg.toolCalls) {
2601
- summaries.push({ name: tc.function.name, args: tc.function.arguments });
2602
- }
2603
- }
2604
- }
2605
- return summaries;
2606
- }
2607
- trimCompletedTools() {
2608
- const completed = this.activeTools.filter((t) => !t.isRunning);
2609
- if (completed.length > MAX_COMPLETED_TOOLS) {
2610
- const excess = completed.length - MAX_COMPLETED_TOOLS;
2611
- let removed = 0;
2612
- this.activeTools = this.activeTools.filter((t) => {
2613
- if (!t.isRunning && removed < excess) {
2614
- removed++;
2615
- return false;
2616
- }
2617
- return true;
2618
- });
2619
- }
2620
- }
2621
- };
2622
- function isAbortError(err) {
2623
- return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
2624
- }
2625
- function extractFirstArg(toolArgs) {
2626
- if (!toolArgs) return "";
2627
- const firstVal = Object.values(toolArgs)[0];
2628
- const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
2629
- return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
2630
- }
2631
- var NOOP_TERMINAL = {
2632
- write: () => {
2633
- },
2634
- writeLine: () => {
2635
- },
2636
- writeMarkdown: () => {
2637
- },
2638
- writeError: () => {
2639
- },
2640
- prompt: () => Promise.resolve(""),
2641
- select: () => Promise.resolve(0),
2642
- spinner: () => ({ stop: () => {
2643
- }, update: () => {
2644
- } })
2645
2672
  };
2646
2673
 
2647
2674
  // src/query.ts
@@ -2796,8 +2823,8 @@ var BuiltinCommandSource = class {
2796
2823
  };
2797
2824
 
2798
2825
  // src/commands/skill-source.ts
2799
- var import_node_fs7 = require("fs");
2800
- var import_node_path10 = require("path");
2826
+ var import_node_fs9 = require("fs");
2827
+ var import_node_path11 = require("path");
2801
2828
  var import_node_os4 = require("os");
2802
2829
  var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
2803
2830
  var LIST_KEYS2 = /* @__PURE__ */ new Set(["allowed-tools"]);
@@ -2845,29 +2872,29 @@ function buildCommand(frontmatter, content, fallbackName) {
2845
2872
  return cmd;
2846
2873
  }
2847
2874
  function scanSkillsDir(skillsDir) {
2848
- if (!(0, import_node_fs7.existsSync)(skillsDir)) return [];
2875
+ if (!(0, import_node_fs9.existsSync)(skillsDir)) return [];
2849
2876
  const commands = [];
2850
- const entries = (0, import_node_fs7.readdirSync)(skillsDir, { withFileTypes: true });
2877
+ const entries = (0, import_node_fs9.readdirSync)(skillsDir, { withFileTypes: true });
2851
2878
  for (const entry of entries) {
2852
2879
  if (!entry.isDirectory()) continue;
2853
- const skillFile = (0, import_node_path10.join)(skillsDir, entry.name, "SKILL.md");
2854
- if (!(0, import_node_fs7.existsSync)(skillFile)) continue;
2855
- const content = (0, import_node_fs7.readFileSync)(skillFile, "utf-8");
2880
+ const skillFile = (0, import_node_path11.join)(skillsDir, entry.name, "SKILL.md");
2881
+ if (!(0, import_node_fs9.existsSync)(skillFile)) continue;
2882
+ const content = (0, import_node_fs9.readFileSync)(skillFile, "utf-8");
2856
2883
  const frontmatter = parseFrontmatter2(content);
2857
2884
  commands.push(buildCommand(frontmatter, content, entry.name));
2858
2885
  }
2859
2886
  return commands;
2860
2887
  }
2861
2888
  function scanCommandsDir(commandsDir) {
2862
- if (!(0, import_node_fs7.existsSync)(commandsDir)) return [];
2889
+ if (!(0, import_node_fs9.existsSync)(commandsDir)) return [];
2863
2890
  const commands = [];
2864
- const entries = (0, import_node_fs7.readdirSync)(commandsDir, { withFileTypes: true });
2891
+ const entries = (0, import_node_fs9.readdirSync)(commandsDir, { withFileTypes: true });
2865
2892
  for (const entry of entries) {
2866
2893
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2867
- const filePath = (0, import_node_path10.join)(commandsDir, entry.name);
2868
- const content = (0, import_node_fs7.readFileSync)(filePath, "utf-8");
2894
+ const filePath = (0, import_node_path11.join)(commandsDir, entry.name);
2895
+ const content = (0, import_node_fs9.readFileSync)(filePath, "utf-8");
2869
2896
  const frontmatter = parseFrontmatter2(content);
2870
- const fallbackName = (0, import_node_path10.basename)(entry.name, ".md");
2897
+ const fallbackName = (0, import_node_path11.basename)(entry.name, ".md");
2871
2898
  commands.push(buildCommand(frontmatter, content, fallbackName));
2872
2899
  }
2873
2900
  return commands;
@@ -2884,10 +2911,10 @@ var SkillCommandSource = class {
2884
2911
  getCommands() {
2885
2912
  if (this.cachedCommands) return this.cachedCommands;
2886
2913
  const sources = [
2887
- scanSkillsDir((0, import_node_path10.join)(this.cwd, ".claude", "skills")),
2888
- scanCommandsDir((0, import_node_path10.join)(this.cwd, ".claude", "commands")),
2889
- scanSkillsDir((0, import_node_path10.join)(this.home, ".robota", "skills")),
2890
- scanSkillsDir((0, import_node_path10.join)(this.cwd, ".agents", "skills"))
2914
+ scanSkillsDir((0, import_node_path11.join)(this.cwd, ".claude", "skills")),
2915
+ scanCommandsDir((0, import_node_path11.join)(this.cwd, ".claude", "commands")),
2916
+ scanSkillsDir((0, import_node_path11.join)(this.home, ".robota", "skills")),
2917
+ scanSkillsDir((0, import_node_path11.join)(this.cwd, ".agents", "skills"))
2891
2918
  ];
2892
2919
  const seen = /* @__PURE__ */ new Set();
2893
2920
  const merged = [];