@robota-sdk/agent-sdk 3.0.0-beta.52 → 3.0.0-beta.53
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 +4 -4
package/dist/node/index.js
CHANGED
|
@@ -1,247 +1,632 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
var PromptExecutor = class {
|
|
10
|
-
type = "prompt";
|
|
11
|
-
providerFactory;
|
|
12
|
-
defaultModel;
|
|
13
|
-
constructor(options) {
|
|
14
|
-
this.providerFactory = options.providerFactory;
|
|
15
|
-
this.defaultModel = options.defaultModel;
|
|
16
|
-
}
|
|
17
|
-
async execute(definition, input) {
|
|
18
|
-
const promptDef = definition;
|
|
19
|
-
const model = promptDef.model ?? this.defaultModel;
|
|
20
|
-
try {
|
|
21
|
-
const provider = this.providerFactory(model);
|
|
22
|
-
const prompt = `${promptDef.prompt}
|
|
23
|
-
|
|
24
|
-
Context:
|
|
25
|
-
${JSON.stringify(input)}
|
|
1
|
+
// src/interactive/interactive-session.ts
|
|
2
|
+
import {
|
|
3
|
+
createUserMessage,
|
|
4
|
+
createAssistantMessage,
|
|
5
|
+
createSystemMessage,
|
|
6
|
+
messageToHistoryEntry
|
|
7
|
+
} from "@robota-sdk/agent-core";
|
|
26
8
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
9
|
+
// src/commands/system-command.ts
|
|
10
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
11
|
+
function createSystemCommands() {
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
name: "help",
|
|
15
|
+
description: "Show available commands",
|
|
16
|
+
execute: (_session, _args) => ({
|
|
17
|
+
message: [
|
|
18
|
+
"Available commands:",
|
|
19
|
+
" help \u2014 Show this help",
|
|
20
|
+
" clear \u2014 Clear conversation",
|
|
21
|
+
" compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
22
|
+
" mode [m] \u2014 Show/change permission mode",
|
|
23
|
+
" model <id> \u2014 Change AI model",
|
|
24
|
+
" language <code> \u2014 Set response language (ko, en, ja, zh)",
|
|
25
|
+
" cost \u2014 Show session info",
|
|
26
|
+
" context \u2014 Context window info",
|
|
27
|
+
" permissions \u2014 Permission rules",
|
|
28
|
+
" resume \u2014 Resume a previous session",
|
|
29
|
+
" rename <name> \u2014 Rename the current session",
|
|
30
|
+
" reset \u2014 Delete settings and exit"
|
|
31
|
+
].join("\n"),
|
|
32
|
+
success: true
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "clear",
|
|
37
|
+
description: "Clear conversation history",
|
|
38
|
+
execute: (session, _args) => {
|
|
39
|
+
const underlying = session.getSession();
|
|
40
|
+
underlying.clearHistory();
|
|
41
|
+
return { message: "Conversation cleared.", success: true };
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "compact",
|
|
46
|
+
description: "Compress context window",
|
|
47
|
+
execute: async (session, args) => {
|
|
48
|
+
const underlying = session.getSession();
|
|
49
|
+
const instructions = args.trim() || void 0;
|
|
50
|
+
const before = underlying.getContextState().usedPercentage;
|
|
51
|
+
await underlying.compact(instructions);
|
|
52
|
+
const after = underlying.getContextState().usedPercentage;
|
|
34
53
|
return {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
|
|
55
|
+
success: true,
|
|
56
|
+
data: { before, after }
|
|
38
57
|
};
|
|
39
58
|
}
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: "mode",
|
|
62
|
+
description: "Show/change permission mode",
|
|
63
|
+
execute: (session, args) => {
|
|
64
|
+
const underlying = session.getSession();
|
|
65
|
+
const arg = args.trim().split(/\s+/)[0];
|
|
66
|
+
if (!arg) {
|
|
67
|
+
return {
|
|
68
|
+
message: `Current mode: ${underlying.getPermissionMode()}`,
|
|
69
|
+
success: true,
|
|
70
|
+
data: { mode: underlying.getPermissionMode() }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (VALID_MODES.includes(arg)) {
|
|
74
|
+
underlying.setPermissionMode(arg);
|
|
75
|
+
return {
|
|
76
|
+
message: `Permission mode set to: ${arg}`,
|
|
77
|
+
success: true,
|
|
78
|
+
data: { mode: arg }
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
|
|
83
|
+
success: false
|
|
84
|
+
};
|
|
42
85
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// src/hooks/agent-executor.ts
|
|
56
|
-
var DEFAULT_MAX_TURNS = 50;
|
|
57
|
-
var DEFAULT_TIMEOUT_SECONDS = 60;
|
|
58
|
-
function extractJson2(raw) {
|
|
59
|
-
const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
|
|
60
|
-
if (codeBlockMatch) {
|
|
61
|
-
return codeBlockMatch[1].trim();
|
|
62
|
-
}
|
|
63
|
-
return raw.trim();
|
|
64
|
-
}
|
|
65
|
-
var AgentExecutor = class {
|
|
66
|
-
type = "agent";
|
|
67
|
-
sessionFactory;
|
|
68
|
-
constructor(options) {
|
|
69
|
-
this.sessionFactory = options.sessionFactory;
|
|
70
|
-
}
|
|
71
|
-
async execute(definition, input) {
|
|
72
|
-
const agentDef = definition;
|
|
73
|
-
const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
74
|
-
const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
75
|
-
try {
|
|
76
|
-
const session = this.sessionFactory({ maxTurns, timeout });
|
|
77
|
-
const prompt = `Hook input:
|
|
78
|
-
${JSON.stringify(input)}
|
|
79
|
-
|
|
80
|
-
Respond with JSON: { "ok": boolean, "reason"?: string }`;
|
|
81
|
-
const rawResponse = await session.run(prompt);
|
|
82
|
-
const jsonStr = extractJson2(rawResponse);
|
|
83
|
-
let parsed;
|
|
84
|
-
try {
|
|
85
|
-
parsed = JSON.parse(jsonStr);
|
|
86
|
-
} catch {
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "model",
|
|
89
|
+
description: "Change AI model",
|
|
90
|
+
execute: (_session, args) => {
|
|
91
|
+
const modelId = args.trim().split(/\s+/)[0];
|
|
92
|
+
if (!modelId) {
|
|
93
|
+
return { message: "Usage: model <model-id>", success: false };
|
|
94
|
+
}
|
|
87
95
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
96
|
+
message: `Model change requested: ${modelId}`,
|
|
97
|
+
success: true,
|
|
98
|
+
data: { modelId }
|
|
91
99
|
};
|
|
92
100
|
}
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "language",
|
|
104
|
+
description: "Set response language",
|
|
105
|
+
execute: (_session, args) => {
|
|
106
|
+
const lang = args.trim().split(/\s+/)[0];
|
|
107
|
+
if (!lang) {
|
|
108
|
+
return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
message: `Language set to "${lang}".`,
|
|
112
|
+
success: true,
|
|
113
|
+
data: { language: lang }
|
|
114
|
+
};
|
|
95
115
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "cost",
|
|
119
|
+
description: "Show session info",
|
|
120
|
+
execute: (session, _args) => {
|
|
121
|
+
const underlying = session.getSession();
|
|
122
|
+
const sessionId = underlying.getSessionId();
|
|
123
|
+
const messageCount = underlying.getMessageCount();
|
|
124
|
+
return {
|
|
125
|
+
message: `Session: ${sessionId}
|
|
126
|
+
Messages: ${messageCount}`,
|
|
127
|
+
success: true,
|
|
128
|
+
data: { sessionId, messageCount }
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "context",
|
|
134
|
+
description: "Context window info",
|
|
135
|
+
execute: (session, _args) => {
|
|
136
|
+
const ctx = session.getContextState();
|
|
137
|
+
return {
|
|
138
|
+
message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
|
|
139
|
+
success: true,
|
|
140
|
+
data: {
|
|
141
|
+
usedTokens: ctx.usedTokens,
|
|
142
|
+
maxTokens: ctx.maxTokens,
|
|
143
|
+
percentage: ctx.usedPercentage
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "permissions",
|
|
150
|
+
description: "Show permission rules",
|
|
151
|
+
execute: (session, _args) => {
|
|
152
|
+
const underlying = session.getSession();
|
|
153
|
+
const mode = underlying.getPermissionMode();
|
|
154
|
+
const sessionAllowed = underlying.getSessionAllowedTools();
|
|
155
|
+
const lines = [`Permission mode: ${mode}`];
|
|
156
|
+
if (sessionAllowed.length > 0) {
|
|
157
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
158
|
+
} else {
|
|
159
|
+
lines.push("No session-approved tools.");
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
message: lines.join("\n"),
|
|
163
|
+
success: true,
|
|
164
|
+
data: { mode, sessionAllowed }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "resume",
|
|
170
|
+
description: "Resume a previous session",
|
|
171
|
+
execute: (_session, _args) => ({
|
|
172
|
+
message: "Opening session picker...",
|
|
173
|
+
success: true,
|
|
174
|
+
data: { triggerResumePicker: true }
|
|
175
|
+
})
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "rename",
|
|
179
|
+
description: "Rename the current session",
|
|
180
|
+
execute: (_session, args) => {
|
|
181
|
+
const name = args.trim();
|
|
182
|
+
if (!name) {
|
|
183
|
+
return { message: "Usage: rename <name>", success: false };
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
message: `Session renamed to "${name}".`,
|
|
187
|
+
success: true,
|
|
188
|
+
data: { name }
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "reset",
|
|
194
|
+
description: "Delete settings",
|
|
195
|
+
execute: (_session, _args) => {
|
|
196
|
+
return {
|
|
197
|
+
message: "Reset requested.",
|
|
198
|
+
success: true,
|
|
199
|
+
data: { resetRequested: true }
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
161
203
|
];
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
204
|
+
}
|
|
205
|
+
var SystemCommandExecutor = class {
|
|
206
|
+
commands;
|
|
207
|
+
constructor(commands) {
|
|
208
|
+
this.commands = /* @__PURE__ */ new Map();
|
|
209
|
+
for (const cmd of commands ?? createSystemCommands()) {
|
|
210
|
+
this.commands.set(cmd.name, cmd);
|
|
211
|
+
}
|
|
166
212
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
\`${cwd}\``);
|
|
213
|
+
/** Register an additional command. */
|
|
214
|
+
register(command) {
|
|
215
|
+
this.commands.set(command.name, command);
|
|
171
216
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
].join("\n")
|
|
178
|
-
);
|
|
179
|
-
if (agentsMd.trim().length > 0) {
|
|
180
|
-
sections.push(["## Agent Instructions", agentsMd].join("\n"));
|
|
217
|
+
/** Execute a command by name. Returns null if command not found. */
|
|
218
|
+
async execute(name, session, args) {
|
|
219
|
+
const cmd = this.commands.get(name);
|
|
220
|
+
if (!cmd) return null;
|
|
221
|
+
return await cmd.execute(session, args);
|
|
181
222
|
}
|
|
182
|
-
|
|
183
|
-
|
|
223
|
+
/** List all registered commands. */
|
|
224
|
+
listCommands() {
|
|
225
|
+
return [...this.commands.values()];
|
|
184
226
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"You have access to web search. When the user asks to search, look up, or find current/latest information,",
|
|
189
|
-
"you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
|
|
190
|
-
"Always prefer web search for: news, latest versions, current events, live documentation."
|
|
191
|
-
].join("\n")
|
|
192
|
-
);
|
|
193
|
-
const toolsSection = buildToolsSection(toolDescriptions);
|
|
194
|
-
if (toolsSection.length > 0) {
|
|
195
|
-
sections.push(toolsSection);
|
|
227
|
+
/** Check if a command exists. */
|
|
228
|
+
hasCommand(name) {
|
|
229
|
+
return this.commands.has(name);
|
|
196
230
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// src/interactive/interactive-session-execution.ts
|
|
234
|
+
function isAbortError(err) {
|
|
235
|
+
return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
|
|
236
|
+
}
|
|
237
|
+
function extractToolSummaries(history, historyBefore) {
|
|
238
|
+
const summaries = [];
|
|
239
|
+
for (let i = historyBefore; i < history.length; i++) {
|
|
240
|
+
const msg = history[i];
|
|
241
|
+
if (msg?.role === "assistant" && msg.toolCalls) {
|
|
242
|
+
for (const tc of msg.toolCalls) {
|
|
243
|
+
summaries.push({ name: tc.function.name, args: tc.function.arguments });
|
|
244
|
+
}
|
|
201
245
|
}
|
|
202
246
|
}
|
|
203
|
-
return
|
|
247
|
+
return summaries;
|
|
204
248
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
249
|
+
function buildResult(response, sessionHistory, interactiveHistory, historyBefore, contextState) {
|
|
250
|
+
const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
|
|
251
|
+
return {
|
|
252
|
+
response,
|
|
253
|
+
history: interactiveHistory,
|
|
254
|
+
toolSummaries,
|
|
255
|
+
contextState
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function buildInterruptedResult(sessionHistory, interactiveHistory, historyBefore, contextState) {
|
|
259
|
+
const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
|
|
260
|
+
const parts = [];
|
|
261
|
+
for (let i = historyBefore; i < sessionHistory.length; i++) {
|
|
262
|
+
const msg = sessionHistory[i];
|
|
263
|
+
if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
response: parts.join("\n\n"),
|
|
267
|
+
history: interactiveHistory,
|
|
268
|
+
toolSummaries,
|
|
269
|
+
contextState
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function persistSession(sessionStore, session, sessionName, cwd, history) {
|
|
273
|
+
try {
|
|
274
|
+
const sessionId = session.getSessionId();
|
|
275
|
+
const existing = sessionStore.load(sessionId);
|
|
276
|
+
sessionStore.save({
|
|
277
|
+
id: sessionId,
|
|
278
|
+
name: sessionName ?? existing?.name,
|
|
279
|
+
cwd,
|
|
280
|
+
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
281
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
282
|
+
messages: session.getHistory(),
|
|
283
|
+
history
|
|
284
|
+
});
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
237
287
|
}
|
|
288
|
+
var NOOP_TERMINAL = {
|
|
289
|
+
write: () => {
|
|
290
|
+
},
|
|
291
|
+
writeLine: () => {
|
|
292
|
+
},
|
|
293
|
+
writeMarkdown: () => {
|
|
294
|
+
},
|
|
295
|
+
writeError: () => {
|
|
296
|
+
},
|
|
297
|
+
prompt: () => Promise.resolve(""),
|
|
298
|
+
select: () => Promise.resolve(0),
|
|
299
|
+
spinner: () => ({ stop: () => {
|
|
300
|
+
}, update: () => {
|
|
301
|
+
} })
|
|
302
|
+
};
|
|
238
303
|
|
|
239
|
-
// src/
|
|
240
|
-
import {
|
|
241
|
-
|
|
304
|
+
// src/interactive/interactive-session-streaming.ts
|
|
305
|
+
import { randomUUID } from "crypto";
|
|
306
|
+
var TOOL_ARG_DISPLAY_MAX = 80;
|
|
307
|
+
var TAIL_KEEP = 30;
|
|
308
|
+
var MAX_COMPLETED_TOOLS = 50;
|
|
309
|
+
var STREAMING_FLUSH_INTERVAL_MS = 16;
|
|
310
|
+
function extractFirstArg(toolArgs) {
|
|
311
|
+
if (!toolArgs) return "";
|
|
312
|
+
const firstVal = Object.values(toolArgs)[0];
|
|
313
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
314
|
+
return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
315
|
+
}
|
|
316
|
+
function pushToolSummaryToHistory(state) {
|
|
317
|
+
if (state.activeTools.length === 0) return;
|
|
318
|
+
const summary = state.activeTools.map((t) => {
|
|
319
|
+
const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
|
|
320
|
+
return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
|
|
321
|
+
}).join("\n");
|
|
322
|
+
state.history.push({
|
|
323
|
+
id: randomUUID(),
|
|
324
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
325
|
+
category: "event",
|
|
326
|
+
type: "tool-summary",
|
|
327
|
+
data: {
|
|
328
|
+
tools: state.activeTools.map((t) => ({
|
|
329
|
+
toolName: t.toolName,
|
|
330
|
+
firstArg: t.firstArg,
|
|
331
|
+
isRunning: t.isRunning,
|
|
332
|
+
result: t.result
|
|
333
|
+
})),
|
|
334
|
+
summary
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function trimCompletedTools(activeTools) {
|
|
339
|
+
const completed = activeTools.filter((t) => !t.isRunning);
|
|
340
|
+
if (completed.length <= MAX_COMPLETED_TOOLS) return activeTools;
|
|
341
|
+
const excess = completed.length - MAX_COMPLETED_TOOLS;
|
|
342
|
+
let removed = 0;
|
|
343
|
+
return activeTools.filter((t) => {
|
|
344
|
+
if (!t.isRunning && removed < excess) {
|
|
345
|
+
removed++;
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function applyToolStart(state, event) {
|
|
352
|
+
const firstArg = extractFirstArg(event.toolArgs);
|
|
353
|
+
const toolState = { toolName: event.toolName, firstArg, isRunning: true };
|
|
354
|
+
state.activeTools.push(toolState);
|
|
355
|
+
state.history.push({
|
|
356
|
+
id: randomUUID(),
|
|
357
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
358
|
+
category: "event",
|
|
359
|
+
type: "tool-start",
|
|
360
|
+
data: { toolName: event.toolName, firstArg, isRunning: true }
|
|
361
|
+
});
|
|
362
|
+
return toolState;
|
|
363
|
+
}
|
|
364
|
+
function applyToolEnd(state, event) {
|
|
365
|
+
const result = event.denied ? "denied" : event.success === false ? "error" : "success";
|
|
366
|
+
const idx = state.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
|
|
367
|
+
if (idx === -1) return null;
|
|
368
|
+
const finished = { ...state.activeTools[idx], isRunning: false, result };
|
|
369
|
+
state.activeTools[idx] = finished;
|
|
370
|
+
state.activeTools = trimCompletedTools(state.activeTools);
|
|
371
|
+
state.history.push({
|
|
372
|
+
id: randomUUID(),
|
|
373
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
374
|
+
category: "event",
|
|
375
|
+
type: "tool-end",
|
|
376
|
+
data: {
|
|
377
|
+
toolName: finished.toolName,
|
|
378
|
+
firstArg: finished.firstArg,
|
|
379
|
+
isRunning: false,
|
|
380
|
+
result
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return finished;
|
|
384
|
+
}
|
|
242
385
|
|
|
243
|
-
// src/
|
|
244
|
-
|
|
386
|
+
// src/hooks/prompt-executor.ts
|
|
387
|
+
function extractJson(raw) {
|
|
388
|
+
const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
|
|
389
|
+
if (codeBlockMatch) {
|
|
390
|
+
return codeBlockMatch[1].trim();
|
|
391
|
+
}
|
|
392
|
+
return raw.trim();
|
|
393
|
+
}
|
|
394
|
+
var PromptExecutor = class {
|
|
395
|
+
type = "prompt";
|
|
396
|
+
providerFactory;
|
|
397
|
+
defaultModel;
|
|
398
|
+
constructor(options) {
|
|
399
|
+
this.providerFactory = options.providerFactory;
|
|
400
|
+
this.defaultModel = options.defaultModel;
|
|
401
|
+
}
|
|
402
|
+
async execute(definition, input) {
|
|
403
|
+
const promptDef = definition;
|
|
404
|
+
const model = promptDef.model ?? this.defaultModel;
|
|
405
|
+
try {
|
|
406
|
+
const provider = this.providerFactory(model);
|
|
407
|
+
const prompt = `${promptDef.prompt}
|
|
408
|
+
|
|
409
|
+
Context:
|
|
410
|
+
${JSON.stringify(input)}
|
|
411
|
+
|
|
412
|
+
Respond with JSON: { "ok": boolean, "reason"?: string }`;
|
|
413
|
+
const rawResponse = await provider.complete(prompt);
|
|
414
|
+
const jsonStr = extractJson(rawResponse);
|
|
415
|
+
let parsed;
|
|
416
|
+
try {
|
|
417
|
+
parsed = JSON.parse(jsonStr);
|
|
418
|
+
} catch {
|
|
419
|
+
return {
|
|
420
|
+
exitCode: 1,
|
|
421
|
+
stdout: "",
|
|
422
|
+
stderr: `Failed to parse AI response as JSON: ${rawResponse}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
if (parsed.ok) {
|
|
426
|
+
return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
exitCode: 2,
|
|
430
|
+
stdout: "",
|
|
431
|
+
stderr: parsed.reason ?? "Blocked by prompt hook"
|
|
432
|
+
};
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
435
|
+
return { exitCode: 1, stdout: "", stderr: message };
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/hooks/agent-executor.ts
|
|
441
|
+
var DEFAULT_MAX_TURNS = 50;
|
|
442
|
+
var DEFAULT_TIMEOUT_SECONDS = 60;
|
|
443
|
+
function extractJson2(raw) {
|
|
444
|
+
const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
|
|
445
|
+
if (codeBlockMatch) {
|
|
446
|
+
return codeBlockMatch[1].trim();
|
|
447
|
+
}
|
|
448
|
+
return raw.trim();
|
|
449
|
+
}
|
|
450
|
+
var AgentExecutor = class {
|
|
451
|
+
type = "agent";
|
|
452
|
+
sessionFactory;
|
|
453
|
+
constructor(options) {
|
|
454
|
+
this.sessionFactory = options.sessionFactory;
|
|
455
|
+
}
|
|
456
|
+
async execute(definition, input) {
|
|
457
|
+
const agentDef = definition;
|
|
458
|
+
const maxTurns = agentDef.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
459
|
+
const timeout = agentDef.timeout ?? DEFAULT_TIMEOUT_SECONDS;
|
|
460
|
+
try {
|
|
461
|
+
const session = this.sessionFactory({ maxTurns, timeout });
|
|
462
|
+
const prompt = `Hook input:
|
|
463
|
+
${JSON.stringify(input)}
|
|
464
|
+
|
|
465
|
+
Respond with JSON: { "ok": boolean, "reason"?: string }`;
|
|
466
|
+
const rawResponse = await session.run(prompt);
|
|
467
|
+
const jsonStr = extractJson2(rawResponse);
|
|
468
|
+
let parsed;
|
|
469
|
+
try {
|
|
470
|
+
parsed = JSON.parse(jsonStr);
|
|
471
|
+
} catch {
|
|
472
|
+
return {
|
|
473
|
+
exitCode: 1,
|
|
474
|
+
stdout: "",
|
|
475
|
+
stderr: `Failed to parse agent response as JSON: ${rawResponse}`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (parsed.ok) {
|
|
479
|
+
return { exitCode: 0, stdout: JSON.stringify(parsed), stderr: "" };
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
exitCode: 2,
|
|
483
|
+
stdout: "",
|
|
484
|
+
stderr: parsed.reason ?? "Blocked by agent hook"
|
|
485
|
+
};
|
|
486
|
+
} catch (err) {
|
|
487
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
488
|
+
return { exitCode: 1, stdout: "", stderr: message };
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
// src/assembly/create-session.ts
|
|
494
|
+
import { Session as Session2 } from "@robota-sdk/agent-sessions";
|
|
495
|
+
|
|
496
|
+
// src/context/system-prompt-builder.ts
|
|
497
|
+
var TRUST_LEVEL_DESCRIPTIONS = {
|
|
498
|
+
safe: "safe (read-only / plan mode \u2014 only read-access tools are available)",
|
|
499
|
+
moderate: "moderate (default mode \u2014 write and bash tools require approval)",
|
|
500
|
+
full: "full (acceptEdits mode \u2014 file writes are auto-approved; bash requires approval)"
|
|
501
|
+
};
|
|
502
|
+
function buildProjectSection(info) {
|
|
503
|
+
const lines = ["## Current Project"];
|
|
504
|
+
if (info.name !== void 0) {
|
|
505
|
+
lines.push(`- **Name:** ${info.name}`);
|
|
506
|
+
}
|
|
507
|
+
if (info.type !== "unknown") {
|
|
508
|
+
lines.push(`- **Type:** ${info.type}`);
|
|
509
|
+
}
|
|
510
|
+
if (info.language !== "unknown") {
|
|
511
|
+
lines.push(`- **Language:** ${info.language}`);
|
|
512
|
+
}
|
|
513
|
+
if (info.packageManager !== void 0) {
|
|
514
|
+
lines.push(`- **Package manager:** ${info.packageManager}`);
|
|
515
|
+
}
|
|
516
|
+
return lines.join("\n");
|
|
517
|
+
}
|
|
518
|
+
function buildToolsSection(descriptions) {
|
|
519
|
+
if (descriptions.length === 0) {
|
|
520
|
+
return "";
|
|
521
|
+
}
|
|
522
|
+
const lines = ["## Available Tools", ...descriptions.map((d) => `- ${d}`)];
|
|
523
|
+
return lines.join("\n");
|
|
524
|
+
}
|
|
525
|
+
function buildSkillsSection(skills) {
|
|
526
|
+
const invocable = skills.filter((s) => s.disableModelInvocation !== true);
|
|
527
|
+
if (invocable.length === 0) {
|
|
528
|
+
return "";
|
|
529
|
+
}
|
|
530
|
+
const lines = [
|
|
531
|
+
"## Skills",
|
|
532
|
+
"The following skills are available:",
|
|
533
|
+
"",
|
|
534
|
+
...invocable.map((s) => `- ${s.name}: ${s.description}`)
|
|
535
|
+
];
|
|
536
|
+
return lines.join("\n");
|
|
537
|
+
}
|
|
538
|
+
function buildSystemPrompt(params) {
|
|
539
|
+
const { agentsMd, claudeMd, toolDescriptions, trustLevel, projectInfo, cwd, language } = params;
|
|
540
|
+
const sections = [];
|
|
541
|
+
const roleLines = [
|
|
542
|
+
"## Role",
|
|
543
|
+
"You are an AI coding assistant with access to tools that let you read and modify code.",
|
|
544
|
+
"You help developers understand, write, and improve their codebase.",
|
|
545
|
+
"Always be precise, follow existing code conventions, and prefer minimal changes."
|
|
546
|
+
];
|
|
547
|
+
if (language) {
|
|
548
|
+
roleLines.push(
|
|
549
|
+
`Always respond in ${language}. Use ${language} for all explanations and communications.`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
sections.push(roleLines.join("\n"));
|
|
553
|
+
if (cwd) {
|
|
554
|
+
sections.push(`## Working Directory
|
|
555
|
+
\`${cwd}\``);
|
|
556
|
+
}
|
|
557
|
+
sections.push(buildProjectSection(projectInfo));
|
|
558
|
+
sections.push(
|
|
559
|
+
[
|
|
560
|
+
"## Permission Mode",
|
|
561
|
+
`Your current trust level is **${TRUST_LEVEL_DESCRIPTIONS[trustLevel]}**.`
|
|
562
|
+
].join("\n")
|
|
563
|
+
);
|
|
564
|
+
if (agentsMd.trim().length > 0) {
|
|
565
|
+
sections.push(["## Agent Instructions", agentsMd].join("\n"));
|
|
566
|
+
}
|
|
567
|
+
if (claudeMd.trim().length > 0) {
|
|
568
|
+
sections.push(["## Project Notes", claudeMd].join("\n"));
|
|
569
|
+
}
|
|
570
|
+
sections.push(
|
|
571
|
+
[
|
|
572
|
+
"## Web Search",
|
|
573
|
+
"You have access to web search. When the user asks to search, look up, or find current/latest information,",
|
|
574
|
+
"you MUST use the web_search tool. Do NOT answer from training data when the user explicitly asks to search.",
|
|
575
|
+
"Always prefer web search for: news, latest versions, current events, live documentation."
|
|
576
|
+
].join("\n")
|
|
577
|
+
);
|
|
578
|
+
const toolsSection = buildToolsSection(toolDescriptions);
|
|
579
|
+
if (toolsSection.length > 0) {
|
|
580
|
+
sections.push(toolsSection);
|
|
581
|
+
}
|
|
582
|
+
if (params.skills !== void 0 && params.skills.length > 0) {
|
|
583
|
+
const skillsSection = buildSkillsSection(params.skills);
|
|
584
|
+
if (skillsSection.length > 0) {
|
|
585
|
+
sections.push(skillsSection);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return sections.join("\n\n");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// src/assembly/create-tools.ts
|
|
592
|
+
import {
|
|
593
|
+
bashTool,
|
|
594
|
+
readTool,
|
|
595
|
+
writeTool,
|
|
596
|
+
editTool,
|
|
597
|
+
globTool,
|
|
598
|
+
grepTool,
|
|
599
|
+
webFetchTool,
|
|
600
|
+
webSearchTool
|
|
601
|
+
} from "@robota-sdk/agent-tools";
|
|
602
|
+
var DEFAULT_TOOL_DESCRIPTIONS = [
|
|
603
|
+
"Bash \u2014 execute shell commands",
|
|
604
|
+
"Read \u2014 read file contents with line numbers",
|
|
605
|
+
"Write \u2014 write content to a file",
|
|
606
|
+
"Edit \u2014 replace a string in a file",
|
|
607
|
+
"Glob \u2014 find files matching a pattern",
|
|
608
|
+
"Grep \u2014 search file contents with regex",
|
|
609
|
+
"WebSearch \u2014 search the internet (Anthropic built-in)"
|
|
610
|
+
];
|
|
611
|
+
function createDefaultTools() {
|
|
612
|
+
return [
|
|
613
|
+
bashTool,
|
|
614
|
+
readTool,
|
|
615
|
+
writeTool,
|
|
616
|
+
editTool,
|
|
617
|
+
globTool,
|
|
618
|
+
grepTool,
|
|
619
|
+
webFetchTool,
|
|
620
|
+
webSearchTool
|
|
621
|
+
];
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// src/tools/agent-tool.ts
|
|
625
|
+
import { z } from "zod";
|
|
626
|
+
import { createZodFunctionTool } from "@robota-sdk/agent-tools";
|
|
627
|
+
|
|
628
|
+
// src/agents/built-in-agents.ts
|
|
629
|
+
var GENERAL_PURPOSE_SYSTEM_PROMPT = `You are a general-purpose task execution agent. You have access to all tools available in the parent session and can perform any task delegated to you.
|
|
245
630
|
|
|
246
631
|
Your role is to complete the assigned task thoroughly and accurately. Follow these guidelines:
|
|
247
632
|
|
|
@@ -677,7 +1062,7 @@ function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
|
|
|
677
1062
|
return join2(baseLogsDir, parentSessionId, "subagents");
|
|
678
1063
|
}
|
|
679
1064
|
|
|
680
|
-
// src/interactive/interactive-session.ts
|
|
1065
|
+
// src/interactive/interactive-session-init.ts
|
|
681
1066
|
import { FileSessionLogger as FileSessionLogger2 } from "@robota-sdk/agent-sessions";
|
|
682
1067
|
|
|
683
1068
|
// src/paths.ts
|
|
@@ -972,298 +1357,65 @@ function tryReadJson(filePath) {
|
|
|
972
1357
|
if (!existsSync4(filePath)) return void 0;
|
|
973
1358
|
try {
|
|
974
1359
|
return JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
975
|
-
} catch {
|
|
976
|
-
return void 0;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
function detectPackageManager(cwd) {
|
|
980
|
-
if (existsSync4(join6(cwd, "pnpm-workspace.yaml")) || existsSync4(join6(cwd, "pnpm-lock.yaml"))) {
|
|
981
|
-
return "pnpm";
|
|
982
|
-
}
|
|
983
|
-
if (existsSync4(join6(cwd, "yarn.lock"))) {
|
|
984
|
-
return "yarn";
|
|
985
|
-
}
|
|
986
|
-
if (existsSync4(join6(cwd, "bun.lockb"))) {
|
|
987
|
-
return "bun";
|
|
988
|
-
}
|
|
989
|
-
if (existsSync4(join6(cwd, "package-lock.json"))) {
|
|
990
|
-
return "npm";
|
|
991
|
-
}
|
|
992
|
-
return void 0;
|
|
993
|
-
}
|
|
994
|
-
async function detectProject(cwd) {
|
|
995
|
-
const pkgJsonPath = join6(cwd, "package.json");
|
|
996
|
-
const tsconfigPath = join6(cwd, "tsconfig.json");
|
|
997
|
-
const pyprojectPath = join6(cwd, "pyproject.toml");
|
|
998
|
-
const cargoPath = join6(cwd, "Cargo.toml");
|
|
999
|
-
const goModPath = join6(cwd, "go.mod");
|
|
1000
|
-
if (existsSync4(pkgJsonPath)) {
|
|
1001
|
-
const pkgJson = tryReadJson(pkgJsonPath);
|
|
1002
|
-
const language = existsSync4(tsconfigPath) ? "typescript" : "javascript";
|
|
1003
|
-
const packageManager = detectPackageManager(cwd);
|
|
1004
|
-
return {
|
|
1005
|
-
type: "node",
|
|
1006
|
-
name: pkgJson?.name,
|
|
1007
|
-
packageManager,
|
|
1008
|
-
language
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
if (existsSync4(pyprojectPath) || existsSync4(join6(cwd, "setup.py"))) {
|
|
1012
|
-
return {
|
|
1013
|
-
type: "python",
|
|
1014
|
-
language: "python"
|
|
1015
|
-
};
|
|
1016
|
-
}
|
|
1017
|
-
if (existsSync4(cargoPath)) {
|
|
1018
|
-
return {
|
|
1019
|
-
type: "rust",
|
|
1020
|
-
language: "rust"
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
if (existsSync4(goModPath)) {
|
|
1024
|
-
return {
|
|
1025
|
-
type: "go",
|
|
1026
|
-
language: "go"
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
return {
|
|
1030
|
-
type: "unknown",
|
|
1031
|
-
language: "unknown"
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// src/interactive/interactive-session.ts
|
|
1036
|
-
import {
|
|
1037
|
-
createUserMessage,
|
|
1038
|
-
createAssistantMessage,
|
|
1039
|
-
createSystemMessage,
|
|
1040
|
-
messageToHistoryEntry
|
|
1041
|
-
} from "@robota-sdk/agent-core";
|
|
1042
|
-
import { randomUUID } from "crypto";
|
|
1043
|
-
|
|
1044
|
-
// src/commands/system-command.ts
|
|
1045
|
-
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
1046
|
-
function createSystemCommands() {
|
|
1047
|
-
return [
|
|
1048
|
-
{
|
|
1049
|
-
name: "help",
|
|
1050
|
-
description: "Show available commands",
|
|
1051
|
-
execute: (_session, _args) => ({
|
|
1052
|
-
message: [
|
|
1053
|
-
"Available commands:",
|
|
1054
|
-
" help \u2014 Show this help",
|
|
1055
|
-
" clear \u2014 Clear conversation",
|
|
1056
|
-
" compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
1057
|
-
" mode [m] \u2014 Show/change permission mode",
|
|
1058
|
-
" model <id> \u2014 Change AI model",
|
|
1059
|
-
" language <code> \u2014 Set response language (ko, en, ja, zh)",
|
|
1060
|
-
" cost \u2014 Show session info",
|
|
1061
|
-
" context \u2014 Context window info",
|
|
1062
|
-
" permissions \u2014 Permission rules",
|
|
1063
|
-
" resume \u2014 Resume a previous session",
|
|
1064
|
-
" rename <name> \u2014 Rename the current session",
|
|
1065
|
-
" reset \u2014 Delete settings and exit"
|
|
1066
|
-
].join("\n"),
|
|
1067
|
-
success: true
|
|
1068
|
-
})
|
|
1069
|
-
},
|
|
1070
|
-
{
|
|
1071
|
-
name: "clear",
|
|
1072
|
-
description: "Clear conversation history",
|
|
1073
|
-
execute: (session, _args) => {
|
|
1074
|
-
const underlying = session.getSession();
|
|
1075
|
-
underlying.clearHistory();
|
|
1076
|
-
return { message: "Conversation cleared.", success: true };
|
|
1077
|
-
}
|
|
1078
|
-
},
|
|
1079
|
-
{
|
|
1080
|
-
name: "compact",
|
|
1081
|
-
description: "Compress context window",
|
|
1082
|
-
execute: async (session, args) => {
|
|
1083
|
-
const underlying = session.getSession();
|
|
1084
|
-
const instructions = args.trim() || void 0;
|
|
1085
|
-
const before = underlying.getContextState().usedPercentage;
|
|
1086
|
-
await underlying.compact(instructions);
|
|
1087
|
-
const after = underlying.getContextState().usedPercentage;
|
|
1088
|
-
return {
|
|
1089
|
-
message: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`,
|
|
1090
|
-
success: true,
|
|
1091
|
-
data: { before, after }
|
|
1092
|
-
};
|
|
1093
|
-
}
|
|
1094
|
-
},
|
|
1095
|
-
{
|
|
1096
|
-
name: "mode",
|
|
1097
|
-
description: "Show/change permission mode",
|
|
1098
|
-
execute: (session, args) => {
|
|
1099
|
-
const underlying = session.getSession();
|
|
1100
|
-
const arg = args.trim().split(/\s+/)[0];
|
|
1101
|
-
if (!arg) {
|
|
1102
|
-
return {
|
|
1103
|
-
message: `Current mode: ${underlying.getPermissionMode()}`,
|
|
1104
|
-
success: true,
|
|
1105
|
-
data: { mode: underlying.getPermissionMode() }
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
if (VALID_MODES.includes(arg)) {
|
|
1109
|
-
underlying.setPermissionMode(arg);
|
|
1110
|
-
return {
|
|
1111
|
-
message: `Permission mode set to: ${arg}`,
|
|
1112
|
-
success: true,
|
|
1113
|
-
data: { mode: arg }
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
return {
|
|
1117
|
-
message: `Invalid mode. Valid: ${VALID_MODES.join(" | ")}`,
|
|
1118
|
-
success: false
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
},
|
|
1122
|
-
{
|
|
1123
|
-
name: "model",
|
|
1124
|
-
description: "Change AI model",
|
|
1125
|
-
execute: (_session, args) => {
|
|
1126
|
-
const modelId = args.trim().split(/\s+/)[0];
|
|
1127
|
-
if (!modelId) {
|
|
1128
|
-
return { message: "Usage: model <model-id>", success: false };
|
|
1129
|
-
}
|
|
1130
|
-
return {
|
|
1131
|
-
message: `Model change requested: ${modelId}`,
|
|
1132
|
-
success: true,
|
|
1133
|
-
data: { modelId }
|
|
1134
|
-
};
|
|
1135
|
-
}
|
|
1136
|
-
},
|
|
1137
|
-
{
|
|
1138
|
-
name: "language",
|
|
1139
|
-
description: "Set response language",
|
|
1140
|
-
execute: (_session, args) => {
|
|
1141
|
-
const lang = args.trim().split(/\s+/)[0];
|
|
1142
|
-
if (!lang) {
|
|
1143
|
-
return { message: "Usage: language <code> (e.g., ko, en, ja, zh)", success: false };
|
|
1144
|
-
}
|
|
1145
|
-
return {
|
|
1146
|
-
message: `Language set to "${lang}".`,
|
|
1147
|
-
success: true,
|
|
1148
|
-
data: { language: lang }
|
|
1149
|
-
};
|
|
1150
|
-
}
|
|
1151
|
-
},
|
|
1152
|
-
{
|
|
1153
|
-
name: "cost",
|
|
1154
|
-
description: "Show session info",
|
|
1155
|
-
execute: (session, _args) => {
|
|
1156
|
-
const underlying = session.getSession();
|
|
1157
|
-
const sessionId = underlying.getSessionId();
|
|
1158
|
-
const messageCount = underlying.getMessageCount();
|
|
1159
|
-
return {
|
|
1160
|
-
message: `Session: ${sessionId}
|
|
1161
|
-
Messages: ${messageCount}`,
|
|
1162
|
-
success: true,
|
|
1163
|
-
data: { sessionId, messageCount }
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
},
|
|
1167
|
-
{
|
|
1168
|
-
name: "context",
|
|
1169
|
-
description: "Context window info",
|
|
1170
|
-
execute: (session, _args) => {
|
|
1171
|
-
const ctx = session.getContextState();
|
|
1172
|
-
return {
|
|
1173
|
-
message: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`,
|
|
1174
|
-
success: true,
|
|
1175
|
-
data: {
|
|
1176
|
-
usedTokens: ctx.usedTokens,
|
|
1177
|
-
maxTokens: ctx.maxTokens,
|
|
1178
|
-
percentage: ctx.usedPercentage
|
|
1179
|
-
}
|
|
1180
|
-
};
|
|
1181
|
-
}
|
|
1182
|
-
},
|
|
1183
|
-
{
|
|
1184
|
-
name: "permissions",
|
|
1185
|
-
description: "Show permission rules",
|
|
1186
|
-
execute: (session, _args) => {
|
|
1187
|
-
const underlying = session.getSession();
|
|
1188
|
-
const mode = underlying.getPermissionMode();
|
|
1189
|
-
const sessionAllowed = underlying.getSessionAllowedTools();
|
|
1190
|
-
const lines = [`Permission mode: ${mode}`];
|
|
1191
|
-
if (sessionAllowed.length > 0) {
|
|
1192
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
1193
|
-
} else {
|
|
1194
|
-
lines.push("No session-approved tools.");
|
|
1195
|
-
}
|
|
1196
|
-
return {
|
|
1197
|
-
message: lines.join("\n"),
|
|
1198
|
-
success: true,
|
|
1199
|
-
data: { mode, sessionAllowed }
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
},
|
|
1203
|
-
{
|
|
1204
|
-
name: "resume",
|
|
1205
|
-
description: "Resume a previous session",
|
|
1206
|
-
execute: (_session, _args) => ({
|
|
1207
|
-
message: "Opening session picker...",
|
|
1208
|
-
success: true,
|
|
1209
|
-
data: { triggerResumePicker: true }
|
|
1210
|
-
})
|
|
1211
|
-
},
|
|
1212
|
-
{
|
|
1213
|
-
name: "rename",
|
|
1214
|
-
description: "Rename the current session",
|
|
1215
|
-
execute: (_session, args) => {
|
|
1216
|
-
const name = args.trim();
|
|
1217
|
-
if (!name) {
|
|
1218
|
-
return { message: "Usage: rename <name>", success: false };
|
|
1219
|
-
}
|
|
1220
|
-
return {
|
|
1221
|
-
message: `Session renamed to "${name}".`,
|
|
1222
|
-
success: true,
|
|
1223
|
-
data: { name }
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
},
|
|
1227
|
-
{
|
|
1228
|
-
name: "reset",
|
|
1229
|
-
description: "Delete settings",
|
|
1230
|
-
execute: (_session, _args) => {
|
|
1231
|
-
return {
|
|
1232
|
-
message: "Reset requested.",
|
|
1233
|
-
success: true,
|
|
1234
|
-
data: { resetRequested: true }
|
|
1235
|
-
};
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
];
|
|
1239
|
-
}
|
|
1240
|
-
var SystemCommandExecutor = class {
|
|
1241
|
-
commands;
|
|
1242
|
-
constructor(commands) {
|
|
1243
|
-
this.commands = /* @__PURE__ */ new Map();
|
|
1244
|
-
for (const cmd of commands ?? createSystemCommands()) {
|
|
1245
|
-
this.commands.set(cmd.name, cmd);
|
|
1246
|
-
}
|
|
1360
|
+
} catch {
|
|
1361
|
+
return void 0;
|
|
1247
1362
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1363
|
+
}
|
|
1364
|
+
function detectPackageManager(cwd) {
|
|
1365
|
+
if (existsSync4(join6(cwd, "pnpm-workspace.yaml")) || existsSync4(join6(cwd, "pnpm-lock.yaml"))) {
|
|
1366
|
+
return "pnpm";
|
|
1251
1367
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
const cmd = this.commands.get(name);
|
|
1255
|
-
if (!cmd) return null;
|
|
1256
|
-
return await cmd.execute(session, args);
|
|
1368
|
+
if (existsSync4(join6(cwd, "yarn.lock"))) {
|
|
1369
|
+
return "yarn";
|
|
1257
1370
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
return [...this.commands.values()];
|
|
1371
|
+
if (existsSync4(join6(cwd, "bun.lockb"))) {
|
|
1372
|
+
return "bun";
|
|
1261
1373
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
return this.commands.has(name);
|
|
1374
|
+
if (existsSync4(join6(cwd, "package-lock.json"))) {
|
|
1375
|
+
return "npm";
|
|
1265
1376
|
}
|
|
1266
|
-
|
|
1377
|
+
return void 0;
|
|
1378
|
+
}
|
|
1379
|
+
async function detectProject(cwd) {
|
|
1380
|
+
const pkgJsonPath = join6(cwd, "package.json");
|
|
1381
|
+
const tsconfigPath = join6(cwd, "tsconfig.json");
|
|
1382
|
+
const pyprojectPath = join6(cwd, "pyproject.toml");
|
|
1383
|
+
const cargoPath = join6(cwd, "Cargo.toml");
|
|
1384
|
+
const goModPath = join6(cwd, "go.mod");
|
|
1385
|
+
if (existsSync4(pkgJsonPath)) {
|
|
1386
|
+
const pkgJson = tryReadJson(pkgJsonPath);
|
|
1387
|
+
const language = existsSync4(tsconfigPath) ? "typescript" : "javascript";
|
|
1388
|
+
const packageManager = detectPackageManager(cwd);
|
|
1389
|
+
return {
|
|
1390
|
+
type: "node",
|
|
1391
|
+
name: pkgJson?.name,
|
|
1392
|
+
packageManager,
|
|
1393
|
+
language
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
if (existsSync4(pyprojectPath) || existsSync4(join6(cwd, "setup.py"))) {
|
|
1397
|
+
return {
|
|
1398
|
+
type: "python",
|
|
1399
|
+
language: "python"
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
if (existsSync4(cargoPath)) {
|
|
1403
|
+
return {
|
|
1404
|
+
type: "rust",
|
|
1405
|
+
language: "rust"
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
if (existsSync4(goModPath)) {
|
|
1409
|
+
return {
|
|
1410
|
+
type: "go",
|
|
1411
|
+
language: "go"
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
type: "unknown",
|
|
1416
|
+
language: "unknown"
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1267
1419
|
|
|
1268
1420
|
// src/plugins/plugin-settings-store.ts
|
|
1269
1421
|
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -1367,8 +1519,11 @@ var PluginSettingsStore = class {
|
|
|
1367
1519
|
};
|
|
1368
1520
|
|
|
1369
1521
|
// src/plugins/bundle-plugin-loader.ts
|
|
1370
|
-
import { existsSync as
|
|
1522
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
1371
1523
|
import { join as join7 } from "path";
|
|
1524
|
+
|
|
1525
|
+
// src/plugins/bundle-plugin-utils.ts
|
|
1526
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
|
|
1372
1527
|
function parseSkillFrontmatter(raw) {
|
|
1373
1528
|
const trimmed = raw.trimStart();
|
|
1374
1529
|
if (!trimmed.startsWith("---")) {
|
|
@@ -1425,6 +1580,8 @@ function getSortedSubdirs(dirPath) {
|
|
|
1425
1580
|
return [];
|
|
1426
1581
|
}
|
|
1427
1582
|
}
|
|
1583
|
+
|
|
1584
|
+
// src/plugins/bundle-plugin-loader.ts
|
|
1428
1585
|
var BundlePluginLoader = class {
|
|
1429
1586
|
pluginsDir;
|
|
1430
1587
|
enabledPlugins;
|
|
@@ -1448,7 +1605,7 @@ var BundlePluginLoader = class {
|
|
|
1448
1605
|
*/
|
|
1449
1606
|
discoverAndLoad() {
|
|
1450
1607
|
const cacheDir = join7(this.pluginsDir, "cache");
|
|
1451
|
-
if (!
|
|
1608
|
+
if (!existsSync7(cacheDir)) {
|
|
1452
1609
|
return [];
|
|
1453
1610
|
}
|
|
1454
1611
|
const results = [];
|
|
@@ -1463,7 +1620,7 @@ var BundlePluginLoader = class {
|
|
|
1463
1620
|
const latestVersion = versions[versions.length - 1];
|
|
1464
1621
|
const versionDir = join7(pluginDir, latestVersion);
|
|
1465
1622
|
const manifestPath = join7(versionDir, ".claude-plugin", "plugin.json");
|
|
1466
|
-
if (!
|
|
1623
|
+
if (!existsSync7(manifestPath)) continue;
|
|
1467
1624
|
const manifest = this.readManifest(manifestPath);
|
|
1468
1625
|
if (!manifest) continue;
|
|
1469
1626
|
const pluginId = `${manifest.name}@${marketplace}`;
|
|
@@ -1513,13 +1670,13 @@ var BundlePluginLoader = class {
|
|
|
1513
1670
|
/** Load skills from the plugin's skills/ directory. */
|
|
1514
1671
|
loadSkills(pluginDir, pluginName) {
|
|
1515
1672
|
const skillsDir = join7(pluginDir, "skills");
|
|
1516
|
-
if (!
|
|
1517
|
-
const entries =
|
|
1673
|
+
if (!existsSync7(skillsDir)) return [];
|
|
1674
|
+
const entries = readdirSync3(skillsDir, { withFileTypes: true });
|
|
1518
1675
|
const skills = [];
|
|
1519
1676
|
for (const entry of entries) {
|
|
1520
1677
|
if (!entry.isDirectory()) continue;
|
|
1521
1678
|
const skillFile = join7(skillsDir, entry.name, "SKILL.md");
|
|
1522
|
-
if (!
|
|
1679
|
+
if (!existsSync7(skillFile)) continue;
|
|
1523
1680
|
const raw = readFileSync6(skillFile, "utf-8");
|
|
1524
1681
|
const { metadata, content } = parseSkillFrontmatter(raw);
|
|
1525
1682
|
const description = typeof metadata.description === "string" ? metadata.description : "";
|
|
@@ -1536,8 +1693,8 @@ var BundlePluginLoader = class {
|
|
|
1536
1693
|
/** Load commands from the plugin's commands/ directory (flat .md files). */
|
|
1537
1694
|
loadCommands(pluginDir, pluginName) {
|
|
1538
1695
|
const commandsDir = join7(pluginDir, "commands");
|
|
1539
|
-
if (!
|
|
1540
|
-
const entries =
|
|
1696
|
+
if (!existsSync7(commandsDir)) return [];
|
|
1697
|
+
const entries = readdirSync3(commandsDir, { withFileTypes: true });
|
|
1541
1698
|
const commands = [];
|
|
1542
1699
|
for (const entry of entries) {
|
|
1543
1700
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
@@ -1557,7 +1714,7 @@ var BundlePluginLoader = class {
|
|
|
1557
1714
|
/** Load hooks from hooks/hooks.json if present. */
|
|
1558
1715
|
loadHooks(pluginDir) {
|
|
1559
1716
|
const hooksPath = join7(pluginDir, "hooks", "hooks.json");
|
|
1560
|
-
if (!
|
|
1717
|
+
if (!existsSync7(hooksPath)) return {};
|
|
1561
1718
|
try {
|
|
1562
1719
|
const raw = readFileSync6(hooksPath, "utf-8");
|
|
1563
1720
|
const data = JSON.parse(raw);
|
|
@@ -1573,8 +1730,8 @@ var BundlePluginLoader = class {
|
|
|
1573
1730
|
loadMcpConfig(pluginDir) {
|
|
1574
1731
|
const primaryPath = join7(pluginDir, ".mcp.json");
|
|
1575
1732
|
const fallbackPath = join7(pluginDir, ".claude-plugin", "mcp.json");
|
|
1576
|
-
const mcpPath =
|
|
1577
|
-
if (!
|
|
1733
|
+
const mcpPath = existsSync7(primaryPath) ? primaryPath : fallbackPath;
|
|
1734
|
+
if (!existsSync7(mcpPath)) return void 0;
|
|
1578
1735
|
try {
|
|
1579
1736
|
const raw = readFileSync6(mcpPath, "utf-8");
|
|
1580
1737
|
return JSON.parse(raw);
|
|
@@ -1585,9 +1742,9 @@ var BundlePluginLoader = class {
|
|
|
1585
1742
|
/** Load agent definitions from agents/ directory if present. */
|
|
1586
1743
|
loadAgents(pluginDir) {
|
|
1587
1744
|
const agentsDir = join7(pluginDir, "agents");
|
|
1588
|
-
if (!
|
|
1745
|
+
if (!existsSync7(agentsDir)) return [];
|
|
1589
1746
|
try {
|
|
1590
|
-
const entries =
|
|
1747
|
+
const entries = readdirSync3(agentsDir, { withFileTypes: true });
|
|
1591
1748
|
return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
|
|
1592
1749
|
} catch {
|
|
1593
1750
|
return [];
|
|
@@ -1597,7 +1754,7 @@ var BundlePluginLoader = class {
|
|
|
1597
1754
|
|
|
1598
1755
|
// src/plugins/bundle-plugin-installer.ts
|
|
1599
1756
|
import { execSync } from "child_process";
|
|
1600
|
-
import { cpSync, existsSync as
|
|
1757
|
+
import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1601
1758
|
import { join as join8, dirname as dirname3 } from "path";
|
|
1602
1759
|
var GIT_CLONE_TIMEOUT_MS = 6e4;
|
|
1603
1760
|
var BundlePluginInstaller = class {
|
|
@@ -1631,7 +1788,7 @@ var BundlePluginInstaller = class {
|
|
|
1631
1788
|
}
|
|
1632
1789
|
const version = this.resolveVersion(entry, marketplaceName);
|
|
1633
1790
|
const targetDir = join8(this.cacheDir, marketplaceName, pluginName, version);
|
|
1634
|
-
if (
|
|
1791
|
+
if (existsSync8(targetDir)) {
|
|
1635
1792
|
throw new Error(
|
|
1636
1793
|
`Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
|
|
1637
1794
|
);
|
|
@@ -1658,7 +1815,7 @@ var BundlePluginInstaller = class {
|
|
|
1658
1815
|
if (!record) {
|
|
1659
1816
|
throw new Error(`Plugin "${pluginId}" is not installed`);
|
|
1660
1817
|
}
|
|
1661
|
-
if (
|
|
1818
|
+
if (existsSync8(record.installPath)) {
|
|
1662
1819
|
rmSync(record.installPath, { recursive: true, force: true });
|
|
1663
1820
|
}
|
|
1664
1821
|
delete registry[pluginId];
|
|
@@ -1711,7 +1868,7 @@ var BundlePluginInstaller = class {
|
|
|
1711
1868
|
if (typeof source === "string") {
|
|
1712
1869
|
const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
|
|
1713
1870
|
const sourcePath = join8(marketplaceDir, source);
|
|
1714
|
-
if (!
|
|
1871
|
+
if (!existsSync8(sourcePath)) {
|
|
1715
1872
|
throw new Error(
|
|
1716
1873
|
`Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
|
|
1717
1874
|
);
|
|
@@ -1728,7 +1885,7 @@ var BundlePluginInstaller = class {
|
|
|
1728
1885
|
throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
|
|
1729
1886
|
}
|
|
1730
1887
|
} catch (err) {
|
|
1731
|
-
if (
|
|
1888
|
+
if (existsSync8(targetDir)) {
|
|
1732
1889
|
rmSync(targetDir, { recursive: true, force: true });
|
|
1733
1890
|
}
|
|
1734
1891
|
throw err;
|
|
@@ -1747,7 +1904,7 @@ var BundlePluginInstaller = class {
|
|
|
1747
1904
|
}
|
|
1748
1905
|
/** Read the installed_plugins.json registry. */
|
|
1749
1906
|
readRegistry() {
|
|
1750
|
-
if (!
|
|
1907
|
+
if (!existsSync8(this.registryPath)) {
|
|
1751
1908
|
return {};
|
|
1752
1909
|
}
|
|
1753
1910
|
try {
|
|
@@ -1764,7 +1921,7 @@ var BundlePluginInstaller = class {
|
|
|
1764
1921
|
/** Write the installed_plugins.json registry. */
|
|
1765
1922
|
writeRegistry(registry) {
|
|
1766
1923
|
const dir = dirname3(this.registryPath);
|
|
1767
|
-
if (!
|
|
1924
|
+
if (!existsSync8(dir)) {
|
|
1768
1925
|
mkdirSync3(dir, { recursive: true });
|
|
1769
1926
|
}
|
|
1770
1927
|
writeFileSync2(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
@@ -1777,16 +1934,66 @@ var BundlePluginInstaller = class {
|
|
|
1777
1934
|
|
|
1778
1935
|
// src/plugins/marketplace-client.ts
|
|
1779
1936
|
import { execSync as execSync2 } from "child_process";
|
|
1780
|
-
import {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
renameSync,
|
|
1786
|
-
rmSync as rmSync2,
|
|
1787
|
-
writeFileSync as writeFileSync3
|
|
1788
|
-
} from "fs";
|
|
1937
|
+
import { cpSync as cpSync2, existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync9, renameSync, rmSync as rmSync3 } from "fs";
|
|
1938
|
+
import { join as join10 } from "path";
|
|
1939
|
+
|
|
1940
|
+
// src/plugins/marketplace-registry.ts
|
|
1941
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
1789
1942
|
import { join as join9, dirname as dirname4 } from "path";
|
|
1943
|
+
function readRegistry(registryPath) {
|
|
1944
|
+
if (!existsSync9(registryPath)) {
|
|
1945
|
+
return {};
|
|
1946
|
+
}
|
|
1947
|
+
try {
|
|
1948
|
+
const raw = readFileSync8(registryPath, "utf-8");
|
|
1949
|
+
const data = JSON.parse(raw);
|
|
1950
|
+
if (typeof data === "object" && data !== null) {
|
|
1951
|
+
return data;
|
|
1952
|
+
}
|
|
1953
|
+
return {};
|
|
1954
|
+
} catch {
|
|
1955
|
+
return {};
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
function writeRegistry(registryPath, registry) {
|
|
1959
|
+
const dir = dirname4(registryPath);
|
|
1960
|
+
if (!existsSync9(dir)) {
|
|
1961
|
+
mkdirSync4(dir, { recursive: true });
|
|
1962
|
+
}
|
|
1963
|
+
writeFileSync3(registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1964
|
+
}
|
|
1965
|
+
function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
|
|
1966
|
+
const installedPath = join9(pluginsDir, "installed_plugins.json");
|
|
1967
|
+
if (!existsSync9(installedPath)) return;
|
|
1968
|
+
let registry;
|
|
1969
|
+
try {
|
|
1970
|
+
const raw = readFileSync8(installedPath, "utf-8");
|
|
1971
|
+
const data = JSON.parse(raw);
|
|
1972
|
+
if (typeof data !== "object" || data === null) return;
|
|
1973
|
+
registry = data;
|
|
1974
|
+
} catch {
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
let changed = false;
|
|
1978
|
+
for (const [pluginId, record] of Object.entries(registry)) {
|
|
1979
|
+
if (record.marketplace === marketplaceName) {
|
|
1980
|
+
if (record.installPath && existsSync9(record.installPath)) {
|
|
1981
|
+
rmSync2(record.installPath, { recursive: true, force: true });
|
|
1982
|
+
}
|
|
1983
|
+
delete registry[pluginId];
|
|
1984
|
+
changed = true;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
if (changed) {
|
|
1988
|
+
const dir = dirname4(installedPath);
|
|
1989
|
+
if (!existsSync9(dir)) {
|
|
1990
|
+
mkdirSync4(dir, { recursive: true });
|
|
1991
|
+
}
|
|
1992
|
+
writeFileSync3(installedPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
// src/plugins/marketplace-client.ts
|
|
1790
1997
|
var GIT_TIMEOUT_MS = 6e4;
|
|
1791
1998
|
var MarketplaceClient = class {
|
|
1792
1999
|
pluginsDir;
|
|
@@ -1796,25 +2003,24 @@ var MarketplaceClient = class {
|
|
|
1796
2003
|
constructor(options) {
|
|
1797
2004
|
this.pluginsDir = options.pluginsDir;
|
|
1798
2005
|
this.exec = options.exec ?? this.defaultExec;
|
|
1799
|
-
this.marketplacesDir =
|
|
1800
|
-
this.registryPath =
|
|
2006
|
+
this.marketplacesDir = join10(this.pluginsDir, "marketplaces");
|
|
2007
|
+
this.registryPath = join10(this.pluginsDir, "known_marketplaces.json");
|
|
1801
2008
|
}
|
|
1802
2009
|
/**
|
|
1803
2010
|
* Add a marketplace by cloning its repository.
|
|
1804
2011
|
*
|
|
1805
|
-
* 1.
|
|
1806
|
-
* 2.
|
|
1807
|
-
* 3.
|
|
1808
|
-
* 4. Register in `known_marketplaces.json`.
|
|
2012
|
+
* 1. Shallow git clone (`--depth 1`) to `marketplaces/<name>/`.
|
|
2013
|
+
* 2. Read `.claude-plugin/marketplace.json` for the `name` field.
|
|
2014
|
+
* 3. Register in `known_marketplaces.json`.
|
|
1809
2015
|
*
|
|
1810
2016
|
* Returns the registered marketplace name from the manifest.
|
|
1811
2017
|
*/
|
|
1812
2018
|
addMarketplace(source) {
|
|
1813
2019
|
const tempName = "temp-" + Date.now().toString(36);
|
|
1814
|
-
const tempDir =
|
|
1815
|
-
|
|
2020
|
+
const tempDir = join10(this.marketplacesDir, tempName);
|
|
2021
|
+
mkdirSync5(this.marketplacesDir, { recursive: true });
|
|
1816
2022
|
if (source.type === "local") {
|
|
1817
|
-
if (!
|
|
2023
|
+
if (!existsSync10(source.path)) {
|
|
1818
2024
|
throw new Error(`Local marketplace path does not exist: ${source.path}`);
|
|
1819
2025
|
}
|
|
1820
2026
|
cpSync2(source.path, tempDir, { recursive: true });
|
|
@@ -1828,9 +2034,9 @@ var MarketplaceClient = class {
|
|
|
1828
2034
|
throw new Error(`Failed to clone marketplace: ${message}`);
|
|
1829
2035
|
}
|
|
1830
2036
|
}
|
|
1831
|
-
const manifestPath =
|
|
1832
|
-
if (!
|
|
1833
|
-
|
|
2037
|
+
const manifestPath = join10(tempDir, ".claude-plugin", "marketplace.json");
|
|
2038
|
+
if (!existsSync10(manifestPath)) {
|
|
2039
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
1834
2040
|
throw new Error(
|
|
1835
2041
|
source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
|
|
1836
2042
|
);
|
|
@@ -1838,22 +2044,22 @@ var MarketplaceClient = class {
|
|
|
1838
2044
|
const manifest = this.readManifestFromPath(manifestPath);
|
|
1839
2045
|
const name = manifest.name;
|
|
1840
2046
|
if (!name) {
|
|
1841
|
-
|
|
2047
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
1842
2048
|
throw new Error('Marketplace manifest does not contain a "name" field');
|
|
1843
2049
|
}
|
|
1844
|
-
const registry = this.
|
|
2050
|
+
const registry = readRegistry(this.registryPath);
|
|
1845
2051
|
if (registry[name]) {
|
|
1846
|
-
|
|
2052
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
1847
2053
|
throw new Error(`Marketplace "${name}" already exists`);
|
|
1848
2054
|
}
|
|
1849
|
-
const finalDir =
|
|
2055
|
+
const finalDir = join10(this.marketplacesDir, name);
|
|
1850
2056
|
renameSync(tempDir, finalDir);
|
|
1851
2057
|
registry[name] = {
|
|
1852
2058
|
source,
|
|
1853
2059
|
installLocation: finalDir,
|
|
1854
2060
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1855
2061
|
};
|
|
1856
|
-
this.
|
|
2062
|
+
writeRegistry(this.registryPath, registry);
|
|
1857
2063
|
return name;
|
|
1858
2064
|
}
|
|
1859
2065
|
/**
|
|
@@ -1862,41 +2068,38 @@ var MarketplaceClient = class {
|
|
|
1862
2068
|
* and removes from the registry.
|
|
1863
2069
|
*/
|
|
1864
2070
|
removeMarketplace(name) {
|
|
1865
|
-
const registry = this.
|
|
2071
|
+
const registry = readRegistry(this.registryPath);
|
|
1866
2072
|
const entry = registry[name];
|
|
1867
2073
|
if (!entry) {
|
|
1868
2074
|
throw new Error(`Marketplace "${name}" not found`);
|
|
1869
2075
|
}
|
|
1870
|
-
this.
|
|
1871
|
-
if (
|
|
1872
|
-
|
|
2076
|
+
removeInstalledPluginsForMarketplace(this.pluginsDir, name);
|
|
2077
|
+
if (existsSync10(entry.installLocation)) {
|
|
2078
|
+
rmSync3(entry.installLocation, { recursive: true, force: true });
|
|
1873
2079
|
}
|
|
1874
2080
|
delete registry[name];
|
|
1875
|
-
this.
|
|
2081
|
+
writeRegistry(this.registryPath, registry);
|
|
1876
2082
|
}
|
|
1877
2083
|
/**
|
|
1878
2084
|
* Update a marketplace by running git pull on its clone.
|
|
1879
2085
|
* The manifest is re-read from disk on demand (via fetchManifest), so the
|
|
1880
2086
|
* updated manifest is automatically available after pull.
|
|
1881
|
-
*
|
|
1882
|
-
* TODO: After pull, detect version changes in installed plugins and offer
|
|
1883
|
-
* to update them (re-install at new version).
|
|
1884
2087
|
*/
|
|
1885
2088
|
updateMarketplace(name) {
|
|
1886
|
-
const registry = this.
|
|
2089
|
+
const registry = readRegistry(this.registryPath);
|
|
1887
2090
|
const entry = registry[name];
|
|
1888
2091
|
if (!entry) {
|
|
1889
2092
|
throw new Error(`Marketplace "${name}" not found`);
|
|
1890
2093
|
}
|
|
1891
|
-
if (!
|
|
2094
|
+
if (!existsSync10(entry.installLocation)) {
|
|
1892
2095
|
throw new Error(`Marketplace directory for "${name}" does not exist`);
|
|
1893
2096
|
}
|
|
1894
2097
|
if (entry.source.type === "local") {
|
|
1895
2098
|
const localSource = entry.source;
|
|
1896
|
-
if (!
|
|
2099
|
+
if (!existsSync10(localSource.path)) {
|
|
1897
2100
|
throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
|
|
1898
2101
|
}
|
|
1899
|
-
|
|
2102
|
+
rmSync3(entry.installLocation, { recursive: true, force: true });
|
|
1900
2103
|
cpSync2(localSource.path, entry.installLocation, { recursive: true });
|
|
1901
2104
|
} else {
|
|
1902
2105
|
const command = `git -C ${entry.installLocation} pull`;
|
|
@@ -1908,28 +2111,26 @@ var MarketplaceClient = class {
|
|
|
1908
2111
|
}
|
|
1909
2112
|
}
|
|
1910
2113
|
entry.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1911
|
-
this.
|
|
2114
|
+
writeRegistry(this.registryPath, registry);
|
|
1912
2115
|
}
|
|
1913
2116
|
/** List all registered marketplaces. */
|
|
1914
2117
|
listMarketplaces() {
|
|
1915
|
-
const registry = this.
|
|
2118
|
+
const registry = readRegistry(this.registryPath);
|
|
1916
2119
|
return Object.entries(registry).map(([name, entry]) => ({
|
|
1917
2120
|
name,
|
|
1918
2121
|
source: entry.source,
|
|
1919
2122
|
lastUpdated: entry.lastUpdated
|
|
1920
2123
|
}));
|
|
1921
2124
|
}
|
|
1922
|
-
/**
|
|
1923
|
-
* Read the marketplace manifest from a registered marketplace's clone.
|
|
1924
|
-
*/
|
|
2125
|
+
/** Read the marketplace manifest from a registered marketplace's clone. */
|
|
1925
2126
|
fetchManifest(marketplaceName) {
|
|
1926
|
-
const registry = this.
|
|
2127
|
+
const registry = readRegistry(this.registryPath);
|
|
1927
2128
|
const entry = registry[marketplaceName];
|
|
1928
2129
|
if (!entry) {
|
|
1929
2130
|
throw new Error(`Marketplace "${marketplaceName}" not found`);
|
|
1930
2131
|
}
|
|
1931
|
-
const manifestPath =
|
|
1932
|
-
if (!
|
|
2132
|
+
const manifestPath = join10(entry.installLocation, ".claude-plugin", "marketplace.json");
|
|
2133
|
+
if (!existsSync10(manifestPath)) {
|
|
1933
2134
|
throw new Error(
|
|
1934
2135
|
`Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
|
|
1935
2136
|
);
|
|
@@ -1938,7 +2139,7 @@ var MarketplaceClient = class {
|
|
|
1938
2139
|
}
|
|
1939
2140
|
/** Get the clone directory path for a registered marketplace. */
|
|
1940
2141
|
getMarketplaceDir(name) {
|
|
1941
|
-
const registry = this.
|
|
2142
|
+
const registry = readRegistry(this.registryPath);
|
|
1942
2143
|
const entry = registry[name];
|
|
1943
2144
|
if (!entry) {
|
|
1944
2145
|
throw new Error(`Marketplace "${name}" not found`);
|
|
@@ -1990,44 +2191,9 @@ var MarketplaceClient = class {
|
|
|
1990
2191
|
throw new Error("URL marketplace source is not yet supported");
|
|
1991
2192
|
}
|
|
1992
2193
|
}
|
|
1993
|
-
/**
|
|
1994
|
-
* Remove all installed plugins that belong to a given marketplace.
|
|
1995
|
-
* Reads installed_plugins.json, deletes cache directories for matching plugins,
|
|
1996
|
-
* and updates the registry.
|
|
1997
|
-
*/
|
|
1998
|
-
removeInstalledPluginsForMarketplace(marketplaceName) {
|
|
1999
|
-
const installedPath = join9(this.pluginsDir, "installed_plugins.json");
|
|
2000
|
-
if (!existsSync8(installedPath)) return;
|
|
2001
|
-
let registry;
|
|
2002
|
-
try {
|
|
2003
|
-
const raw = readFileSync8(installedPath, "utf-8");
|
|
2004
|
-
const data = JSON.parse(raw);
|
|
2005
|
-
if (typeof data !== "object" || data === null) return;
|
|
2006
|
-
registry = data;
|
|
2007
|
-
} catch {
|
|
2008
|
-
return;
|
|
2009
|
-
}
|
|
2010
|
-
let changed = false;
|
|
2011
|
-
for (const [pluginId, record] of Object.entries(registry)) {
|
|
2012
|
-
if (record.marketplace === marketplaceName) {
|
|
2013
|
-
if (record.installPath && existsSync8(record.installPath)) {
|
|
2014
|
-
rmSync2(record.installPath, { recursive: true, force: true });
|
|
2015
|
-
}
|
|
2016
|
-
delete registry[pluginId];
|
|
2017
|
-
changed = true;
|
|
2018
|
-
}
|
|
2019
|
-
}
|
|
2020
|
-
if (changed) {
|
|
2021
|
-
const dir = dirname4(installedPath);
|
|
2022
|
-
if (!existsSync8(dir)) {
|
|
2023
|
-
mkdirSync4(dir, { recursive: true });
|
|
2024
|
-
}
|
|
2025
|
-
writeFileSync3(installedPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
2194
|
/** Read and parse a marketplace.json from a file path. */
|
|
2029
2195
|
readManifestFromPath(path) {
|
|
2030
|
-
const raw =
|
|
2196
|
+
const raw = readFileSync9(path, "utf-8");
|
|
2031
2197
|
const data = JSON.parse(raw);
|
|
2032
2198
|
if (typeof data !== "object" || data === null) {
|
|
2033
2199
|
throw new Error("Invalid marketplace manifest: not an object");
|
|
@@ -2038,30 +2204,6 @@ var MarketplaceClient = class {
|
|
|
2038
2204
|
}
|
|
2039
2205
|
return data;
|
|
2040
2206
|
}
|
|
2041
|
-
/** Read the known_marketplaces.json registry. */
|
|
2042
|
-
readRegistry() {
|
|
2043
|
-
if (!existsSync8(this.registryPath)) {
|
|
2044
|
-
return {};
|
|
2045
|
-
}
|
|
2046
|
-
try {
|
|
2047
|
-
const raw = readFileSync8(this.registryPath, "utf-8");
|
|
2048
|
-
const data = JSON.parse(raw);
|
|
2049
|
-
if (typeof data === "object" && data !== null) {
|
|
2050
|
-
return data;
|
|
2051
|
-
}
|
|
2052
|
-
return {};
|
|
2053
|
-
} catch {
|
|
2054
|
-
return {};
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
/** Write the known_marketplaces.json registry. */
|
|
2058
|
-
writeRegistry(registry) {
|
|
2059
|
-
const dir = dirname4(this.registryPath);
|
|
2060
|
-
if (!existsSync8(dir)) {
|
|
2061
|
-
mkdirSync4(dir, { recursive: true });
|
|
2062
|
-
}
|
|
2063
|
-
writeFileSync3(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
2064
|
-
}
|
|
2065
2207
|
/** Default exec implementation using child_process. */
|
|
2066
2208
|
defaultExec(command, options) {
|
|
2067
2209
|
return execSync2(command, { timeout: options.timeout, stdio: "pipe" });
|
|
@@ -2069,9 +2211,9 @@ var MarketplaceClient = class {
|
|
|
2069
2211
|
};
|
|
2070
2212
|
|
|
2071
2213
|
// src/plugins/plugin-hooks-merger.ts
|
|
2072
|
-
import { join as
|
|
2214
|
+
import { join as join11, dirname as dirname5 } from "path";
|
|
2073
2215
|
function buildPluginEnv(plugin) {
|
|
2074
|
-
const dataDir =
|
|
2216
|
+
const dataDir = join11(dirname5(dirname5(plugin.pluginDir)), "data", plugin.manifest.name);
|
|
2075
2217
|
return {
|
|
2076
2218
|
CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
|
|
2077
2219
|
CLAUDE_PLUGIN_PATH: plugin.pluginDir,
|
|
@@ -2132,41 +2274,111 @@ function mergeHooksIntoConfig(configHooks, pluginHooks) {
|
|
|
2132
2274
|
return merged;
|
|
2133
2275
|
}
|
|
2134
2276
|
|
|
2135
|
-
// src/interactive/interactive-session.ts
|
|
2277
|
+
// src/interactive/interactive-session-init.ts
|
|
2136
2278
|
import { homedir as homedir3 } from "os";
|
|
2137
|
-
import { join as
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2279
|
+
import { join as join12 } from "path";
|
|
2280
|
+
async function createInteractiveSession(options) {
|
|
2281
|
+
const cwd = options.cwd;
|
|
2282
|
+
const [config, context, projectInfo] = await Promise.all([
|
|
2283
|
+
loadConfig(cwd),
|
|
2284
|
+
loadContext(cwd),
|
|
2285
|
+
detectProject(cwd)
|
|
2286
|
+
]);
|
|
2287
|
+
const pluginsDir = join12(homedir3(), ".robota", "plugins");
|
|
2288
|
+
const pluginLoader = new BundlePluginLoader(pluginsDir);
|
|
2289
|
+
let mergedConfig = config;
|
|
2290
|
+
try {
|
|
2291
|
+
const plugins = pluginLoader.loadPluginsSync();
|
|
2292
|
+
if (plugins.length > 0) {
|
|
2293
|
+
const pluginHooks = mergePluginHooks(plugins);
|
|
2294
|
+
mergedConfig = {
|
|
2295
|
+
...config,
|
|
2296
|
+
hooks: mergeHooksIntoConfig(
|
|
2297
|
+
config.hooks,
|
|
2298
|
+
pluginHooks
|
|
2299
|
+
)
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
} catch {
|
|
2303
|
+
}
|
|
2304
|
+
const paths = projectPaths(cwd);
|
|
2305
|
+
const sessionId = options.resumeSessionId && !options.forkSession ? options.resumeSessionId : void 0;
|
|
2306
|
+
return createSession({
|
|
2307
|
+
config: mergedConfig,
|
|
2308
|
+
context,
|
|
2309
|
+
projectInfo,
|
|
2310
|
+
permissionMode: options.permissionMode,
|
|
2311
|
+
maxTurns: options.maxTurns,
|
|
2312
|
+
terminal: NOOP_TERMINAL,
|
|
2313
|
+
sessionLogger: new FileSessionLogger2(paths.logs),
|
|
2314
|
+
permissionHandler: options.permissionHandler,
|
|
2315
|
+
provider: options.provider,
|
|
2316
|
+
onTextDelta: options.onTextDelta,
|
|
2317
|
+
onToolExecution: options.onToolExecution,
|
|
2318
|
+
sessionId
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
function injectSavedMessage(session, msg) {
|
|
2322
|
+
if (!msg || typeof msg !== "object") return;
|
|
2323
|
+
const m = msg;
|
|
2324
|
+
if (!m.role || !m.content) return;
|
|
2325
|
+
const role = m.role;
|
|
2326
|
+
if (role === "tool") {
|
|
2327
|
+
const toolCallId = m.toolCallId ?? "";
|
|
2328
|
+
const name = m.name ?? void 0;
|
|
2329
|
+
session.injectMessage("tool", m.content, { toolCallId, name });
|
|
2330
|
+
} else if (role === "user" || role === "assistant" || role === "system") {
|
|
2331
|
+
session.injectMessage(role, m.content);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingSession) {
|
|
2335
|
+
const record = sessionStore.load(resumeSessionId);
|
|
2336
|
+
if (!record) {
|
|
2337
|
+
return { history: [], sessionName: void 0, pendingRestoreMessages: null };
|
|
2338
|
+
}
|
|
2339
|
+
const history = record.history ?? [];
|
|
2340
|
+
const sessionName = record.name;
|
|
2341
|
+
let pendingRestoreMessages = null;
|
|
2342
|
+
if (!forkSession && record.messages) {
|
|
2343
|
+
if (existingSession) {
|
|
2344
|
+
for (const msg of record.messages) {
|
|
2345
|
+
injectSavedMessage(existingSession, msg);
|
|
2346
|
+
}
|
|
2347
|
+
} else {
|
|
2348
|
+
pendingRestoreMessages = record.messages;
|
|
2349
|
+
}
|
|
2350
|
+
}
|
|
2351
|
+
return { history, sessionName, pendingRestoreMessages };
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
// src/interactive/interactive-session.ts
|
|
2142
2355
|
var InteractiveSession = class {
|
|
2143
2356
|
session = null;
|
|
2144
2357
|
commandExecutor;
|
|
2145
2358
|
listeners = /* @__PURE__ */ new Map();
|
|
2146
2359
|
initialized = false;
|
|
2147
2360
|
initPromise = null;
|
|
2148
|
-
// Streaming state
|
|
2149
2361
|
streamingText = "";
|
|
2150
2362
|
flushTimer = null;
|
|
2151
|
-
// Tool state
|
|
2152
2363
|
activeTools = [];
|
|
2153
|
-
// Execution state
|
|
2154
2364
|
executing = false;
|
|
2155
2365
|
pendingPrompt = null;
|
|
2156
2366
|
pendingDisplayInput;
|
|
2157
2367
|
pendingRawInput;
|
|
2158
|
-
// Full history timeline (chat messages + events)
|
|
2159
2368
|
history = [];
|
|
2160
|
-
// Session persistence
|
|
2161
2369
|
sessionStore;
|
|
2162
2370
|
sessionName;
|
|
2163
2371
|
cwd;
|
|
2164
|
-
// Session restore state
|
|
2165
2372
|
pendingRestoreMessages = null;
|
|
2166
2373
|
resumeSessionId;
|
|
2167
2374
|
forkSession;
|
|
2168
2375
|
constructor(options) {
|
|
2169
2376
|
this.commandExecutor = new SystemCommandExecutor(createSystemCommands());
|
|
2377
|
+
this.sessionStore = options.sessionStore;
|
|
2378
|
+
this.sessionName = options.sessionName;
|
|
2379
|
+
this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
|
|
2380
|
+
this.resumeSessionId = options.resumeSessionId;
|
|
2381
|
+
this.forkSession = options.forkSession ?? false;
|
|
2170
2382
|
if ("session" in options && options.session) {
|
|
2171
2383
|
this.session = options.session;
|
|
2172
2384
|
this.initialized = true;
|
|
@@ -2174,98 +2386,46 @@ var InteractiveSession = class {
|
|
|
2174
2386
|
const stdOpts = options;
|
|
2175
2387
|
this.initPromise = this.initializeAsync(stdOpts);
|
|
2176
2388
|
}
|
|
2177
|
-
this.sessionStore = options.sessionStore;
|
|
2178
|
-
this.sessionName = options.sessionName;
|
|
2179
|
-
this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
|
|
2180
|
-
this.resumeSessionId = options.resumeSessionId;
|
|
2181
|
-
this.forkSession = options.forkSession ?? false;
|
|
2182
2389
|
if (options.resumeSessionId && this.sessionStore) {
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
this.
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
this.session.injectMessage(m.role, m.content);
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
} else {
|
|
2196
|
-
this.pendingRestoreMessages = record.messages;
|
|
2197
|
-
}
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2390
|
+
const restored = loadSessionRecord(
|
|
2391
|
+
this.sessionStore,
|
|
2392
|
+
options.resumeSessionId,
|
|
2393
|
+
this.forkSession,
|
|
2394
|
+
this.session
|
|
2395
|
+
);
|
|
2396
|
+
if (restored.history.length > 0) this.history = restored.history;
|
|
2397
|
+
if (restored.sessionName) this.sessionName = restored.sessionName;
|
|
2398
|
+
this.pendingRestoreMessages = restored.pendingRestoreMessages;
|
|
2200
2399
|
}
|
|
2201
2400
|
}
|
|
2202
2401
|
async initializeAsync(options) {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
loadContext(cwd),
|
|
2207
|
-
detectProject(cwd)
|
|
2208
|
-
]);
|
|
2209
|
-
const pluginsDir = join11(homedir3(), ".robota", "plugins");
|
|
2210
|
-
const pluginLoader = new BundlePluginLoader(pluginsDir);
|
|
2211
|
-
let mergedConfig = config;
|
|
2212
|
-
try {
|
|
2213
|
-
const plugins = pluginLoader.loadPluginsSync();
|
|
2214
|
-
if (plugins.length > 0) {
|
|
2215
|
-
const pluginHooks = mergePluginHooks(plugins);
|
|
2216
|
-
mergedConfig = {
|
|
2217
|
-
...config,
|
|
2218
|
-
hooks: mergeHooksIntoConfig(
|
|
2219
|
-
config.hooks,
|
|
2220
|
-
pluginHooks
|
|
2221
|
-
)
|
|
2222
|
-
};
|
|
2223
|
-
}
|
|
2224
|
-
} catch {
|
|
2225
|
-
}
|
|
2226
|
-
const paths = projectPaths(cwd);
|
|
2227
|
-
const sessionId = this.resumeSessionId && !this.forkSession ? this.resumeSessionId : void 0;
|
|
2228
|
-
this.session = createSession({
|
|
2229
|
-
config: mergedConfig,
|
|
2230
|
-
context,
|
|
2231
|
-
projectInfo,
|
|
2402
|
+
this.session = await createInteractiveSession({
|
|
2403
|
+
cwd: options.cwd,
|
|
2404
|
+
provider: options.provider,
|
|
2232
2405
|
permissionMode: options.permissionMode,
|
|
2233
2406
|
maxTurns: options.maxTurns,
|
|
2234
|
-
terminal: NOOP_TERMINAL,
|
|
2235
|
-
sessionLogger: new FileSessionLogger2(paths.logs),
|
|
2236
2407
|
permissionHandler: options.permissionHandler,
|
|
2237
|
-
|
|
2408
|
+
resumeSessionId: this.resumeSessionId,
|
|
2409
|
+
forkSession: this.forkSession,
|
|
2238
2410
|
onTextDelta: (delta) => this.handleTextDelta(delta),
|
|
2239
|
-
onToolExecution: (event) => this.handleToolExecution(event)
|
|
2240
|
-
sessionId
|
|
2411
|
+
onToolExecution: (event) => this.handleToolExecution(event)
|
|
2241
2412
|
});
|
|
2242
2413
|
if (this.pendingRestoreMessages) {
|
|
2243
|
-
for (const msg of this.pendingRestoreMessages)
|
|
2244
|
-
if (msg && typeof msg === "object" && "role" in msg && "content" in msg) {
|
|
2245
|
-
this.session.injectMessage(
|
|
2246
|
-
msg.role,
|
|
2247
|
-
msg.content
|
|
2248
|
-
);
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2414
|
+
for (const msg of this.pendingRestoreMessages) injectSavedMessage(this.session, msg);
|
|
2251
2415
|
this.pendingRestoreMessages = null;
|
|
2252
2416
|
}
|
|
2253
2417
|
this.initialized = true;
|
|
2254
2418
|
}
|
|
2255
2419
|
async ensureInitialized() {
|
|
2256
|
-
if (this.initialized)
|
|
2257
|
-
if (this.initPromise) await this.initPromise;
|
|
2420
|
+
if (!this.initialized && this.initPromise) await this.initPromise;
|
|
2258
2421
|
}
|
|
2259
2422
|
getSessionOrThrow() {
|
|
2260
2423
|
if (!this.session)
|
|
2261
2424
|
throw new Error("InteractiveSession not initialized. Call submit() or await initialization.");
|
|
2262
2425
|
return this.session;
|
|
2263
2426
|
}
|
|
2264
|
-
// ── Event system ──────────────────────────────────────────────
|
|
2265
2427
|
on(event, handler) {
|
|
2266
|
-
if (!this.listeners.has(event))
|
|
2267
|
-
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
2268
|
-
}
|
|
2428
|
+
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
2269
2429
|
this.listeners.get(event).add(handler);
|
|
2270
2430
|
}
|
|
2271
2431
|
off(event, handler) {
|
|
@@ -2273,14 +2433,8 @@ var InteractiveSession = class {
|
|
|
2273
2433
|
}
|
|
2274
2434
|
emit(event, ...args) {
|
|
2275
2435
|
const handlers = this.listeners.get(event);
|
|
2276
|
-
if (handlers)
|
|
2277
|
-
for (const handler of handlers) {
|
|
2278
|
-
handler(...args);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2436
|
+
if (handlers) for (const handler of handlers) handler(...args);
|
|
2281
2437
|
}
|
|
2282
|
-
// ── Public API ────────────────────────────────────────────────
|
|
2283
|
-
/** Submit a prompt. Queues if already executing (max 1 queued). */
|
|
2284
2438
|
async submit(input, displayInput, rawInput) {
|
|
2285
2439
|
await this.ensureInitialized();
|
|
2286
2440
|
if (this.executing) {
|
|
@@ -2291,27 +2445,24 @@ var InteractiveSession = class {
|
|
|
2291
2445
|
}
|
|
2292
2446
|
await this.executePrompt(input, displayInput, rawInput);
|
|
2293
2447
|
}
|
|
2294
|
-
/** Execute a system command by name. Returns null if not found. */
|
|
2295
2448
|
async executeCommand(name, args) {
|
|
2296
2449
|
await this.ensureInitialized();
|
|
2297
2450
|
return this.commandExecutor.execute(name, this, args);
|
|
2298
2451
|
}
|
|
2299
|
-
/** List all registered system commands. */
|
|
2300
2452
|
listCommands() {
|
|
2301
2453
|
return this.commandExecutor.listCommands().map((cmd) => ({
|
|
2302
2454
|
name: cmd.name,
|
|
2303
2455
|
description: cmd.description
|
|
2304
2456
|
}));
|
|
2305
2457
|
}
|
|
2306
|
-
/** Abort current execution and clear queue. */
|
|
2307
2458
|
abort() {
|
|
2308
|
-
this.
|
|
2309
|
-
this.pendingDisplayInput = void 0;
|
|
2310
|
-
this.pendingRawInput = void 0;
|
|
2459
|
+
this.clearPendingQueue();
|
|
2311
2460
|
this.session?.abort();
|
|
2312
2461
|
}
|
|
2313
|
-
/** Cancel queued prompt without aborting current execution. */
|
|
2314
2462
|
cancelQueue() {
|
|
2463
|
+
this.clearPendingQueue();
|
|
2464
|
+
}
|
|
2465
|
+
clearPendingQueue() {
|
|
2315
2466
|
this.pendingPrompt = null;
|
|
2316
2467
|
this.pendingDisplayInput = void 0;
|
|
2317
2468
|
this.pendingRawInput = void 0;
|
|
@@ -2322,11 +2473,9 @@ var InteractiveSession = class {
|
|
|
2322
2473
|
getPendingPrompt() {
|
|
2323
2474
|
return this.pendingPrompt;
|
|
2324
2475
|
}
|
|
2325
|
-
/** Get full history timeline (chat + events) for TUI rendering */
|
|
2326
2476
|
getFullHistory() {
|
|
2327
2477
|
return this.history;
|
|
2328
2478
|
}
|
|
2329
|
-
/** Get chat messages only (backward compatible) */
|
|
2330
2479
|
getMessages() {
|
|
2331
2480
|
return this.history.filter((e) => e.category === "chat").map((e) => e.data);
|
|
2332
2481
|
}
|
|
@@ -2339,11 +2488,12 @@ var InteractiveSession = class {
|
|
|
2339
2488
|
getContextState() {
|
|
2340
2489
|
return this.getSessionOrThrow().getContextState();
|
|
2341
2490
|
}
|
|
2342
|
-
/** Get session name. */
|
|
2343
2491
|
getName() {
|
|
2344
2492
|
return this.sessionName;
|
|
2345
2493
|
}
|
|
2346
|
-
|
|
2494
|
+
getSession() {
|
|
2495
|
+
return this.getSessionOrThrow();
|
|
2496
|
+
}
|
|
2347
2497
|
setName(name) {
|
|
2348
2498
|
this.sessionName = name;
|
|
2349
2499
|
if (this.sessionStore && this.session) {
|
|
@@ -2359,15 +2509,9 @@ var InteractiveSession = class {
|
|
|
2359
2509
|
}
|
|
2360
2510
|
}
|
|
2361
2511
|
}
|
|
2362
|
-
/** Attach a transport adapter to this session. Calls transport.attach(this). */
|
|
2363
2512
|
attachTransport(transport) {
|
|
2364
2513
|
transport.attach(this);
|
|
2365
2514
|
}
|
|
2366
|
-
/** Access underlying Session. For advanced use / testing only. */
|
|
2367
|
-
getSession() {
|
|
2368
|
-
return this.getSessionOrThrow();
|
|
2369
|
-
}
|
|
2370
|
-
// ── Execution ─────────────────────────────────────────────────
|
|
2371
2515
|
async executePrompt(input, displayInput, rawInput) {
|
|
2372
2516
|
this.executing = true;
|
|
2373
2517
|
this.clearStreaming();
|
|
@@ -2377,25 +2521,35 @@ var InteractiveSession = class {
|
|
|
2377
2521
|
try {
|
|
2378
2522
|
const response = await this.getSessionOrThrow().run(input, rawInput);
|
|
2379
2523
|
this.flushStreaming();
|
|
2380
|
-
this.
|
|
2524
|
+
pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
|
|
2381
2525
|
this.clearStreaming();
|
|
2382
|
-
const result =
|
|
2526
|
+
const result = buildResult(
|
|
2527
|
+
response || "(empty response)",
|
|
2528
|
+
this.getSessionOrThrow().getHistory(),
|
|
2529
|
+
this.history,
|
|
2530
|
+
historyBefore,
|
|
2531
|
+
this.getContextState()
|
|
2532
|
+
);
|
|
2383
2533
|
this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
|
|
2384
2534
|
this.emit("complete", result);
|
|
2385
2535
|
this.emit("context_update", this.getContextState());
|
|
2386
2536
|
} catch (err) {
|
|
2387
2537
|
this.flushStreaming();
|
|
2388
2538
|
if (isAbortError(err)) {
|
|
2389
|
-
const result =
|
|
2390
|
-
|
|
2539
|
+
const result = buildInterruptedResult(
|
|
2540
|
+
this.getSessionOrThrow().getHistory(),
|
|
2541
|
+
this.history,
|
|
2542
|
+
historyBefore,
|
|
2543
|
+
this.getContextState()
|
|
2544
|
+
);
|
|
2545
|
+
pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
|
|
2391
2546
|
this.clearStreaming();
|
|
2392
|
-
if (result.response)
|
|
2547
|
+
if (result.response)
|
|
2393
2548
|
this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
|
|
2394
|
-
}
|
|
2395
2549
|
this.history.push(messageToHistoryEntry(createSystemMessage("Interrupted by user.")));
|
|
2396
2550
|
this.emit("interrupted", result);
|
|
2397
2551
|
} else {
|
|
2398
|
-
this.
|
|
2552
|
+
pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
|
|
2399
2553
|
this.clearStreaming();
|
|
2400
2554
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2401
2555
|
this.history.push(messageToHistoryEntry(createSystemMessage(`Error: ${errMsg}`)));
|
|
@@ -2405,33 +2559,23 @@ var InteractiveSession = class {
|
|
|
2405
2559
|
this.executing = false;
|
|
2406
2560
|
this.emit("thinking", false);
|
|
2407
2561
|
if (this.sessionStore && this.session) {
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
this.
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
createdAt: existing?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2416
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2417
|
-
messages: this.getSessionOrThrow().getHistory(),
|
|
2418
|
-
history: this.history
|
|
2419
|
-
});
|
|
2420
|
-
} catch {
|
|
2421
|
-
}
|
|
2562
|
+
persistSession(
|
|
2563
|
+
this.sessionStore,
|
|
2564
|
+
this.session,
|
|
2565
|
+
this.sessionName,
|
|
2566
|
+
this.cwd ?? "",
|
|
2567
|
+
this.history
|
|
2568
|
+
);
|
|
2422
2569
|
}
|
|
2423
2570
|
if (this.pendingPrompt) {
|
|
2424
2571
|
const queued = this.pendingPrompt;
|
|
2425
2572
|
const queuedDisplay = this.pendingDisplayInput;
|
|
2426
2573
|
const queuedRaw = this.pendingRawInput;
|
|
2427
|
-
this.
|
|
2428
|
-
this.pendingDisplayInput = void 0;
|
|
2429
|
-
this.pendingRawInput = void 0;
|
|
2574
|
+
this.clearPendingQueue();
|
|
2430
2575
|
setTimeout(() => this.executePrompt(queued, queuedDisplay, queuedRaw), 0);
|
|
2431
2576
|
}
|
|
2432
2577
|
}
|
|
2433
2578
|
}
|
|
2434
|
-
// ── Streaming callbacks ───────────────────────────────────────
|
|
2435
2579
|
handleTextDelta(delta) {
|
|
2436
2580
|
this.streamingText += delta;
|
|
2437
2581
|
this.emit("text_delta", delta);
|
|
@@ -2442,67 +2586,17 @@ var InteractiveSession = class {
|
|
|
2442
2586
|
}
|
|
2443
2587
|
}
|
|
2444
2588
|
handleToolExecution(event) {
|
|
2589
|
+
const streamingState = { activeTools: this.activeTools, history: this.history };
|
|
2445
2590
|
if (event.type === "start") {
|
|
2446
|
-
const
|
|
2447
|
-
|
|
2448
|
-
this.
|
|
2449
|
-
this.emit("tool_start", state);
|
|
2450
|
-
this.history.push({
|
|
2451
|
-
id: randomUUID(),
|
|
2452
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2453
|
-
category: "event",
|
|
2454
|
-
type: "tool-start",
|
|
2455
|
-
data: { toolName: event.toolName, firstArg, isRunning: true }
|
|
2456
|
-
});
|
|
2591
|
+
const toolState = applyToolStart(streamingState, event);
|
|
2592
|
+
this.activeTools = streamingState.activeTools;
|
|
2593
|
+
this.emit("tool_start", toolState);
|
|
2457
2594
|
} else {
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
if (
|
|
2461
|
-
const finished = { ...this.activeTools[idx], isRunning: false, result };
|
|
2462
|
-
this.activeTools[idx] = finished;
|
|
2463
|
-
this.trimCompletedTools();
|
|
2464
|
-
this.emit("tool_end", finished);
|
|
2465
|
-
this.history.push({
|
|
2466
|
-
id: randomUUID(),
|
|
2467
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2468
|
-
category: "event",
|
|
2469
|
-
type: "tool-end",
|
|
2470
|
-
data: {
|
|
2471
|
-
toolName: finished.toolName,
|
|
2472
|
-
firstArg: finished.firstArg,
|
|
2473
|
-
isRunning: false,
|
|
2474
|
-
result
|
|
2475
|
-
}
|
|
2476
|
-
});
|
|
2477
|
-
}
|
|
2595
|
+
const finished = applyToolEnd(streamingState, event);
|
|
2596
|
+
this.activeTools = streamingState.activeTools;
|
|
2597
|
+
if (finished) this.emit("tool_end", finished);
|
|
2478
2598
|
}
|
|
2479
2599
|
}
|
|
2480
|
-
// ── Helpers ───────────────────────────────────────────────────
|
|
2481
|
-
/** Push tool execution summary into messages (before Robota response).
|
|
2482
|
-
* Moves tool info from activeTools (real-time display) to messages (permanent display).
|
|
2483
|
-
* After this, activeTools will be cleared by clearStreaming(). */
|
|
2484
|
-
pushToolSummaryMessage() {
|
|
2485
|
-
if (this.activeTools.length === 0) return;
|
|
2486
|
-
const summary = this.activeTools.map((t) => {
|
|
2487
|
-
const status = t.isRunning ? "\u27F3" : t.result === "success" ? "\u2713" : t.result === "error" ? "\u2717" : "\u2298";
|
|
2488
|
-
return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
|
|
2489
|
-
}).join("\n");
|
|
2490
|
-
this.history.push({
|
|
2491
|
-
id: randomUUID(),
|
|
2492
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
2493
|
-
category: "event",
|
|
2494
|
-
type: "tool-summary",
|
|
2495
|
-
data: {
|
|
2496
|
-
tools: this.activeTools.map((t) => ({
|
|
2497
|
-
toolName: t.toolName,
|
|
2498
|
-
firstArg: t.firstArg,
|
|
2499
|
-
isRunning: t.isRunning,
|
|
2500
|
-
result: t.result
|
|
2501
|
-
})),
|
|
2502
|
-
summary
|
|
2503
|
-
}
|
|
2504
|
-
});
|
|
2505
|
-
}
|
|
2506
2600
|
clearStreaming() {
|
|
2507
2601
|
this.streamingText = "";
|
|
2508
2602
|
this.activeTools = [];
|
|
@@ -2517,81 +2611,6 @@ var InteractiveSession = class {
|
|
|
2517
2611
|
this.flushTimer = null;
|
|
2518
2612
|
}
|
|
2519
2613
|
}
|
|
2520
|
-
buildResult(response, historyBefore) {
|
|
2521
|
-
const toolSummaries = this.extractToolSummaries(historyBefore);
|
|
2522
|
-
return {
|
|
2523
|
-
response,
|
|
2524
|
-
history: this.history,
|
|
2525
|
-
toolSummaries,
|
|
2526
|
-
contextState: this.getContextState()
|
|
2527
|
-
};
|
|
2528
|
-
}
|
|
2529
|
-
buildInterruptedResult(historyBefore) {
|
|
2530
|
-
const history = this.getSessionOrThrow().getHistory();
|
|
2531
|
-
const toolSummaries = this.extractToolSummaries(historyBefore);
|
|
2532
|
-
const parts = [];
|
|
2533
|
-
for (let i = historyBefore; i < history.length; i++) {
|
|
2534
|
-
const msg = history[i];
|
|
2535
|
-
if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
|
|
2536
|
-
}
|
|
2537
|
-
return {
|
|
2538
|
-
response: parts.join("\n\n"),
|
|
2539
|
-
history: this.history,
|
|
2540
|
-
toolSummaries,
|
|
2541
|
-
contextState: this.getContextState()
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
extractToolSummaries(historyBefore) {
|
|
2545
|
-
const history = this.getSessionOrThrow().getHistory();
|
|
2546
|
-
const summaries = [];
|
|
2547
|
-
for (let i = historyBefore; i < history.length; i++) {
|
|
2548
|
-
const msg = history[i];
|
|
2549
|
-
if (msg?.role === "assistant" && msg.toolCalls) {
|
|
2550
|
-
for (const tc of msg.toolCalls) {
|
|
2551
|
-
summaries.push({ name: tc.function.name, args: tc.function.arguments });
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
return summaries;
|
|
2556
|
-
}
|
|
2557
|
-
trimCompletedTools() {
|
|
2558
|
-
const completed = this.activeTools.filter((t) => !t.isRunning);
|
|
2559
|
-
if (completed.length > MAX_COMPLETED_TOOLS) {
|
|
2560
|
-
const excess = completed.length - MAX_COMPLETED_TOOLS;
|
|
2561
|
-
let removed = 0;
|
|
2562
|
-
this.activeTools = this.activeTools.filter((t) => {
|
|
2563
|
-
if (!t.isRunning && removed < excess) {
|
|
2564
|
-
removed++;
|
|
2565
|
-
return false;
|
|
2566
|
-
}
|
|
2567
|
-
return true;
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
};
|
|
2572
|
-
function isAbortError(err) {
|
|
2573
|
-
return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
|
|
2574
|
-
}
|
|
2575
|
-
function extractFirstArg(toolArgs) {
|
|
2576
|
-
if (!toolArgs) return "";
|
|
2577
|
-
const firstVal = Object.values(toolArgs)[0];
|
|
2578
|
-
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
2579
|
-
return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
|
|
2580
|
-
}
|
|
2581
|
-
var NOOP_TERMINAL = {
|
|
2582
|
-
write: () => {
|
|
2583
|
-
},
|
|
2584
|
-
writeLine: () => {
|
|
2585
|
-
},
|
|
2586
|
-
writeMarkdown: () => {
|
|
2587
|
-
},
|
|
2588
|
-
writeError: () => {
|
|
2589
|
-
},
|
|
2590
|
-
prompt: () => Promise.resolve(""),
|
|
2591
|
-
select: () => Promise.resolve(0),
|
|
2592
|
-
spinner: () => ({ stop: () => {
|
|
2593
|
-
}, update: () => {
|
|
2594
|
-
} })
|
|
2595
2614
|
};
|
|
2596
2615
|
|
|
2597
2616
|
// src/query.ts
|
|
@@ -2746,8 +2765,8 @@ var BuiltinCommandSource = class {
|
|
|
2746
2765
|
};
|
|
2747
2766
|
|
|
2748
2767
|
// src/commands/skill-source.ts
|
|
2749
|
-
import { readdirSync as
|
|
2750
|
-
import { join as
|
|
2768
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync10, existsSync as existsSync11 } from "fs";
|
|
2769
|
+
import { join as join13, basename as basename2 } from "path";
|
|
2751
2770
|
import { homedir as homedir4 } from "os";
|
|
2752
2771
|
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
|
|
2753
2772
|
var LIST_KEYS2 = /* @__PURE__ */ new Set(["allowed-tools"]);
|
|
@@ -2795,27 +2814,27 @@ function buildCommand(frontmatter, content, fallbackName) {
|
|
|
2795
2814
|
return cmd;
|
|
2796
2815
|
}
|
|
2797
2816
|
function scanSkillsDir(skillsDir) {
|
|
2798
|
-
if (!
|
|
2817
|
+
if (!existsSync11(skillsDir)) return [];
|
|
2799
2818
|
const commands = [];
|
|
2800
|
-
const entries =
|
|
2819
|
+
const entries = readdirSync4(skillsDir, { withFileTypes: true });
|
|
2801
2820
|
for (const entry of entries) {
|
|
2802
2821
|
if (!entry.isDirectory()) continue;
|
|
2803
|
-
const skillFile =
|
|
2804
|
-
if (!
|
|
2805
|
-
const content =
|
|
2822
|
+
const skillFile = join13(skillsDir, entry.name, "SKILL.md");
|
|
2823
|
+
if (!existsSync11(skillFile)) continue;
|
|
2824
|
+
const content = readFileSync10(skillFile, "utf-8");
|
|
2806
2825
|
const frontmatter = parseFrontmatter2(content);
|
|
2807
2826
|
commands.push(buildCommand(frontmatter, content, entry.name));
|
|
2808
2827
|
}
|
|
2809
2828
|
return commands;
|
|
2810
2829
|
}
|
|
2811
2830
|
function scanCommandsDir(commandsDir) {
|
|
2812
|
-
if (!
|
|
2831
|
+
if (!existsSync11(commandsDir)) return [];
|
|
2813
2832
|
const commands = [];
|
|
2814
|
-
const entries =
|
|
2833
|
+
const entries = readdirSync4(commandsDir, { withFileTypes: true });
|
|
2815
2834
|
for (const entry of entries) {
|
|
2816
2835
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
2817
|
-
const filePath =
|
|
2818
|
-
const content =
|
|
2836
|
+
const filePath = join13(commandsDir, entry.name);
|
|
2837
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
2819
2838
|
const frontmatter = parseFrontmatter2(content);
|
|
2820
2839
|
const fallbackName = basename2(entry.name, ".md");
|
|
2821
2840
|
commands.push(buildCommand(frontmatter, content, fallbackName));
|
|
@@ -2834,10 +2853,10 @@ var SkillCommandSource = class {
|
|
|
2834
2853
|
getCommands() {
|
|
2835
2854
|
if (this.cachedCommands) return this.cachedCommands;
|
|
2836
2855
|
const sources = [
|
|
2837
|
-
scanSkillsDir(
|
|
2838
|
-
scanCommandsDir(
|
|
2839
|
-
scanSkillsDir(
|
|
2840
|
-
scanSkillsDir(
|
|
2856
|
+
scanSkillsDir(join13(this.cwd, ".claude", "skills")),
|
|
2857
|
+
scanCommandsDir(join13(this.cwd, ".claude", "commands")),
|
|
2858
|
+
scanSkillsDir(join13(this.home, ".robota", "skills")),
|
|
2859
|
+
scanSkillsDir(join13(this.cwd, ".agents", "skills"))
|
|
2841
2860
|
];
|
|
2842
2861
|
const seen = /* @__PURE__ */ new Set();
|
|
2843
2862
|
const merged = [];
|