@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.
- package/dist/node/index.cjs +712 -685
- package/dist/node/index.d.cts +96 -116
- package/dist/node/index.d.ts +96 -116
- package/dist/node/index.js +937 -918
- package/package.json +6 -4
package/dist/node/index.cjs
CHANGED
|
@@ -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
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
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
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
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
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1575
|
-
const entries = (0,
|
|
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,
|
|
1581
|
-
const raw = (0,
|
|
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,
|
|
1598
|
-
const entries = (0,
|
|
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,
|
|
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,
|
|
1775
|
+
if (!(0, import_node_fs5.existsSync)(hooksPath)) return {};
|
|
1619
1776
|
try {
|
|
1620
|
-
const raw = (0,
|
|
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,
|
|
1635
|
-
if (!(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,
|
|
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,
|
|
1803
|
+
if (!(0, import_node_fs5.existsSync)(agentsDir)) return [];
|
|
1647
1804
|
try {
|
|
1648
|
-
const entries = (0,
|
|
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
|
|
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,
|
|
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,
|
|
1720
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1790
|
-
(0,
|
|
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,
|
|
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,
|
|
1965
|
+
if (!(0, import_node_fs6.existsSync)(this.registryPath)) {
|
|
1809
1966
|
return {};
|
|
1810
1967
|
}
|
|
1811
1968
|
try {
|
|
1812
|
-
const raw = (0,
|
|
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,
|
|
1826
|
-
(0,
|
|
1982
|
+
if (!(0, import_node_fs6.existsSync)(dir)) {
|
|
1983
|
+
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
1827
1984
|
}
|
|
1828
|
-
(0,
|
|
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
|
|
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,
|
|
1850
|
-
this.registryPath = (0,
|
|
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.
|
|
1856
|
-
* 2.
|
|
1857
|
-
* 3.
|
|
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,
|
|
1865
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
1882
|
-
if (!(0,
|
|
1883
|
-
(0,
|
|
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,
|
|
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.
|
|
2108
|
+
const registry = readRegistry(this.registryPath);
|
|
1895
2109
|
if (registry[name]) {
|
|
1896
|
-
(0,
|
|
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,
|
|
1900
|
-
(0,
|
|
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.
|
|
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.
|
|
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.
|
|
1921
|
-
if ((0,
|
|
1922
|
-
(0,
|
|
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
1950
|
-
(0,
|
|
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.
|
|
2172
|
+
writeRegistry(this.registryPath, registry);
|
|
1962
2173
|
}
|
|
1963
2174
|
/** List all registered marketplaces. */
|
|
1964
2175
|
listMarketplaces() {
|
|
1965
|
-
const registry = this.
|
|
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.
|
|
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,
|
|
1982
|
-
if (!(0,
|
|
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.
|
|
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,
|
|
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
|
|
2272
|
+
var import_node_path9 = require("path");
|
|
2123
2273
|
function buildPluginEnv(plugin) {
|
|
2124
|
-
const dataDir = (0,
|
|
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
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
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
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
this.
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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
|
-
|
|
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.
|
|
2582
|
+
pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
|
|
2431
2583
|
this.clearStreaming();
|
|
2432
|
-
const result =
|
|
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 =
|
|
2440
|
-
|
|
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.
|
|
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
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
this.
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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.
|
|
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
|
|
2497
|
-
|
|
2498
|
-
this.
|
|
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
|
|
2509
|
-
|
|
2510
|
-
if (
|
|
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
|
|
2800
|
-
var
|
|
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,
|
|
2875
|
+
if (!(0, import_node_fs9.existsSync)(skillsDir)) return [];
|
|
2849
2876
|
const commands = [];
|
|
2850
|
-
const entries = (0,
|
|
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,
|
|
2854
|
-
if (!(0,
|
|
2855
|
-
const content = (0,
|
|
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,
|
|
2889
|
+
if (!(0, import_node_fs9.existsSync)(commandsDir)) return [];
|
|
2863
2890
|
const commands = [];
|
|
2864
|
-
const entries = (0,
|
|
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,
|
|
2868
|
-
const content = (0,
|
|
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,
|
|
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,
|
|
2888
|
-
scanCommandsDir((0,
|
|
2889
|
-
scanSkillsDir((0,
|
|
2890
|
-
scanSkillsDir((0,
|
|
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 = [];
|