shark-ai 0.4.20 → 0.4.22
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/bin/shark.js +4604 -156
- package/dist/bin/shark.js.map +1 -1
- package/dist/{chunk-CUUMQTCW.js → chunk-NKE5GNDJ.js} +323 -48
- package/dist/chunk-NKE5GNDJ.js.map +1 -0
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/dispatching-parallel-agents/SKILL.md +65 -0
- package/skills/subagent-driven-development/SKILL.md +60 -0
- package/dist/chunk-CUUMQTCW.js.map +0 -1
- package/dist/chunk-JLE7NOEG.js +0 -292
- package/dist/chunk-JLE7NOEG.js.map +0 -1
- package/dist/chunk-O6YG3GZN.js +0 -3828
- package/dist/chunk-O6YG3GZN.js.map +0 -1
- package/dist/developer-agent-7KAQMMOK.js +0 -9
- package/dist/developer-agent-7KAQMMOK.js.map +0 -1
package/dist/bin/shark.js
CHANGED
|
@@ -1,49 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
configCommand,
|
|
4
|
-
loginCommand
|
|
5
|
-
} from "../chunk-JLE7NOEG.js";
|
|
6
|
-
import {
|
|
7
|
-
AGENT_RESPONSE_JSON_SCHEMA,
|
|
8
|
-
ProviderResolver,
|
|
9
|
-
TechStackEnum,
|
|
10
|
-
astAddClass,
|
|
11
|
-
astAddDecorator,
|
|
12
|
-
astAddFunction,
|
|
13
|
-
astAddImport,
|
|
14
|
-
astAddInterface,
|
|
15
|
-
astAddMethod,
|
|
16
|
-
astAddProperty,
|
|
17
|
-
astAddTypeAlias,
|
|
18
|
-
astGetMethod,
|
|
19
|
-
astGetProperty,
|
|
20
|
-
astGrepRewrite,
|
|
21
|
-
astGrepSearch,
|
|
22
|
-
astListStructure,
|
|
23
|
-
astModifyMethod,
|
|
24
|
-
astModifyProperty,
|
|
25
|
-
astOrganizeImports,
|
|
26
|
-
astRemoveFunction,
|
|
27
|
-
astRemoveImport,
|
|
28
|
-
astRemoveMethod,
|
|
29
|
-
astRemoveProperty,
|
|
30
|
-
conversationManager,
|
|
31
|
-
handleListFiles,
|
|
32
|
-
handleReadFile,
|
|
33
|
-
handleRunCommand,
|
|
34
|
-
handleSearchCode,
|
|
35
|
-
handleSearchFile,
|
|
36
|
-
interactiveDeveloperAgent,
|
|
37
|
-
replaceLineRange,
|
|
38
|
-
startSmartReplace,
|
|
39
|
-
workflowManager
|
|
40
|
-
} from "../chunk-O6YG3GZN.js";
|
|
41
2
|
import {
|
|
42
3
|
ConfigManager,
|
|
43
4
|
FileLogger,
|
|
5
|
+
authenticate,
|
|
44
6
|
colors,
|
|
7
|
+
configCommand,
|
|
8
|
+
loginCommand,
|
|
9
|
+
tokenStorage,
|
|
45
10
|
tui
|
|
46
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-NKE5GNDJ.js";
|
|
47
12
|
|
|
48
13
|
// src/core/error/crash-handler.ts
|
|
49
14
|
import fs from "fs";
|
|
@@ -102,10 +67,123 @@ Node: ${process.version}
|
|
|
102
67
|
var crashHandler = CrashHandler.getInstance();
|
|
103
68
|
|
|
104
69
|
// src/bin/shark.ts
|
|
105
|
-
import { Command as
|
|
70
|
+
import { Command as Command7 } from "commander";
|
|
106
71
|
|
|
107
72
|
// src/commands/init.ts
|
|
108
73
|
import { Command } from "commander";
|
|
74
|
+
|
|
75
|
+
// src/core/workflow/workflow-manager.ts
|
|
76
|
+
import fs2 from "fs/promises";
|
|
77
|
+
import path2 from "path";
|
|
78
|
+
|
|
79
|
+
// src/core/workflow/shark-workflow.schema.ts
|
|
80
|
+
import { z } from "zod";
|
|
81
|
+
var TechStackEnum = z.enum([
|
|
82
|
+
"react",
|
|
83
|
+
"nextjs",
|
|
84
|
+
"angular",
|
|
85
|
+
"vue",
|
|
86
|
+
"node-ts",
|
|
87
|
+
"python",
|
|
88
|
+
"dotnet",
|
|
89
|
+
"java",
|
|
90
|
+
"unknown"
|
|
91
|
+
]);
|
|
92
|
+
var WorkflowStageEnum = z.enum([
|
|
93
|
+
"business_analysis",
|
|
94
|
+
"specification",
|
|
95
|
+
"architecture",
|
|
96
|
+
"development",
|
|
97
|
+
"verification",
|
|
98
|
+
"deployment"
|
|
99
|
+
]);
|
|
100
|
+
var StageStatusEnum = z.enum([
|
|
101
|
+
"pending",
|
|
102
|
+
"in_progress",
|
|
103
|
+
"completed",
|
|
104
|
+
"failed",
|
|
105
|
+
"skipped"
|
|
106
|
+
]);
|
|
107
|
+
var WorkflowSchema = z.object({
|
|
108
|
+
projectId: z.string().uuid(),
|
|
109
|
+
projectName: z.string().min(1),
|
|
110
|
+
techStack: TechStackEnum.default("unknown"),
|
|
111
|
+
currentStage: WorkflowStageEnum.default("business_analysis"),
|
|
112
|
+
stageStatus: StageStatusEnum.default("pending"),
|
|
113
|
+
lastUpdated: z.string().datetime(),
|
|
114
|
+
// ISO 8601
|
|
115
|
+
conversationId: z.string().optional(),
|
|
116
|
+
conversations: z.record(z.string(), z.string()).optional(),
|
|
117
|
+
// agentType -> conversationId
|
|
118
|
+
artifacts: z.array(z.string()).default([]),
|
|
119
|
+
// Extensible metadata bag
|
|
120
|
+
metadata: z.record(z.unknown()).optional()
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// src/core/workflow/workflow-manager.ts
|
|
124
|
+
var WorkflowManager = class _WorkflowManager {
|
|
125
|
+
static instance;
|
|
126
|
+
filename = "shark-workflow.json";
|
|
127
|
+
tmpFilename = ".shark-workflow.tmp";
|
|
128
|
+
constructor() {
|
|
129
|
+
}
|
|
130
|
+
static getInstance() {
|
|
131
|
+
if (!_WorkflowManager.instance) {
|
|
132
|
+
_WorkflowManager.instance = new _WorkflowManager();
|
|
133
|
+
}
|
|
134
|
+
return _WorkflowManager.instance;
|
|
135
|
+
}
|
|
136
|
+
getFilePath() {
|
|
137
|
+
return path2.join(process.cwd(), this.filename);
|
|
138
|
+
}
|
|
139
|
+
getTmpFilePath() {
|
|
140
|
+
return path2.join(process.cwd(), this.tmpFilename);
|
|
141
|
+
}
|
|
142
|
+
async save(state) {
|
|
143
|
+
const parsed = WorkflowSchema.safeParse(state);
|
|
144
|
+
if (!parsed.success) {
|
|
145
|
+
throw new Error(`Invalid workflow state: ${parsed.error.message}`);
|
|
146
|
+
}
|
|
147
|
+
const filePath = this.getFilePath();
|
|
148
|
+
const tmpPath = this.getTmpFilePath();
|
|
149
|
+
const data = JSON.stringify(parsed.data, null, 2);
|
|
150
|
+
try {
|
|
151
|
+
await fs2.writeFile(tmpPath, data, "utf-8");
|
|
152
|
+
await fs2.rename(tmpPath, filePath);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
throw new Error(`Failed to save workflow state atomically: ${error.message}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async load() {
|
|
158
|
+
const filePath = this.getFilePath();
|
|
159
|
+
try {
|
|
160
|
+
try {
|
|
161
|
+
await fs2.access(filePath);
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
166
|
+
const json = JSON.parse(content);
|
|
167
|
+
const parsed = WorkflowSchema.safeParse(json);
|
|
168
|
+
if (parsed.success) {
|
|
169
|
+
return parsed.data;
|
|
170
|
+
} else {
|
|
171
|
+
console.warn(colors.warning(`\u26A0\uFE0F Corrupted workflow file detected: ${parsed.error.message}`));
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.warn(colors.warning(`\u26A0\uFE0F Failed to load workflow state: ${error.message}`));
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Helper to get current state or default if none exists
|
|
180
|
+
async getOrInitState() {
|
|
181
|
+
return await this.load();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
var workflowManager = WorkflowManager.getInstance();
|
|
185
|
+
|
|
186
|
+
// src/commands/init.ts
|
|
109
187
|
import { randomUUID } from "crypto";
|
|
110
188
|
var initAction = async () => {
|
|
111
189
|
tui.intro("Shark Project Initialization");
|
|
@@ -125,72 +203,4434 @@ var initAction = async () => {
|
|
|
125
203
|
{ value: "exit", label: "\u274C Exit" }
|
|
126
204
|
]
|
|
127
205
|
});
|
|
128
|
-
if (tui.isCancel(action) || action === "exit") {
|
|
129
|
-
tui.outro("See you later! \u{1F44B}");
|
|
130
|
-
return;
|
|
206
|
+
if (tui.isCancel(action) || action === "exit") {
|
|
207
|
+
tui.outro("See you later! \u{1F44B}");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (action === "resume") {
|
|
211
|
+
tui.log.success(`Resuming work on ${colors.primary(existingState.projectName)}...`);
|
|
212
|
+
tui.outro(`To continue, run: "shark agent" (Context loaded)`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const projectName = await tui.text({
|
|
217
|
+
message: "What is the name of your project?",
|
|
218
|
+
placeholder: "e.g. My Awesome App",
|
|
219
|
+
validate: (value) => {
|
|
220
|
+
if (!value) return "Project name is required";
|
|
221
|
+
if (value.trim().length < 2) return "Project name must be at least 2 characters";
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
if (tui.isCancel(projectName)) {
|
|
225
|
+
tui.outro("Initialization cancelled.");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const techStackOptions = TechStackEnum.options.map((stack) => ({
|
|
229
|
+
value: stack,
|
|
230
|
+
label: stack === "node-ts" ? "Node.js (TypeScript)" : stack === "nextjs" ? "Next.js" : stack.charAt(0).toUpperCase() + stack.slice(1)
|
|
231
|
+
}));
|
|
232
|
+
const techStack = await tui.select({
|
|
233
|
+
message: "Select your technology stack:",
|
|
234
|
+
options: techStackOptions
|
|
235
|
+
});
|
|
236
|
+
if (tui.isCancel(techStack)) {
|
|
237
|
+
tui.outro("Initialization cancelled.");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const spinner = tui.spinner();
|
|
241
|
+
spinner.start("Initializing project workflow...");
|
|
242
|
+
try {
|
|
243
|
+
const newState = {
|
|
244
|
+
projectId: randomUUID(),
|
|
245
|
+
projectName,
|
|
246
|
+
techStack,
|
|
247
|
+
currentStage: "business_analysis",
|
|
248
|
+
stageStatus: "pending",
|
|
249
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
250
|
+
artifacts: [],
|
|
251
|
+
metadata: {
|
|
252
|
+
initializedBy: "shark-cli",
|
|
253
|
+
version: "0.0.1"
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
await workflowManager.save(newState);
|
|
257
|
+
spinner.stop("Project workflow created!");
|
|
258
|
+
tui.log.success(`Project ${colors.primary(projectName)} initialized successfully.`);
|
|
259
|
+
tui.log.message(`Your Project ID: ${colors.dim(newState.projectId)}`);
|
|
260
|
+
tui.outro('Ready to start! Run "shark agent" to begin analyzing requirements.');
|
|
261
|
+
} catch (error) {
|
|
262
|
+
spinner.stop("Initialization failed.", 1);
|
|
263
|
+
tui.log.error(error.message);
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
var initCommand = new Command("init").description("Initialize a new Shark project").action(initAction);
|
|
268
|
+
|
|
269
|
+
// src/commands/dev.ts
|
|
270
|
+
import { Command as Command2 } from "commander";
|
|
271
|
+
|
|
272
|
+
// src/core/agents/agent-response-parser.ts
|
|
273
|
+
import { z as z2 } from "zod";
|
|
274
|
+
var AgentActionSchema = z2.object({
|
|
275
|
+
type: z2.enum([
|
|
276
|
+
"create_file",
|
|
277
|
+
"modify_file",
|
|
278
|
+
"list_files",
|
|
279
|
+
"search_file",
|
|
280
|
+
"search_code",
|
|
281
|
+
"read_file",
|
|
282
|
+
"delete_file",
|
|
283
|
+
"list_structure",
|
|
284
|
+
"modify_ast",
|
|
285
|
+
"search_ast",
|
|
286
|
+
"run_command",
|
|
287
|
+
"talk_with_user",
|
|
288
|
+
"use_mcp_tool",
|
|
289
|
+
"activate_skill",
|
|
290
|
+
"define_subagent",
|
|
291
|
+
"invoke_subagent",
|
|
292
|
+
"send_message",
|
|
293
|
+
"manage_subagents",
|
|
294
|
+
"complete_task",
|
|
295
|
+
"wait",
|
|
296
|
+
"ast_list_structure",
|
|
297
|
+
"ast_get_method",
|
|
298
|
+
"ast_add_method",
|
|
299
|
+
"ast_modify_method",
|
|
300
|
+
"ast_remove_method",
|
|
301
|
+
"ast_add_class",
|
|
302
|
+
"ast_get_property",
|
|
303
|
+
"ast_add_property",
|
|
304
|
+
"ast_modify_property",
|
|
305
|
+
"ast_remove_property",
|
|
306
|
+
"ast_add_decorator",
|
|
307
|
+
"ast_add_interface",
|
|
308
|
+
"ast_add_type_alias",
|
|
309
|
+
"ast_add_function",
|
|
310
|
+
"ast_remove_function",
|
|
311
|
+
"ast_add_import",
|
|
312
|
+
"ast_remove_import",
|
|
313
|
+
"ast_organize_imports"
|
|
314
|
+
]),
|
|
315
|
+
path: z2.string().nullable().optional(),
|
|
316
|
+
// Nullable for strict mode combatibility
|
|
317
|
+
content: z2.string().nullable().optional(),
|
|
318
|
+
line_range: z2.array(z2.number()).nullable().optional(),
|
|
319
|
+
target_content: z2.string().nullable().optional(),
|
|
320
|
+
command: z2.string().nullable().optional(),
|
|
321
|
+
tool_name: z2.string().nullable().optional(),
|
|
322
|
+
tool_args: z2.string().nullable().optional(),
|
|
323
|
+
// JSON string argument
|
|
324
|
+
// search_code fields
|
|
325
|
+
query: z2.string().nullable().optional(),
|
|
326
|
+
is_regex: z2.boolean().nullable().optional(),
|
|
327
|
+
// AST-Grep fields
|
|
328
|
+
pattern: z2.string().nullable().optional(),
|
|
329
|
+
fix: z2.string().nullable().optional(),
|
|
330
|
+
language: z2.string().nullable().optional(),
|
|
331
|
+
file_path: z2.string().nullable().optional(),
|
|
332
|
+
// Alias for path in ast-grep actions
|
|
333
|
+
// New AST Tool Specific Fields
|
|
334
|
+
class_name: z2.string().nullable().optional(),
|
|
335
|
+
method_name: z2.string().nullable().optional(),
|
|
336
|
+
method_code: z2.string().nullable().optional(),
|
|
337
|
+
property_name: z2.string().nullable().optional(),
|
|
338
|
+
property_code: z2.string().nullable().optional(),
|
|
339
|
+
extends_class: z2.string().nullable().optional(),
|
|
340
|
+
implements_interfaces: z2.array(z2.string()).nullable().optional(),
|
|
341
|
+
decorator_code: z2.string().nullable().optional(),
|
|
342
|
+
interface_code: z2.string().nullable().optional(),
|
|
343
|
+
type_code: z2.string().nullable().optional(),
|
|
344
|
+
function_name: z2.string().nullable().optional(),
|
|
345
|
+
function_code: z2.string().nullable().optional(),
|
|
346
|
+
import_statement: z2.string().nullable().optional(),
|
|
347
|
+
module_path: z2.string().nullable().optional(),
|
|
348
|
+
new_body: z2.string().nullable().optional(),
|
|
349
|
+
// Preview confirmation
|
|
350
|
+
confirmed: z2.boolean().nullable().optional(),
|
|
351
|
+
start_anchor: z2.string().nullable().optional(),
|
|
352
|
+
end_anchor: z2.string().nullable().optional(),
|
|
353
|
+
// Superpowers fields
|
|
354
|
+
skill_name: z2.string().nullable().optional(),
|
|
355
|
+
duration_seconds: z2.number().nullable().optional(),
|
|
356
|
+
Subagents: z2.array(z2.object({
|
|
357
|
+
TypeName: z2.string(),
|
|
358
|
+
Role: z2.string(),
|
|
359
|
+
Prompt: z2.string()
|
|
360
|
+
})).nullable().optional(),
|
|
361
|
+
Recipient: z2.string().nullable().optional(),
|
|
362
|
+
Message: z2.string().nullable().optional(),
|
|
363
|
+
Action: z2.enum(["list", "kill", "kill_all"]).nullable().optional(),
|
|
364
|
+
ConversationIds: z2.array(z2.string()).nullable().optional(),
|
|
365
|
+
// define_subagent fields
|
|
366
|
+
name: z2.string().nullable().optional(),
|
|
367
|
+
description: z2.string().nullable().optional(),
|
|
368
|
+
system_prompt: z2.string().nullable().optional(),
|
|
369
|
+
enable_write_tools: z2.boolean().nullable().optional(),
|
|
370
|
+
enable_subagent_tools: z2.boolean().nullable().optional(),
|
|
371
|
+
enable_mcp_tools: z2.boolean().nullable().optional()
|
|
372
|
+
});
|
|
373
|
+
var AgentCommandSchema = z2.object({
|
|
374
|
+
command: z2.string(),
|
|
375
|
+
description: z2.string(),
|
|
376
|
+
critical: z2.boolean()
|
|
377
|
+
});
|
|
378
|
+
var AgentResponseSchema = z2.object({
|
|
379
|
+
action: AgentActionSchema.nullable().optional(),
|
|
380
|
+
actions: z2.array(AgentActionSchema).default([]),
|
|
381
|
+
// Maintain backward compatibility
|
|
382
|
+
commands: z2.array(AgentCommandSchema).optional(),
|
|
383
|
+
// Maintain backward compatibility
|
|
384
|
+
summary: z2.string().optional(),
|
|
385
|
+
// Legacy fields handling for smooth transition/fallback
|
|
386
|
+
message: z2.string().optional(),
|
|
387
|
+
conversation_id: z2.string().optional()
|
|
388
|
+
});
|
|
389
|
+
function parseAgentResponse(rawResponse) {
|
|
390
|
+
FileLogger.log("PARSER", "Parsing Agent Response", { rawType: typeof rawResponse });
|
|
391
|
+
let parsedObj = {};
|
|
392
|
+
let conversation_id;
|
|
393
|
+
if (typeof rawResponse === "string") {
|
|
394
|
+
FileLogger.log("PARSER", "Type String", { length: rawResponse.length });
|
|
395
|
+
try {
|
|
396
|
+
parsedObj = extractFirstJson(rawResponse);
|
|
397
|
+
} catch (e) {
|
|
398
|
+
FileLogger.log("PARSER", "String Parse Failed", { error: e.message });
|
|
399
|
+
const errMsg = e.message;
|
|
400
|
+
const cleanRaw = rawResponse.trim();
|
|
401
|
+
if (cleanRaw === "") {
|
|
402
|
+
const systemMsg2 = `[SYSTEM ERROR]: O modelo retornou uma resposta vazia. Por favor, tente novamente e forne\xE7a uma a\xE7\xE3o JSON v\xE1lida.`;
|
|
403
|
+
return {
|
|
404
|
+
action: {
|
|
405
|
+
type: "talk_with_user",
|
|
406
|
+
content: systemMsg2,
|
|
407
|
+
path: ""
|
|
408
|
+
},
|
|
409
|
+
actions: [{
|
|
410
|
+
type: "talk_with_user",
|
|
411
|
+
content: systemMsg2,
|
|
412
|
+
path: ""
|
|
413
|
+
}],
|
|
414
|
+
message: systemMsg2
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
const looksLikeJson = cleanRaw.startsWith("{") || cleanRaw.startsWith("[");
|
|
418
|
+
if (!looksLikeJson) {
|
|
419
|
+
return {
|
|
420
|
+
action: {
|
|
421
|
+
type: "talk_with_user",
|
|
422
|
+
content: rawResponse,
|
|
423
|
+
path: ""
|
|
424
|
+
},
|
|
425
|
+
actions: [{
|
|
426
|
+
type: "talk_with_user",
|
|
427
|
+
content: rawResponse,
|
|
428
|
+
path: ""
|
|
429
|
+
}],
|
|
430
|
+
message: rawResponse
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const isTruncated = errMsg.includes("Unterminated string") || errMsg.includes("Unexpected end of JSON input");
|
|
434
|
+
const charCount = rawResponse.length;
|
|
435
|
+
const safeLimit = Math.floor(charCount * 0.9);
|
|
436
|
+
const systemMsg = isTruncated ? `[SYSTEM ERROR]: Sua resposta anterior foi cortada/truncada antes do final devido ao limite m\xE1ximo de tokens de sa\xEDda (output token limit) ap\xF3s atingir ${charCount} caracteres. O JSON ficou incompleto: ${errMsg}. Por favor, envie uma nova resposta com formato JSON completo e v\xE1lido (n\xE3o tente apenas completar o JSON anterior). Continue o trabalho l\xF3gico da tarefa de forma mais curta ou incremental (ex: criando apenas o esqueleto/estrutura b\xE1sica ou escrevendo uma parte menor do arquivo de cada vez). Garanta que o tamanho total desta nova resposta JSON seja menor que ${safeLimit} caracteres para evitar novos cortes.` : `[SYSTEM ERROR]: Falha ao parsear o JSON de resposta: ${errMsg}.`;
|
|
437
|
+
return {
|
|
438
|
+
action: {
|
|
439
|
+
type: "talk_with_user",
|
|
440
|
+
content: systemMsg,
|
|
441
|
+
path: ""
|
|
442
|
+
},
|
|
443
|
+
actions: [{
|
|
444
|
+
type: "talk_with_user",
|
|
445
|
+
content: systemMsg,
|
|
446
|
+
path: ""
|
|
447
|
+
}],
|
|
448
|
+
message: systemMsg
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
} else if (typeof rawResponse === "object" && rawResponse !== null) {
|
|
452
|
+
const anyResp = rawResponse;
|
|
453
|
+
conversation_id = anyResp.conversation_id;
|
|
454
|
+
FileLogger.log("PARSER", "Type Object", {
|
|
455
|
+
hasContent: !!anyResp.content,
|
|
456
|
+
hasMessage: !!anyResp.message,
|
|
457
|
+
messageType: typeof anyResp.message
|
|
458
|
+
});
|
|
459
|
+
const stringContent = anyResp.content || anyResp.message;
|
|
460
|
+
if (stringContent && typeof stringContent === "string") {
|
|
461
|
+
try {
|
|
462
|
+
const parsedInside = extractFirstJson(stringContent);
|
|
463
|
+
if (typeof parsedInside === "object" && parsedInside !== null) {
|
|
464
|
+
parsedObj = parsedInside;
|
|
465
|
+
FileLogger.log("PARSER", "Inner JSON Parsed", { keys: Object.keys(parsedObj) });
|
|
466
|
+
} else {
|
|
467
|
+
parsedObj = rawResponse;
|
|
468
|
+
FileLogger.log("PARSER", "Inner JSON was primitive");
|
|
469
|
+
}
|
|
470
|
+
} catch (e) {
|
|
471
|
+
FileLogger.log("PARSER", "Inner JSON Parse Error", { error: e.message });
|
|
472
|
+
const errMsg = e.message;
|
|
473
|
+
const cleanContent = stringContent.trim();
|
|
474
|
+
const looksLikeJson = cleanContent.startsWith("{") || cleanContent.startsWith("[");
|
|
475
|
+
if (looksLikeJson) {
|
|
476
|
+
const isTruncated = errMsg.includes("Unterminated string") || errMsg.includes("Unexpected end of JSON input");
|
|
477
|
+
const charCount = stringContent.length;
|
|
478
|
+
const safeLimit = Math.floor(charCount * 0.9);
|
|
479
|
+
const systemMsg = isTruncated ? `[SYSTEM ERROR]: Sua resposta anterior foi cortada/truncada antes do final devido ao limite m\xE1ximo de tokens de sa\xEDda (output token limit) ap\xF3s atingir ${charCount} caracteres. O JSON ficou incompleto: ${errMsg}. Por favor, envie uma nova resposta com formato JSON completo e v\xE1lido (n\xE3o tente apenas completar o JSON anterior). Continue o trabalho l\xF3gico da tarefa de forma mais curta ou incremental (ex: criando apenas o esqueleto/estrutura b\xE1sica ou escrevendo uma parte menor do arquivo de cada vez). Garanta que o tamanho total desta nova resposta JSON seja menor que ${safeLimit} caracteres para evitar novos cortes.` : `[SYSTEM ERROR]: Falha ao parsear o JSON de resposta: ${errMsg}.`;
|
|
480
|
+
parsedObj = {
|
|
481
|
+
action: {
|
|
482
|
+
type: "talk_with_user",
|
|
483
|
+
content: systemMsg,
|
|
484
|
+
path: ""
|
|
485
|
+
},
|
|
486
|
+
actions: [{
|
|
487
|
+
type: "talk_with_user",
|
|
488
|
+
content: systemMsg,
|
|
489
|
+
path: ""
|
|
490
|
+
}],
|
|
491
|
+
summary: "Parsing failed due to truncated JSON response"
|
|
492
|
+
};
|
|
493
|
+
} else {
|
|
494
|
+
parsedObj = rawResponse;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
parsedObj = rawResponse;
|
|
499
|
+
}
|
|
500
|
+
if (!parsedObj.actions && !parsedObj.action) {
|
|
501
|
+
parsedObj = rawResponse;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
let normalizedAction = parsedObj.action;
|
|
505
|
+
let normalizedActions = parsedObj.actions;
|
|
506
|
+
if (!normalizedAction && (!normalizedActions || normalizedActions.length === 0) && parsedObj && typeof parsedObj === "object" && typeof parsedObj.type === "string") {
|
|
507
|
+
const validTypes = [
|
|
508
|
+
"create_file",
|
|
509
|
+
"modify_file",
|
|
510
|
+
"list_files",
|
|
511
|
+
"search_file",
|
|
512
|
+
"search_code",
|
|
513
|
+
"read_file",
|
|
514
|
+
"delete_file",
|
|
515
|
+
"talk_with_user",
|
|
516
|
+
"use_mcp_tool",
|
|
517
|
+
"list_structure",
|
|
518
|
+
"modify_ast",
|
|
519
|
+
"search_ast",
|
|
520
|
+
"run_command",
|
|
521
|
+
"activate_skill",
|
|
522
|
+
"define_subagent",
|
|
523
|
+
"invoke_subagent",
|
|
524
|
+
"send_message",
|
|
525
|
+
"manage_subagents",
|
|
526
|
+
"complete_task"
|
|
527
|
+
];
|
|
528
|
+
if (validTypes.includes(parsedObj.type)) {
|
|
529
|
+
normalizedAction = parsedObj;
|
|
530
|
+
normalizedActions = [parsedObj];
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (!normalizedAction && normalizedActions && normalizedActions.length > 0) {
|
|
534
|
+
normalizedAction = normalizedActions[0];
|
|
535
|
+
} else if (normalizedAction && (!normalizedActions || normalizedActions.length === 0)) {
|
|
536
|
+
normalizedActions = [normalizedAction];
|
|
537
|
+
}
|
|
538
|
+
if (!normalizedAction && !normalizedActions) {
|
|
539
|
+
FileLogger.log("PARSER", "No Action/Actions Found - Constructing Default");
|
|
540
|
+
const content = parsedObj.message || (typeof parsedObj === "object" ? JSON.stringify(parsedObj) : String(parsedObj));
|
|
541
|
+
normalizedAction = {
|
|
542
|
+
type: "talk_with_user",
|
|
543
|
+
content,
|
|
544
|
+
path: ""
|
|
545
|
+
};
|
|
546
|
+
normalizedActions = [normalizedAction];
|
|
547
|
+
}
|
|
548
|
+
let normalizedCommands = [];
|
|
549
|
+
if (Array.isArray(parsedObj.commands)) {
|
|
550
|
+
normalizedCommands = parsedObj.commands.map((cmd) => {
|
|
551
|
+
if (typeof cmd === "string") {
|
|
552
|
+
return {
|
|
553
|
+
command: cmd,
|
|
554
|
+
description: `Execute ${cmd}`,
|
|
555
|
+
critical: false
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
if (cmd && typeof cmd === "object") {
|
|
559
|
+
return {
|
|
560
|
+
command: cmd.command || "",
|
|
561
|
+
description: cmd.description || `Execute ${cmd.command || ""}`,
|
|
562
|
+
critical: cmd.critical === true
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}).filter(Boolean);
|
|
567
|
+
}
|
|
568
|
+
const result = {
|
|
569
|
+
action: normalizedAction,
|
|
570
|
+
actions: normalizedActions,
|
|
571
|
+
commands: normalizedCommands,
|
|
572
|
+
summary: parsedObj.summary || "",
|
|
573
|
+
conversation_id,
|
|
574
|
+
message: parsedObj.summary || "Agent Action"
|
|
575
|
+
// Backward compatibility
|
|
576
|
+
};
|
|
577
|
+
FileLogger.log("PARSER", "Final Result Constructed", { hasAction: !!result.action });
|
|
578
|
+
try {
|
|
579
|
+
return AgentResponseSchema.parse(result);
|
|
580
|
+
} catch (e) {
|
|
581
|
+
FileLogger.log("PARSER", "Schema Validation Failed", { error: e.message });
|
|
582
|
+
throw e;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function extractFirstJson(str) {
|
|
586
|
+
try {
|
|
587
|
+
return JSON.parse(str);
|
|
588
|
+
} catch (e) {
|
|
589
|
+
const firstOpen = str.indexOf("{");
|
|
590
|
+
if (firstOpen === -1) throw e;
|
|
591
|
+
let balance = 0;
|
|
592
|
+
let inString = false;
|
|
593
|
+
let escape = false;
|
|
594
|
+
for (let i = firstOpen; i < str.length; i++) {
|
|
595
|
+
const char = str[i];
|
|
596
|
+
if (escape) {
|
|
597
|
+
escape = false;
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
if (char === "\\") {
|
|
601
|
+
escape = true;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (char === '"') {
|
|
605
|
+
inString = !inString;
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
if (!inString) {
|
|
609
|
+
if (char === "{") balance++;
|
|
610
|
+
else if (char === "}") {
|
|
611
|
+
balance--;
|
|
612
|
+
if (balance === 0) {
|
|
613
|
+
const potentialJson = str.substring(firstOpen, i + 1);
|
|
614
|
+
try {
|
|
615
|
+
return JSON.parse(potentialJson);
|
|
616
|
+
} catch (innerE) {
|
|
617
|
+
throw e;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
throw e;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/core/api/stackspot-client.ts
|
|
628
|
+
var AuthError = class extends Error {
|
|
629
|
+
constructor(message) {
|
|
630
|
+
super(message);
|
|
631
|
+
this.name = "AuthError";
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
var STACKSPOT_AGENT_API_BASE = "https://genai-inference-app.stackspot.com";
|
|
635
|
+
async function ensureValidToken(realm) {
|
|
636
|
+
let creds = await tokenStorage.getCredentials(realm);
|
|
637
|
+
if (!creds?.accessToken) {
|
|
638
|
+
throw new AuthError(`Authentication required for realm '${realm}'.
|
|
639
|
+
Please run 'shark login' to authenticate.`);
|
|
640
|
+
}
|
|
641
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
642
|
+
const buffer = 300;
|
|
643
|
+
if (creds.expiresAt && creds.clientId && creds.clientKey) {
|
|
644
|
+
if (now > creds.expiresAt - buffer) {
|
|
645
|
+
try {
|
|
646
|
+
const newTokens = await authenticate(realm, creds.clientId, creds.clientKey);
|
|
647
|
+
await tokenStorage.saveToken(
|
|
648
|
+
realm,
|
|
649
|
+
newTokens.access_token,
|
|
650
|
+
creds.clientId,
|
|
651
|
+
creds.clientKey,
|
|
652
|
+
newTokens.expires_in
|
|
653
|
+
);
|
|
654
|
+
return newTokens.access_token;
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.warn(colors.warning(`\u26A0\uFE0F Failed to auto-refresh token: ${error.message}`));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return creds.accessToken;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// src/core/api/sse-client.ts
|
|
664
|
+
var SSEClient = class {
|
|
665
|
+
/**
|
|
666
|
+
* Streams agent response using Server-Sent Events.
|
|
667
|
+
*
|
|
668
|
+
* @param url - The SSE endpoint URL
|
|
669
|
+
* @param requestPayload - The request payload to POST
|
|
670
|
+
* @param headers - Request headers (including Authorization)
|
|
671
|
+
* @param callbacks - Event callbacks for chunks, completion, and errors
|
|
672
|
+
*/
|
|
673
|
+
async streamAgentResponse(url, requestPayload, headers, callbacks = {}) {
|
|
674
|
+
const { onChunk, onComplete, onError } = callbacks;
|
|
675
|
+
FileLogger.log("SSE", `Starting Request to ${url}`, {
|
|
676
|
+
headers,
|
|
677
|
+
payload: requestPayload
|
|
678
|
+
});
|
|
679
|
+
try {
|
|
680
|
+
const response = await fetch(url, {
|
|
681
|
+
method: "POST",
|
|
682
|
+
headers: {
|
|
683
|
+
...headers,
|
|
684
|
+
"Content-Type": "application/json"
|
|
685
|
+
},
|
|
686
|
+
body: JSON.stringify(requestPayload)
|
|
687
|
+
});
|
|
688
|
+
FileLogger.log("SSE", `Response Status: ${response.status} ${response.statusText}`);
|
|
689
|
+
const responseHeaders = {};
|
|
690
|
+
response.headers.forEach((value, key) => {
|
|
691
|
+
responseHeaders[key] = value;
|
|
692
|
+
});
|
|
693
|
+
FileLogger.log("SSE", "Response Headers", responseHeaders);
|
|
694
|
+
if (!response.ok) {
|
|
695
|
+
const errorText = await response.text();
|
|
696
|
+
FileLogger.log("SSE", "Response Error Body", errorText);
|
|
697
|
+
throw new Error(`SSE request failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
698
|
+
}
|
|
699
|
+
if (!response.body) {
|
|
700
|
+
throw new Error("Response body is null");
|
|
701
|
+
}
|
|
702
|
+
const contentType = response.headers.get("content-type") || "";
|
|
703
|
+
const isJson = contentType.includes("application/json");
|
|
704
|
+
if (isJson) {
|
|
705
|
+
const jsonBody = await response.json();
|
|
706
|
+
FileLogger.log("SSE", "Received Non-Streaming JSON Response", { length: JSON.stringify(jsonBody).length });
|
|
707
|
+
let content = "";
|
|
708
|
+
if (typeof jsonBody === "string") content = jsonBody;
|
|
709
|
+
else if (jsonBody.message) content = jsonBody.message;
|
|
710
|
+
else if (jsonBody.choices?.[0]?.message?.content) content = jsonBody.choices[0].message.content;
|
|
711
|
+
else content = JSON.stringify(jsonBody);
|
|
712
|
+
if (onChunk) onChunk(content);
|
|
713
|
+
if (onComplete) onComplete(content, jsonBody);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const reader = response.body.getReader();
|
|
717
|
+
const decoder = new TextDecoder();
|
|
718
|
+
let buffer = "";
|
|
719
|
+
let fullMessage = "";
|
|
720
|
+
let metadata = {};
|
|
721
|
+
while (true) {
|
|
722
|
+
const { done, value } = await reader.read();
|
|
723
|
+
if (done) {
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
buffer += decoder.decode(value, { stream: true });
|
|
727
|
+
const lines = buffer.split("\n");
|
|
728
|
+
buffer = lines.pop() || "";
|
|
729
|
+
for (const line of lines) {
|
|
730
|
+
if (line.startsWith("data:")) {
|
|
731
|
+
const data = line.slice(5).trim();
|
|
732
|
+
if (data === "[DONE]") {
|
|
733
|
+
FileLogger.log("SSE", "Stream Complete [DONE]", { fullMessage, metadata });
|
|
734
|
+
if (onComplete) {
|
|
735
|
+
onComplete(fullMessage, metadata);
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
try {
|
|
740
|
+
const parsed = JSON.parse(data);
|
|
741
|
+
const chunk = parsed.message || parsed.content || data;
|
|
742
|
+
fullMessage += chunk;
|
|
743
|
+
if (parsed.conversation_id) {
|
|
744
|
+
metadata.conversation_id = parsed.conversation_id;
|
|
745
|
+
}
|
|
746
|
+
if (onChunk) {
|
|
747
|
+
onChunk(chunk);
|
|
748
|
+
}
|
|
749
|
+
} catch (parseError) {
|
|
750
|
+
fullMessage += data;
|
|
751
|
+
if (onChunk) {
|
|
752
|
+
onChunk(data);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
FileLogger.log("SSE", "Stream Ended Naturally", { fullMessage, metadata });
|
|
759
|
+
if (onComplete) {
|
|
760
|
+
onComplete(fullMessage, metadata);
|
|
761
|
+
}
|
|
762
|
+
} catch (error) {
|
|
763
|
+
FileLogger.log("SSE", "Stream Error", error);
|
|
764
|
+
if (onError) {
|
|
765
|
+
onError(error instanceof Error ? error : new Error(String(error)));
|
|
766
|
+
} else {
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
var sseClient = new SSEClient();
|
|
773
|
+
|
|
774
|
+
// src/core/auth/get-active-realm.ts
|
|
775
|
+
async function getActiveRealm() {
|
|
776
|
+
const configManager = ConfigManager.getInstance();
|
|
777
|
+
const config = configManager.getConfig();
|
|
778
|
+
const realm = config.activeRealm;
|
|
779
|
+
if (!realm) {
|
|
780
|
+
throw new Error(
|
|
781
|
+
'No active authentication found.\nPlease run "shark login" first to authenticate.'
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return realm;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/core/api/prompts.ts
|
|
788
|
+
var UNIFIED_SYSTEM_PROMPT = `Voc\xEA \xE9 o Shark Dev, um agente de intelig\xEAncia artificial de desenvolvimento colaborativo no Shark AI.
|
|
789
|
+
Seu objetivo \xE9 ajudar o usu\xE1rio a analisar, especificar e implementar c\xF3digo de forma estruturada.
|
|
790
|
+
|
|
791
|
+
\u2139\uFE0F SISTEMA DE \xC2NCORAS PARA LEITURA/EDI\xC7\xC3O DE ARQUIVOS (Anchor System):
|
|
792
|
+
- Quando voc\xEA l\xEA um arquivo usando a a\xE7\xE3o 'read_file', cada linha do arquivo ser\xE1 retornada no formato: \`palavra_\xE2ncora\xA7conte\xFAdo_da_linha\`.
|
|
793
|
+
- Exemplo: \`apple\xA7const x = 10;\`
|
|
794
|
+
- Ao modificar um arquivo usando a a\xE7\xE3o 'modify_file', voc\xEA DEVE especificar:
|
|
795
|
+
- \`start_anchor\`: A palavra \xE2ncora (ex: \`apple\`) que marca o in\xEDcio do bloco a ser substitu\xEDdo.
|
|
796
|
+
- \`end_anchor\`: A palavra \xE2ncora (ex: \`apple\`) que marca o fim do bloco a ser substitu\xEDdo (inclusive).
|
|
797
|
+
- \`content\`: O novo conte\xFAdo que substituir\xE1 todo o bloco entre (e incluindo) as duas \xE2ncoras.
|
|
798
|
+
- Importante: Use APENAS a palavra \xE2ncora no campo \`start_anchor\` e \`end_anchor\` (por exemplo: \`apple\`), e N\xC3O a linha inteira ou o separador \`\xA7\`.
|
|
799
|
+
|
|
800
|
+
\u26A0\uFE0F REGRA GERAL PARA ARQUIVOS GRANDES (Evitar JSON truncado):
|
|
801
|
+
- Evite criar ou modificar arquivos grandes (como planos, documenta\xE7\xF5es ou c\xF3digos extensos) de uma \xFAnica vez.
|
|
802
|
+
- Limite de Sa\xEDda R\xEDgido: A API possui um limite m\xE1ximo de tokens de sa\xEDda. Para sua seguran\xE7a, garanta que o conte\xFAdo de cada resposta JSON sua tenha no m\xE1ximo 15.000 caracteres (cerca de 4.000 tokens). NUNCA gere respostas \xFAnicas maiores do que isso.
|
|
803
|
+
- Se a tarefa exigir criar ou modificar arquivos longos, siga estritamente esta l\xF3gica:
|
|
804
|
+
1. Use 'create_file' para criar apenas a estrutura b\xE1sica ou esqueleto do arquivo (cabe\xE7alhos e se\xE7\xF5es vazias).
|
|
805
|
+
2. Nas rodadas subsequentes, use 'modify_file' com o sistema de \xE2ncoras para preencher/atualizar o conte\xFAdo de forma incremental e em peda\xE7os menores (no m\xE1ximo 50 a 100 linhas por vez).
|
|
806
|
+
- Isso evita que a sua resposta JSON seja cortada no meio devido ao limite m\xE1ximo de tokens de sa\xEDda da API.
|
|
807
|
+
|
|
808
|
+
\u{1F6A8} REGRAS CR\xCDTICAS DE RESPOSTA (JSON):
|
|
809
|
+
- Voc\xEA DEVE responder APENAS com um objeto JSON v\xE1lido.
|
|
810
|
+
- N\xE3o inclua nenhuma introdu\xE7\xE3o, explica\xE7\xE3o ou bloco de markdown fora do JSON.
|
|
811
|
+
- Se precisar falar com o usu\xE1rio, use a action com type 'talk_with_user'.
|
|
812
|
+
|
|
813
|
+
SUA SA\xCDDA DEVE SEGUIR EXATAMENTE ESTE FORMATO JSON:
|
|
814
|
+
{
|
|
815
|
+
"action": {
|
|
816
|
+
"type": "create_file" | "modify_file" | "read_file" | "list_files" | "search_file" | "search_code" | "delete_file" | "run_command" | "talk_with_user" | "use_mcp_tool" | "activate_skill" | "define_subagent" | "invoke_subagent" | "send_message" | "manage_subagents" | "complete_task" | "wait",
|
|
817
|
+
"path": "caminho/relativo/do/arquivo (opcional)",
|
|
818
|
+
"content": "conte\xFAdo do arquivo ou mensagem para o usu\xE1rio (opcional)",
|
|
819
|
+
"start_anchor": "\xE2ncora de in\xEDcio de substitui\xE7\xE3o (modify_file apenas)",
|
|
820
|
+
"end_anchor": "\xE2ncora de fim de substitui\xE7\xE3o (modify_file apenas)",
|
|
821
|
+
"command": "comando bash a ser executado (run_command apenas)",
|
|
822
|
+
"query": "termo de busca (search_code apenas)",
|
|
823
|
+
"tool_name": "nome da ferramenta MCP (use_mcp_tool apenas)",
|
|
824
|
+
"tool_args": "argumentos em string JSON para MCP (use_mcp_tool apenas)",
|
|
825
|
+
"skill_name": "nome da habilidade a ativar (activate_skill apenas)",
|
|
826
|
+
"duration_seconds": "tempo m\xE1ximo em segundos para aguardar atualiza\xE7\xF5es (opcional, wait apenas)",
|
|
827
|
+
"Subagents": [
|
|
828
|
+
{
|
|
829
|
+
"TypeName": "tipo do subagente",
|
|
830
|
+
"Role": "papel do subagente",
|
|
831
|
+
"Prompt": "instru\xE7\xF5es de tarefa para o subagente"
|
|
832
|
+
}
|
|
833
|
+
] (invoke_subagent apenas),
|
|
834
|
+
"Recipient": "ID da conversa de destino da mensagem (send_message apenas)",
|
|
835
|
+
"Message": "conte\xFAdo da mensagem a ser enviada (send_message apenas)",
|
|
836
|
+
"Action": "list" | "kill" | "kill_all" (manage_subagents apenas),
|
|
837
|
+
"ConversationIds": ["lista de IDs de conversa para cancelar"] (manage_subagents apenas, opcional),
|
|
838
|
+
"name": "nome do subagente (define_subagent apenas)",
|
|
839
|
+
"description": "descri\xE7\xE3o do subagente (define_subagent apenas)",
|
|
840
|
+
"system_prompt": "prompt de sistema customizado (define_subagent apenas)",
|
|
841
|
+
"enable_write_tools": true | false (define_subagent apenas, opcional),
|
|
842
|
+
"enable_subagent_tools": true | false (define_subagent apenas, opcional),
|
|
843
|
+
"enable_mcp_tools": true | false (define_subagent apenas, opcional)
|
|
844
|
+
},
|
|
845
|
+
"summary": "Resumo de 1 frase do que voc\xEA realizou nesta rodada."
|
|
846
|
+
}`;
|
|
847
|
+
var AGENT_RESPONSE_JSON_SCHEMA = {
|
|
848
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
849
|
+
"title": "AgentResponse",
|
|
850
|
+
"type": "object",
|
|
851
|
+
"properties": {
|
|
852
|
+
"action": {
|
|
853
|
+
"type": "object",
|
|
854
|
+
"properties": {
|
|
855
|
+
"type": {
|
|
856
|
+
"type": "string",
|
|
857
|
+
"enum": [
|
|
858
|
+
"create_file",
|
|
859
|
+
"modify_file",
|
|
860
|
+
"read_file",
|
|
861
|
+
"list_files",
|
|
862
|
+
"search_file",
|
|
863
|
+
"search_code",
|
|
864
|
+
"delete_file",
|
|
865
|
+
"run_command",
|
|
866
|
+
"talk_with_user",
|
|
867
|
+
"use_mcp_tool",
|
|
868
|
+
"activate_skill",
|
|
869
|
+
"define_subagent",
|
|
870
|
+
"invoke_subagent",
|
|
871
|
+
"send_message",
|
|
872
|
+
"manage_subagents",
|
|
873
|
+
"complete_task",
|
|
874
|
+
"wait"
|
|
875
|
+
]
|
|
876
|
+
},
|
|
877
|
+
"path": { "type": ["string", "null"] },
|
|
878
|
+
"content": { "type": ["string", "null"] },
|
|
879
|
+
"start_anchor": { "type": ["string", "null"] },
|
|
880
|
+
"end_anchor": { "type": ["string", "null"] },
|
|
881
|
+
"command": { "type": ["string", "null"] },
|
|
882
|
+
"query": { "type": ["string", "null"] },
|
|
883
|
+
"tool_name": { "type": ["string", "null"] },
|
|
884
|
+
"tool_args": { "type": ["string", "null"] },
|
|
885
|
+
"skill_name": { "type": ["string", "null"] },
|
|
886
|
+
"duration_seconds": {
|
|
887
|
+
"type": ["integer", "null"],
|
|
888
|
+
"description": "Tempo maximo em segundos para aguardar atualizacoes."
|
|
889
|
+
},
|
|
890
|
+
"Subagents": {
|
|
891
|
+
"type": ["array", "null"],
|
|
892
|
+
"items": {
|
|
893
|
+
"type": "object",
|
|
894
|
+
"properties": {
|
|
895
|
+
"TypeName": { "type": "string" },
|
|
896
|
+
"Role": { "type": "string" },
|
|
897
|
+
"Prompt": { "type": "string" }
|
|
898
|
+
},
|
|
899
|
+
"required": ["TypeName", "Role", "Prompt"]
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
"Recipient": { "type": ["string", "null"] },
|
|
903
|
+
"Message": { "type": ["string", "null"] },
|
|
904
|
+
"Action": { "type": ["string", "null"], "enum": ["list", "kill", "kill_all"] },
|
|
905
|
+
"ConversationIds": {
|
|
906
|
+
"type": ["array", "null"],
|
|
907
|
+
"items": { "type": "string" }
|
|
908
|
+
},
|
|
909
|
+
"name": { "type": ["string", "null"] },
|
|
910
|
+
"description": { "type": ["string", "null"] },
|
|
911
|
+
"system_prompt": { "type": ["string", "null"] },
|
|
912
|
+
"enable_write_tools": { "type": ["boolean", "null"] },
|
|
913
|
+
"enable_subagent_tools": { "type": ["boolean", "null"] },
|
|
914
|
+
"enable_mcp_tools": { "type": ["boolean", "null"] }
|
|
915
|
+
},
|
|
916
|
+
"required": ["type"]
|
|
917
|
+
},
|
|
918
|
+
"summary": { "type": "string" }
|
|
919
|
+
},
|
|
920
|
+
"required": ["action"]
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// src/core/api/stackspot-provider.ts
|
|
924
|
+
var StackSpotProvider = class {
|
|
925
|
+
constructor(agentType) {
|
|
926
|
+
this.agentType = agentType;
|
|
927
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
928
|
+
this.agentId = config.stackspot?.agentId;
|
|
929
|
+
}
|
|
930
|
+
agentId;
|
|
931
|
+
getAgentId() {
|
|
932
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
933
|
+
const configKeyMapping = {
|
|
934
|
+
"developer_agent": "dev",
|
|
935
|
+
"business_analyst": "ba",
|
|
936
|
+
"specification_agent": "spec",
|
|
937
|
+
"qa_agent": "qa",
|
|
938
|
+
"scan_agent": "scan",
|
|
939
|
+
"code_review": "codeReview"
|
|
940
|
+
};
|
|
941
|
+
const mappedKey = configKeyMapping[this.agentType];
|
|
942
|
+
if (mappedKey && config.agents?.[mappedKey]) {
|
|
943
|
+
return config.agents[mappedKey];
|
|
944
|
+
}
|
|
945
|
+
const envIdMapping = {
|
|
946
|
+
"business_analyst": process.env.STACKSPOT_BA_AGENT_ID,
|
|
947
|
+
"developer_agent": process.env.STACKSPOT_DEV_AGENT_ID,
|
|
948
|
+
"qa_agent": process.env.STACKSPOT_QA_AGENT_ID,
|
|
949
|
+
"specification_agent": process.env.STACKSPOT_SPEC_AGENT_ID,
|
|
950
|
+
"scan_agent": process.env.STACKSPOT_SCAN_AGENT_ID,
|
|
951
|
+
"code_review": process.env.STACKSPOT_CODE_REVIEW_AGENT_ID
|
|
952
|
+
};
|
|
953
|
+
const envResolved = envIdMapping[this.agentType];
|
|
954
|
+
if (envResolved) {
|
|
955
|
+
return envResolved;
|
|
956
|
+
}
|
|
957
|
+
if (this.agentId && this.agentId !== "01KEQCGJ65YENRA4QBXVN1YFFX") {
|
|
958
|
+
return this.agentId;
|
|
959
|
+
}
|
|
960
|
+
const defaultIdMapping = {
|
|
961
|
+
"business_analyst": "01KEJ95G304TNNAKGH5XNEEBVD",
|
|
962
|
+
"developer_agent": "01KEQCGJ65YENRA4QBXVN1YFFX",
|
|
963
|
+
"qa_agent": "01KEQFJZ3Q3JER11NH22HEZX9X",
|
|
964
|
+
"specification_agent": "01KEPXTX37FTB4N672TZST4SGP",
|
|
965
|
+
"scan_agent": "01KEQ9AHWB550J2244YBH3QATN",
|
|
966
|
+
"code_review": ""
|
|
967
|
+
};
|
|
968
|
+
const resolved = defaultIdMapping[this.agentType];
|
|
969
|
+
if (this.agentType === "code_review") {
|
|
970
|
+
if (!resolved) {
|
|
971
|
+
throw new Error("Agent ID for 'code_review' is not configured.");
|
|
972
|
+
}
|
|
973
|
+
return resolved;
|
|
974
|
+
}
|
|
975
|
+
return resolved || "01KEQCGJ65YENRA4QBXVN1YFFX";
|
|
976
|
+
}
|
|
977
|
+
getAgentVersion() {
|
|
978
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
979
|
+
const versionMapping = {
|
|
980
|
+
"business_analyst": config.agentVersions?.ba || process.env.STACKSPOT_BA_AGENT_VERSION,
|
|
981
|
+
"developer_agent": config.agentVersions?.dev || process.env.STACKSPOT_DEV_AGENT_VERSION,
|
|
982
|
+
"qa_agent": config.agentVersions?.qa || process.env.STACKSPOT_QA_AGENT_VERSION,
|
|
983
|
+
"specification_agent": config.agentVersions?.spec || process.env.STACKSPOT_SPEC_AGENT_VERSION,
|
|
984
|
+
"scan_agent": config.agentVersions?.scan || process.env.STACKSPOT_SCAN_AGENT_VERSION,
|
|
985
|
+
"code_review": config.agentVersions?.codeReview || process.env.STACKSPOT_CODE_REVIEW_AGENT_VERSION
|
|
986
|
+
};
|
|
987
|
+
return versionMapping[this.agentType];
|
|
988
|
+
}
|
|
989
|
+
async streamChat(prompt, options) {
|
|
990
|
+
const realm = await getActiveRealm();
|
|
991
|
+
let token = null;
|
|
992
|
+
try {
|
|
993
|
+
token = await ensureValidToken(realm);
|
|
994
|
+
} catch (error) {
|
|
995
|
+
token = await tokenStorage.getToken(realm);
|
|
996
|
+
}
|
|
997
|
+
if (!token) {
|
|
998
|
+
throw new Error(`No authentication token found for realm '${realm}'. Please run 'shark login'.`);
|
|
999
|
+
}
|
|
1000
|
+
const isFirstTurn = !options.conversationId;
|
|
1001
|
+
const finalPrompt = isFirstTurn ? `SYSTEM INSTRUCTIONS:
|
|
1002
|
+
${UNIFIED_SYSTEM_PROMPT}
|
|
1003
|
+
|
|
1004
|
+
USER REQUEST:
|
|
1005
|
+
${prompt}` : prompt;
|
|
1006
|
+
const requestPayload = {
|
|
1007
|
+
user_prompt: finalPrompt,
|
|
1008
|
+
streaming: true,
|
|
1009
|
+
stackspot_knowledge: false,
|
|
1010
|
+
return_ks_in_response: true,
|
|
1011
|
+
deep_search_ks: false,
|
|
1012
|
+
use_conversation: true,
|
|
1013
|
+
conversation_id: options.conversationId
|
|
1014
|
+
};
|
|
1015
|
+
const agentVersion = this.getAgentVersion();
|
|
1016
|
+
if (agentVersion) {
|
|
1017
|
+
requestPayload.agent_version_number = agentVersion;
|
|
1018
|
+
}
|
|
1019
|
+
const effectiveAgentId = this.getAgentId();
|
|
1020
|
+
const agentUrl = `${STACKSPOT_AGENT_API_BASE}/v1/agent/${effectiveAgentId}/chat`;
|
|
1021
|
+
const headers = {
|
|
1022
|
+
"Authorization": `Bearer ${token}`,
|
|
1023
|
+
"Content-Type": "application/json"
|
|
1024
|
+
};
|
|
1025
|
+
const sanitizedHeaders = { ...headers };
|
|
1026
|
+
if (sanitizedHeaders["Authorization"]) {
|
|
1027
|
+
sanitizedHeaders["Authorization"] = "Bearer ***";
|
|
1028
|
+
}
|
|
1029
|
+
FileLogger.log("PROVIDER_REQUEST", "Request payload sent to StackSpot API", {
|
|
1030
|
+
agentId: effectiveAgentId,
|
|
1031
|
+
url: agentUrl,
|
|
1032
|
+
headers: sanitizedHeaders,
|
|
1033
|
+
payload: requestPayload
|
|
1034
|
+
});
|
|
1035
|
+
let fullMessage = "";
|
|
1036
|
+
let rawResponse = {};
|
|
1037
|
+
await sseClient.streamAgentResponse(
|
|
1038
|
+
agentUrl,
|
|
1039
|
+
requestPayload,
|
|
1040
|
+
headers,
|
|
1041
|
+
{
|
|
1042
|
+
onChunk: (chunk) => {
|
|
1043
|
+
fullMessage += chunk;
|
|
1044
|
+
if (options.onChunk) {
|
|
1045
|
+
options.onChunk(chunk);
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
onComplete: async (message, metadata) => {
|
|
1049
|
+
rawResponse = {
|
|
1050
|
+
message: message || fullMessage,
|
|
1051
|
+
conversation_id: metadata?.conversation_id || options.conversationId
|
|
1052
|
+
};
|
|
1053
|
+
},
|
|
1054
|
+
onError: (error) => {
|
|
1055
|
+
throw error;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
);
|
|
1059
|
+
FileLogger.log("PROVIDER_RESPONSE", "Raw response from StackSpot API", { rawResponse });
|
|
1060
|
+
const parsedResponse = parseAgentResponse(rawResponse);
|
|
1061
|
+
if (options.onComplete) {
|
|
1062
|
+
options.onComplete(parsedResponse);
|
|
1063
|
+
}
|
|
1064
|
+
return parsedResponse;
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
// src/core/workflow/history-manager.ts
|
|
1069
|
+
import fs3 from "fs";
|
|
1070
|
+
import path3 from "path";
|
|
1071
|
+
var HistoryManager = class {
|
|
1072
|
+
static getHistoryDir() {
|
|
1073
|
+
const dir = path3.resolve(process.cwd(), "_sharkrc", "history");
|
|
1074
|
+
if (!fs3.existsSync(dir)) {
|
|
1075
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1076
|
+
}
|
|
1077
|
+
return dir;
|
|
1078
|
+
}
|
|
1079
|
+
static getFilePath(conversationId) {
|
|
1080
|
+
return path3.resolve(this.getHistoryDir(), `${conversationId}.json`);
|
|
1081
|
+
}
|
|
1082
|
+
static async getHistory(conversationId) {
|
|
1083
|
+
const filePath = this.getFilePath(conversationId);
|
|
1084
|
+
if (!fs3.existsSync(filePath)) {
|
|
1085
|
+
return [];
|
|
1086
|
+
}
|
|
1087
|
+
try {
|
|
1088
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
1089
|
+
const parsed = JSON.parse(raw);
|
|
1090
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
1091
|
+
} catch {
|
|
1092
|
+
return [];
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
static async saveHistory(conversationId, messages) {
|
|
1096
|
+
const filePath = this.getFilePath(conversationId);
|
|
1097
|
+
fs3.writeFileSync(filePath, JSON.stringify(messages, null, 2), "utf-8");
|
|
1098
|
+
}
|
|
1099
|
+
static async appendMessage(conversationId, message) {
|
|
1100
|
+
const history = await this.getHistory(conversationId);
|
|
1101
|
+
history.push(message);
|
|
1102
|
+
await this.saveHistory(conversationId, history);
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1105
|
+
|
|
1106
|
+
// src/core/api/openai-compatible-provider.ts
|
|
1107
|
+
import crypto from "crypto";
|
|
1108
|
+
var OpenAICompatibleProvider = class {
|
|
1109
|
+
constructor(options) {
|
|
1110
|
+
this.options = options;
|
|
1111
|
+
}
|
|
1112
|
+
getAgentSystemPrompt(agentType) {
|
|
1113
|
+
return UNIFIED_SYSTEM_PROMPT;
|
|
1114
|
+
}
|
|
1115
|
+
async streamChat(prompt, options) {
|
|
1116
|
+
const conversationId = options.conversationId || crypto.randomUUID();
|
|
1117
|
+
const history = await HistoryManager.getHistory(conversationId);
|
|
1118
|
+
if (history.length === 0) {
|
|
1119
|
+
history.push({
|
|
1120
|
+
role: "system",
|
|
1121
|
+
content: this.getAgentSystemPrompt(options.agentType)
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
history.push({ role: "user", content: prompt });
|
|
1125
|
+
const requestPayload = {
|
|
1126
|
+
model: this.options.model,
|
|
1127
|
+
messages: history,
|
|
1128
|
+
stream: true,
|
|
1129
|
+
temperature: 0.2
|
|
1130
|
+
};
|
|
1131
|
+
if (this.options.useStructuredOutputs) {
|
|
1132
|
+
requestPayload.response_format = {
|
|
1133
|
+
type: "json_schema",
|
|
1134
|
+
json_schema: {
|
|
1135
|
+
name: "agent_response",
|
|
1136
|
+
strict: true,
|
|
1137
|
+
schema: {
|
|
1138
|
+
type: "object",
|
|
1139
|
+
properties: {
|
|
1140
|
+
action: {
|
|
1141
|
+
type: "object",
|
|
1142
|
+
properties: {
|
|
1143
|
+
type: {
|
|
1144
|
+
type: "string",
|
|
1145
|
+
enum: [
|
|
1146
|
+
"create_file",
|
|
1147
|
+
"modify_file",
|
|
1148
|
+
"read_file",
|
|
1149
|
+
"list_files",
|
|
1150
|
+
"search_file",
|
|
1151
|
+
"search_code",
|
|
1152
|
+
"delete_file",
|
|
1153
|
+
"run_command",
|
|
1154
|
+
"talk_with_user",
|
|
1155
|
+
"use_mcp_tool",
|
|
1156
|
+
"activate_skill",
|
|
1157
|
+
"define_subagent",
|
|
1158
|
+
"invoke_subagent",
|
|
1159
|
+
"send_message",
|
|
1160
|
+
"manage_subagents",
|
|
1161
|
+
"complete_task",
|
|
1162
|
+
"list_structure",
|
|
1163
|
+
"modify_ast",
|
|
1164
|
+
"search_ast",
|
|
1165
|
+
"ast_list_structure",
|
|
1166
|
+
"ast_get_method",
|
|
1167
|
+
"ast_add_method",
|
|
1168
|
+
"ast_modify_method",
|
|
1169
|
+
"ast_remove_method",
|
|
1170
|
+
"ast_add_class",
|
|
1171
|
+
"ast_get_property",
|
|
1172
|
+
"ast_add_property",
|
|
1173
|
+
"ast_modify_property",
|
|
1174
|
+
"ast_remove_property",
|
|
1175
|
+
"ast_add_decorator",
|
|
1176
|
+
"ast_add_interface",
|
|
1177
|
+
"ast_add_type_alias",
|
|
1178
|
+
"ast_add_function",
|
|
1179
|
+
"ast_remove_function",
|
|
1180
|
+
"ast_add_import",
|
|
1181
|
+
"ast_remove_import",
|
|
1182
|
+
"ast_organize_imports"
|
|
1183
|
+
]
|
|
1184
|
+
},
|
|
1185
|
+
path: { type: ["string", "null"] },
|
|
1186
|
+
content: { type: ["string", "null"] },
|
|
1187
|
+
start_anchor: { type: ["string", "null"] },
|
|
1188
|
+
end_anchor: { type: ["string", "null"] },
|
|
1189
|
+
command: { type: ["string", "null"] },
|
|
1190
|
+
query: { type: ["string", "null"] },
|
|
1191
|
+
tool_name: { type: ["string", "null"] },
|
|
1192
|
+
tool_args: { type: ["string", "null"] },
|
|
1193
|
+
line_range: {
|
|
1194
|
+
type: ["array", "null"],
|
|
1195
|
+
items: { type: "number" }
|
|
1196
|
+
},
|
|
1197
|
+
target_content: { type: ["string", "null"] },
|
|
1198
|
+
is_regex: { type: ["boolean", "null"] },
|
|
1199
|
+
pattern: { type: ["string", "null"] },
|
|
1200
|
+
fix: { type: ["string", "null"] },
|
|
1201
|
+
language: { type: ["string", "null"] },
|
|
1202
|
+
file_path: { type: ["string", "null"] },
|
|
1203
|
+
class_name: { type: ["string", "null"] },
|
|
1204
|
+
method_name: { type: ["string", "null"] },
|
|
1205
|
+
method_code: { type: ["string", "null"] },
|
|
1206
|
+
property_name: { type: ["string", "null"] },
|
|
1207
|
+
property_code: { type: ["string", "null"] },
|
|
1208
|
+
extends_class: { type: ["string", "null"] },
|
|
1209
|
+
implements_interfaces: {
|
|
1210
|
+
type: ["array", "null"],
|
|
1211
|
+
items: { type: "string" }
|
|
1212
|
+
},
|
|
1213
|
+
decorator_code: { type: ["string", "null"] },
|
|
1214
|
+
interface_code: { type: ["string", "null"] },
|
|
1215
|
+
type_code: { type: ["string", "null"] },
|
|
1216
|
+
function_name: { type: ["string", "null"] },
|
|
1217
|
+
function_code: { type: ["string", "null"] },
|
|
1218
|
+
import_statement: { type: ["string", "null"] },
|
|
1219
|
+
module_path: { type: ["string", "null"] },
|
|
1220
|
+
new_body: { type: ["string", "null"] },
|
|
1221
|
+
confirmed: { type: ["boolean", "null"] },
|
|
1222
|
+
skill_name: { type: ["string", "null"] },
|
|
1223
|
+
Subagents: {
|
|
1224
|
+
type: ["array", "null"],
|
|
1225
|
+
items: {
|
|
1226
|
+
type: "object",
|
|
1227
|
+
properties: {
|
|
1228
|
+
TypeName: { type: "string" },
|
|
1229
|
+
Role: { type: "string" },
|
|
1230
|
+
Prompt: { type: "string" }
|
|
1231
|
+
},
|
|
1232
|
+
required: ["TypeName", "Role", "Prompt"],
|
|
1233
|
+
additionalProperties: false
|
|
1234
|
+
}
|
|
1235
|
+
},
|
|
1236
|
+
Recipient: { type: ["string", "null"] },
|
|
1237
|
+
Message: { type: ["string", "null"] },
|
|
1238
|
+
Action: { type: ["string", "null"] },
|
|
1239
|
+
ConversationIds: {
|
|
1240
|
+
type: ["array", "null"],
|
|
1241
|
+
items: { type: "string" }
|
|
1242
|
+
},
|
|
1243
|
+
name: { type: ["string", "null"] },
|
|
1244
|
+
description: { type: ["string", "null"] },
|
|
1245
|
+
system_prompt: { type: ["string", "null"] },
|
|
1246
|
+
enable_write_tools: { type: ["boolean", "null"] },
|
|
1247
|
+
enable_subagent_tools: { type: ["boolean", "null"] },
|
|
1248
|
+
enable_mcp_tools: { type: ["boolean", "null"] }
|
|
1249
|
+
},
|
|
1250
|
+
required: [
|
|
1251
|
+
"type",
|
|
1252
|
+
"path",
|
|
1253
|
+
"content",
|
|
1254
|
+
"start_anchor",
|
|
1255
|
+
"end_anchor",
|
|
1256
|
+
"command",
|
|
1257
|
+
"query",
|
|
1258
|
+
"tool_name",
|
|
1259
|
+
"tool_args",
|
|
1260
|
+
"line_range",
|
|
1261
|
+
"target_content",
|
|
1262
|
+
"is_regex",
|
|
1263
|
+
"pattern",
|
|
1264
|
+
"fix",
|
|
1265
|
+
"language",
|
|
1266
|
+
"file_path",
|
|
1267
|
+
"class_name",
|
|
1268
|
+
"method_name",
|
|
1269
|
+
"method_code",
|
|
1270
|
+
"property_name",
|
|
1271
|
+
"property_code",
|
|
1272
|
+
"extends_class",
|
|
1273
|
+
"implements_interfaces",
|
|
1274
|
+
"decorator_code",
|
|
1275
|
+
"interface_code",
|
|
1276
|
+
"type_code",
|
|
1277
|
+
"function_name",
|
|
1278
|
+
"function_code",
|
|
1279
|
+
"import_statement",
|
|
1280
|
+
"module_path",
|
|
1281
|
+
"new_body",
|
|
1282
|
+
"confirmed",
|
|
1283
|
+
"skill_name",
|
|
1284
|
+
"Subagents",
|
|
1285
|
+
"Recipient",
|
|
1286
|
+
"Message",
|
|
1287
|
+
"Action",
|
|
1288
|
+
"ConversationIds",
|
|
1289
|
+
"name",
|
|
1290
|
+
"description",
|
|
1291
|
+
"system_prompt",
|
|
1292
|
+
"enable_write_tools",
|
|
1293
|
+
"enable_subagent_tools",
|
|
1294
|
+
"enable_mcp_tools"
|
|
1295
|
+
],
|
|
1296
|
+
additionalProperties: false
|
|
1297
|
+
},
|
|
1298
|
+
summary: { type: "string" }
|
|
1299
|
+
},
|
|
1300
|
+
required: ["action", "summary"],
|
|
1301
|
+
additionalProperties: false
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
} else {
|
|
1306
|
+
requestPayload.response_format = { type: "json_object" };
|
|
1307
|
+
}
|
|
1308
|
+
const headers = {
|
|
1309
|
+
"Content-Type": "application/json"
|
|
1310
|
+
};
|
|
1311
|
+
if (this.options.apiKey) {
|
|
1312
|
+
headers["Authorization"] = `Bearer ${this.options.apiKey}`;
|
|
1313
|
+
}
|
|
1314
|
+
const sanitizedHeaders = { ...headers };
|
|
1315
|
+
if (sanitizedHeaders["Authorization"]) {
|
|
1316
|
+
sanitizedHeaders["Authorization"] = "Bearer ***";
|
|
1317
|
+
}
|
|
1318
|
+
FileLogger.log("PROVIDER_REQUEST", "Request payload sent to OpenAI Compatible API", {
|
|
1319
|
+
baseURL: this.options.baseURL,
|
|
1320
|
+
headers: sanitizedHeaders,
|
|
1321
|
+
payload: requestPayload
|
|
1322
|
+
});
|
|
1323
|
+
const res = await fetch(`${this.options.baseURL}/chat/completions`, {
|
|
1324
|
+
method: "POST",
|
|
1325
|
+
headers,
|
|
1326
|
+
body: JSON.stringify(requestPayload)
|
|
1327
|
+
});
|
|
1328
|
+
if (!res.ok) {
|
|
1329
|
+
const errBody = await res.text();
|
|
1330
|
+
throw new Error(`OpenAI API request failed: ${res.status} ${res.statusText} - ${errBody}`);
|
|
1331
|
+
}
|
|
1332
|
+
const reader = res.body?.getReader();
|
|
1333
|
+
if (!reader) {
|
|
1334
|
+
throw new Error("Response body reader is undefined");
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
const decoder = new TextDecoder();
|
|
1338
|
+
let fullContent = "";
|
|
1339
|
+
let done = false;
|
|
1340
|
+
let buffer = "";
|
|
1341
|
+
while (!done) {
|
|
1342
|
+
const { value, done: doneReading } = await reader.read();
|
|
1343
|
+
done = doneReading;
|
|
1344
|
+
if (value) {
|
|
1345
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
1346
|
+
const lines = buffer.split("\n");
|
|
1347
|
+
buffer = lines.pop() || "";
|
|
1348
|
+
for (const line of lines) {
|
|
1349
|
+
const clean = line.trim();
|
|
1350
|
+
if (!clean || clean === "data: [DONE]") continue;
|
|
1351
|
+
if (clean.startsWith("data: ")) {
|
|
1352
|
+
let parsed;
|
|
1353
|
+
try {
|
|
1354
|
+
parsed = JSON.parse(clean.substring(6));
|
|
1355
|
+
} catch {
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
if (parsed && parsed.error) {
|
|
1359
|
+
throw new Error(`OpenAI Stream Error: ${JSON.stringify(parsed.error)}`);
|
|
1360
|
+
}
|
|
1361
|
+
const delta = parsed?.choices?.[0]?.delta?.content || "";
|
|
1362
|
+
if (delta) {
|
|
1363
|
+
fullContent += delta;
|
|
1364
|
+
if (options.onChunk) {
|
|
1365
|
+
options.onChunk(delta);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (buffer) {
|
|
1373
|
+
const clean = buffer.trim();
|
|
1374
|
+
if (clean && clean !== "data: [DONE]" && clean.startsWith("data: ")) {
|
|
1375
|
+
let parsed;
|
|
1376
|
+
try {
|
|
1377
|
+
parsed = JSON.parse(clean.substring(6));
|
|
1378
|
+
} catch {
|
|
1379
|
+
}
|
|
1380
|
+
if (parsed && parsed.error) {
|
|
1381
|
+
throw new Error(`OpenAI Stream Error: ${JSON.stringify(parsed.error)}`);
|
|
1382
|
+
}
|
|
1383
|
+
const delta = parsed?.choices?.[0]?.delta?.content || "";
|
|
1384
|
+
if (delta) {
|
|
1385
|
+
fullContent += delta;
|
|
1386
|
+
if (options.onChunk) {
|
|
1387
|
+
options.onChunk(delta);
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
FileLogger.log("PROVIDER_RESPONSE", "Raw response from OpenAI Compatible API", { fullContent });
|
|
1393
|
+
const parsedResponse = parseAgentResponse(fullContent);
|
|
1394
|
+
parsedResponse.conversation_id = conversationId;
|
|
1395
|
+
history.push({ role: "assistant", content: JSON.stringify(parsedResponse) });
|
|
1396
|
+
await HistoryManager.saveHistory(conversationId, history);
|
|
1397
|
+
if (options.onComplete) {
|
|
1398
|
+
options.onComplete(parsedResponse);
|
|
1399
|
+
}
|
|
1400
|
+
return parsedResponse;
|
|
1401
|
+
} finally {
|
|
1402
|
+
if (typeof reader.releaseLock === "function") {
|
|
1403
|
+
reader.releaseLock();
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// src/core/api/provider-resolver.ts
|
|
1410
|
+
var ProviderResolver = class {
|
|
1411
|
+
static getProvider(agentType) {
|
|
1412
|
+
const config = ConfigManager.getInstance().getConfig();
|
|
1413
|
+
if (config.provider === "openai-compatible") {
|
|
1414
|
+
const opt = config["openai-compatible"] || {};
|
|
1415
|
+
return new OpenAICompatibleProvider({
|
|
1416
|
+
baseURL: opt.baseURL || "http://localhost:11434/v1",
|
|
1417
|
+
apiKey: opt.apiKey || "ollama",
|
|
1418
|
+
model: opt.model || "llama3",
|
|
1419
|
+
useStructuredOutputs: opt.useStructuredOutputs ?? true
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
return new StackSpotProvider(agentType);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
|
|
1426
|
+
// src/core/workflow/conversation-manager.ts
|
|
1427
|
+
var ConversationManager = class _ConversationManager {
|
|
1428
|
+
static instance;
|
|
1429
|
+
constructor() {
|
|
1430
|
+
}
|
|
1431
|
+
static getInstance() {
|
|
1432
|
+
if (!_ConversationManager.instance) {
|
|
1433
|
+
_ConversationManager.instance = new _ConversationManager();
|
|
1434
|
+
}
|
|
1435
|
+
return _ConversationManager.instance;
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Saves a conversation ID for a specific agent type.
|
|
1439
|
+
*
|
|
1440
|
+
* @param agentType - The type of agent (e.g., 'business_analyst', 'architect')
|
|
1441
|
+
* @param conversationId - The conversation ID from the agent response
|
|
1442
|
+
*/
|
|
1443
|
+
async saveConversationId(agentType, conversationId) {
|
|
1444
|
+
const state = await workflowManager.load();
|
|
1445
|
+
if (!state) {
|
|
1446
|
+
throw new Error('No workflow state found. Please run "shark init" first.');
|
|
1447
|
+
}
|
|
1448
|
+
if (!state.conversations) {
|
|
1449
|
+
state.conversations = {};
|
|
1450
|
+
}
|
|
1451
|
+
state.conversations[agentType] = conversationId;
|
|
1452
|
+
await workflowManager.save(state);
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* Retrieves the conversation ID for a specific agent type.
|
|
1456
|
+
*
|
|
1457
|
+
* @param agentType - The type of agent
|
|
1458
|
+
* @returns The conversation ID, or undefined if none exists
|
|
1459
|
+
*/
|
|
1460
|
+
async getConversationId(agentType) {
|
|
1461
|
+
const state = await workflowManager.load();
|
|
1462
|
+
if (!state || !state.conversations) {
|
|
1463
|
+
return void 0;
|
|
1464
|
+
}
|
|
1465
|
+
return state.conversations[agentType];
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Clears the conversation ID for a specific agent type.
|
|
1469
|
+
*
|
|
1470
|
+
* @param agentType - The type of agent
|
|
1471
|
+
*/
|
|
1472
|
+
async clearConversationId(agentType) {
|
|
1473
|
+
const state = await workflowManager.load();
|
|
1474
|
+
if (!state || !state.conversations) {
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
delete state.conversations[agentType];
|
|
1478
|
+
await workflowManager.save(state);
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Clears all conversation IDs.
|
|
1482
|
+
* Useful when transitioning to a new project or resetting state.
|
|
1483
|
+
*/
|
|
1484
|
+
async clearAllConversations() {
|
|
1485
|
+
const state = await workflowManager.load();
|
|
1486
|
+
if (!state) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
state.conversations = {};
|
|
1490
|
+
await workflowManager.save(state);
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
var conversationManager = ConversationManager.getInstance();
|
|
1494
|
+
|
|
1495
|
+
// src/core/workflow/anchor-state-manager.ts
|
|
1496
|
+
import fs4 from "fs";
|
|
1497
|
+
import path4 from "path";
|
|
1498
|
+
import { diffLines } from "diff";
|
|
1499
|
+
var AnchorStateManager = class {
|
|
1500
|
+
cache = /* @__PURE__ */ new Map();
|
|
1501
|
+
wordPool = [];
|
|
1502
|
+
constructor() {
|
|
1503
|
+
this.initializeWordPool();
|
|
1504
|
+
}
|
|
1505
|
+
initializeWordPool() {
|
|
1506
|
+
this.wordPool = [
|
|
1507
|
+
// group 1
|
|
1508
|
+
"apple",
|
|
1509
|
+
"beach",
|
|
1510
|
+
"cabin",
|
|
1511
|
+
"dust",
|
|
1512
|
+
"edge",
|
|
1513
|
+
"stone",
|
|
1514
|
+
"river",
|
|
1515
|
+
"tree",
|
|
1516
|
+
"leaf",
|
|
1517
|
+
"flower",
|
|
1518
|
+
"grass",
|
|
1519
|
+
"seed",
|
|
1520
|
+
"soil",
|
|
1521
|
+
"sand",
|
|
1522
|
+
"rock",
|
|
1523
|
+
"hill",
|
|
1524
|
+
"path",
|
|
1525
|
+
"road",
|
|
1526
|
+
"street",
|
|
1527
|
+
"house",
|
|
1528
|
+
"room",
|
|
1529
|
+
"door",
|
|
1530
|
+
"window",
|
|
1531
|
+
"wall",
|
|
1532
|
+
"roof",
|
|
1533
|
+
"floor",
|
|
1534
|
+
"desk",
|
|
1535
|
+
"chair",
|
|
1536
|
+
"table",
|
|
1537
|
+
"book",
|
|
1538
|
+
"pen",
|
|
1539
|
+
"paper",
|
|
1540
|
+
"bag",
|
|
1541
|
+
"box",
|
|
1542
|
+
"cup",
|
|
1543
|
+
"plate",
|
|
1544
|
+
"bowl",
|
|
1545
|
+
"spoon",
|
|
1546
|
+
"fork",
|
|
1547
|
+
"knife",
|
|
1548
|
+
"food",
|
|
1549
|
+
"bread",
|
|
1550
|
+
"milk",
|
|
1551
|
+
"water",
|
|
1552
|
+
"tea",
|
|
1553
|
+
"fruit",
|
|
1554
|
+
"pear",
|
|
1555
|
+
"peach",
|
|
1556
|
+
"plum",
|
|
1557
|
+
"grape",
|
|
1558
|
+
"melon",
|
|
1559
|
+
"berry",
|
|
1560
|
+
"nut",
|
|
1561
|
+
"bean",
|
|
1562
|
+
"corn",
|
|
1563
|
+
"rice",
|
|
1564
|
+
"oat",
|
|
1565
|
+
"wheat",
|
|
1566
|
+
"fish",
|
|
1567
|
+
"bird",
|
|
1568
|
+
"cat",
|
|
1569
|
+
"dog",
|
|
1570
|
+
"cow",
|
|
1571
|
+
"sheep",
|
|
1572
|
+
"goat",
|
|
1573
|
+
"pig",
|
|
1574
|
+
"horse",
|
|
1575
|
+
"deer",
|
|
1576
|
+
"bear",
|
|
1577
|
+
"wolf",
|
|
1578
|
+
"fox",
|
|
1579
|
+
"lion",
|
|
1580
|
+
"tiger",
|
|
1581
|
+
"seal",
|
|
1582
|
+
"whale",
|
|
1583
|
+
"crab",
|
|
1584
|
+
"frog",
|
|
1585
|
+
"toad",
|
|
1586
|
+
"snake",
|
|
1587
|
+
"worm",
|
|
1588
|
+
"bee",
|
|
1589
|
+
"ant",
|
|
1590
|
+
"fly",
|
|
1591
|
+
"spider",
|
|
1592
|
+
"moth",
|
|
1593
|
+
"fern",
|
|
1594
|
+
"moss",
|
|
1595
|
+
"pine",
|
|
1596
|
+
"oak",
|
|
1597
|
+
"maple",
|
|
1598
|
+
"birch",
|
|
1599
|
+
"willow",
|
|
1600
|
+
"ash",
|
|
1601
|
+
"elm",
|
|
1602
|
+
"palm",
|
|
1603
|
+
"rose",
|
|
1604
|
+
"lily",
|
|
1605
|
+
"daisy",
|
|
1606
|
+
"tulip",
|
|
1607
|
+
"iris",
|
|
1608
|
+
// group 2
|
|
1609
|
+
"about",
|
|
1610
|
+
"above",
|
|
1611
|
+
"actor",
|
|
1612
|
+
"acute",
|
|
1613
|
+
"admit",
|
|
1614
|
+
"adopt",
|
|
1615
|
+
"adult",
|
|
1616
|
+
"after",
|
|
1617
|
+
"again",
|
|
1618
|
+
"agent",
|
|
1619
|
+
"agree",
|
|
1620
|
+
"ahead",
|
|
1621
|
+
"alarm",
|
|
1622
|
+
"album",
|
|
1623
|
+
"alert",
|
|
1624
|
+
"alike",
|
|
1625
|
+
"alive",
|
|
1626
|
+
"allow",
|
|
1627
|
+
"alone",
|
|
1628
|
+
"along",
|
|
1629
|
+
"alter",
|
|
1630
|
+
"among",
|
|
1631
|
+
"anger",
|
|
1632
|
+
"angle",
|
|
1633
|
+
"angry",
|
|
1634
|
+
"apart",
|
|
1635
|
+
"appeal",
|
|
1636
|
+
"apron",
|
|
1637
|
+
"arena",
|
|
1638
|
+
"argue",
|
|
1639
|
+
"arise",
|
|
1640
|
+
"array",
|
|
1641
|
+
"arrow",
|
|
1642
|
+
"aside",
|
|
1643
|
+
"asset",
|
|
1644
|
+
"audio",
|
|
1645
|
+
"audit",
|
|
1646
|
+
"avoid",
|
|
1647
|
+
"award",
|
|
1648
|
+
"aware",
|
|
1649
|
+
"awful",
|
|
1650
|
+
"back",
|
|
1651
|
+
"bacon",
|
|
1652
|
+
"badge",
|
|
1653
|
+
"baker",
|
|
1654
|
+
"ball",
|
|
1655
|
+
"band",
|
|
1656
|
+
"bank",
|
|
1657
|
+
"base",
|
|
1658
|
+
"basic",
|
|
1659
|
+
"basil",
|
|
1660
|
+
"basin",
|
|
1661
|
+
"basis",
|
|
1662
|
+
"bath",
|
|
1663
|
+
"baton",
|
|
1664
|
+
"bayou",
|
|
1665
|
+
"bead",
|
|
1666
|
+
"beak",
|
|
1667
|
+
"beam",
|
|
1668
|
+
"beast",
|
|
1669
|
+
"beat",
|
|
1670
|
+
"beauty",
|
|
1671
|
+
"beef",
|
|
1672
|
+
"beer",
|
|
1673
|
+
"beet",
|
|
1674
|
+
"begin",
|
|
1675
|
+
"begun",
|
|
1676
|
+
"being",
|
|
1677
|
+
"belief",
|
|
1678
|
+
"bell",
|
|
1679
|
+
"belly",
|
|
1680
|
+
"below",
|
|
1681
|
+
"bench",
|
|
1682
|
+
"bend",
|
|
1683
|
+
"best",
|
|
1684
|
+
"bible",
|
|
1685
|
+
"bike",
|
|
1686
|
+
"bill",
|
|
1687
|
+
"bind",
|
|
1688
|
+
"bingo",
|
|
1689
|
+
"birth",
|
|
1690
|
+
"bite",
|
|
1691
|
+
"black",
|
|
1692
|
+
"blade",
|
|
1693
|
+
"blame",
|
|
1694
|
+
"blank",
|
|
1695
|
+
"blast",
|
|
1696
|
+
"blaze",
|
|
1697
|
+
"bleed",
|
|
1698
|
+
"blend",
|
|
1699
|
+
"blind",
|
|
1700
|
+
"blink",
|
|
1701
|
+
"block",
|
|
1702
|
+
"blond",
|
|
1703
|
+
"blood",
|
|
1704
|
+
"bloom",
|
|
1705
|
+
"blow",
|
|
1706
|
+
"blue",
|
|
1707
|
+
"blunt",
|
|
1708
|
+
"blush",
|
|
1709
|
+
// group 3
|
|
1710
|
+
"board",
|
|
1711
|
+
"boast",
|
|
1712
|
+
"boat",
|
|
1713
|
+
"body",
|
|
1714
|
+
"boil",
|
|
1715
|
+
"bold",
|
|
1716
|
+
"bolt",
|
|
1717
|
+
"bomb",
|
|
1718
|
+
"bond",
|
|
1719
|
+
"bone",
|
|
1720
|
+
"bonus",
|
|
1721
|
+
"boom",
|
|
1722
|
+
"boost",
|
|
1723
|
+
"boot",
|
|
1724
|
+
"booth",
|
|
1725
|
+
"border",
|
|
1726
|
+
"bore",
|
|
1727
|
+
"boss",
|
|
1728
|
+
"both",
|
|
1729
|
+
"bough",
|
|
1730
|
+
"bound",
|
|
1731
|
+
"boy",
|
|
1732
|
+
"brace",
|
|
1733
|
+
"brain",
|
|
1734
|
+
"brake",
|
|
1735
|
+
"branch",
|
|
1736
|
+
"brand",
|
|
1737
|
+
"brass",
|
|
1738
|
+
"brave",
|
|
1739
|
+
"break",
|
|
1740
|
+
"breast",
|
|
1741
|
+
"breath",
|
|
1742
|
+
"breezy",
|
|
1743
|
+
"brick",
|
|
1744
|
+
"bride",
|
|
1745
|
+
"bridge",
|
|
1746
|
+
"brief",
|
|
1747
|
+
"bright",
|
|
1748
|
+
"brim",
|
|
1749
|
+
"bring",
|
|
1750
|
+
"brisk",
|
|
1751
|
+
"broad",
|
|
1752
|
+
"broil",
|
|
1753
|
+
"broke",
|
|
1754
|
+
"bronze",
|
|
1755
|
+
"brook",
|
|
1756
|
+
"broom",
|
|
1757
|
+
"broth",
|
|
1758
|
+
"brown",
|
|
1759
|
+
"brush",
|
|
1760
|
+
"bubble",
|
|
1761
|
+
"bucket",
|
|
1762
|
+
"buckle",
|
|
1763
|
+
"bud",
|
|
1764
|
+
"budget",
|
|
1765
|
+
"buffet",
|
|
1766
|
+
"bugle",
|
|
1767
|
+
"build",
|
|
1768
|
+
"built",
|
|
1769
|
+
"bulb",
|
|
1770
|
+
"bulk",
|
|
1771
|
+
"bull",
|
|
1772
|
+
"bullet",
|
|
1773
|
+
"bump",
|
|
1774
|
+
"bunch",
|
|
1775
|
+
"bundle",
|
|
1776
|
+
"bunk",
|
|
1777
|
+
"bunny",
|
|
1778
|
+
"burden",
|
|
1779
|
+
"bureau",
|
|
1780
|
+
"burn",
|
|
1781
|
+
"burst",
|
|
1782
|
+
"bush",
|
|
1783
|
+
"busy",
|
|
1784
|
+
"butler",
|
|
1785
|
+
"butter",
|
|
1786
|
+
"button",
|
|
1787
|
+
"buyer",
|
|
1788
|
+
"buzz",
|
|
1789
|
+
"cable",
|
|
1790
|
+
"cactus",
|
|
1791
|
+
"cage",
|
|
1792
|
+
"cake",
|
|
1793
|
+
"calf",
|
|
1794
|
+
"calm",
|
|
1795
|
+
"came",
|
|
1796
|
+
"camel",
|
|
1797
|
+
"camera",
|
|
1798
|
+
"camp",
|
|
1799
|
+
"canal",
|
|
1800
|
+
"candle",
|
|
1801
|
+
"candy",
|
|
1802
|
+
"cane",
|
|
1803
|
+
"canoe",
|
|
1804
|
+
"canopy",
|
|
1805
|
+
"canvas",
|
|
1806
|
+
"canyon",
|
|
1807
|
+
"cape",
|
|
1808
|
+
"capital",
|
|
1809
|
+
"captain",
|
|
1810
|
+
// group 4
|
|
1811
|
+
"car",
|
|
1812
|
+
"card",
|
|
1813
|
+
"care",
|
|
1814
|
+
"cargo",
|
|
1815
|
+
"carol",
|
|
1816
|
+
"carpet",
|
|
1817
|
+
"carrot",
|
|
1818
|
+
"carry",
|
|
1819
|
+
"cart",
|
|
1820
|
+
"carve",
|
|
1821
|
+
"case",
|
|
1822
|
+
"cash",
|
|
1823
|
+
"cask",
|
|
1824
|
+
"cast",
|
|
1825
|
+
"castle",
|
|
1826
|
+
"catch",
|
|
1827
|
+
"cater",
|
|
1828
|
+
"cattle",
|
|
1829
|
+
"cause",
|
|
1830
|
+
"cave",
|
|
1831
|
+
"caviar",
|
|
1832
|
+
"cavity",
|
|
1833
|
+
"cedar",
|
|
1834
|
+
"celery",
|
|
1835
|
+
"cell",
|
|
1836
|
+
"cellar",
|
|
1837
|
+
"cello",
|
|
1838
|
+
"cement",
|
|
1839
|
+
"census",
|
|
1840
|
+
"center",
|
|
1841
|
+
"cereal",
|
|
1842
|
+
"chain",
|
|
1843
|
+
"chalk",
|
|
1844
|
+
"chamber",
|
|
1845
|
+
"chance",
|
|
1846
|
+
"change",
|
|
1847
|
+
"channel",
|
|
1848
|
+
"chapel",
|
|
1849
|
+
"chapter",
|
|
1850
|
+
"char",
|
|
1851
|
+
"charcoal",
|
|
1852
|
+
"charge",
|
|
1853
|
+
"charm",
|
|
1854
|
+
"chart",
|
|
1855
|
+
"chase",
|
|
1856
|
+
"chasm",
|
|
1857
|
+
"cheap",
|
|
1858
|
+
"cheat",
|
|
1859
|
+
"check",
|
|
1860
|
+
"cheek",
|
|
1861
|
+
"cheer",
|
|
1862
|
+
"cheese",
|
|
1863
|
+
"chef",
|
|
1864
|
+
"cherry",
|
|
1865
|
+
"chess",
|
|
1866
|
+
"chest",
|
|
1867
|
+
"chew",
|
|
1868
|
+
"chick",
|
|
1869
|
+
"chief",
|
|
1870
|
+
"child",
|
|
1871
|
+
"chili",
|
|
1872
|
+
"chill",
|
|
1873
|
+
"chime",
|
|
1874
|
+
"chin",
|
|
1875
|
+
"china",
|
|
1876
|
+
"chip",
|
|
1877
|
+
"chirp",
|
|
1878
|
+
"chisel",
|
|
1879
|
+
"choir",
|
|
1880
|
+
"choke",
|
|
1881
|
+
"choose",
|
|
1882
|
+
"chop",
|
|
1883
|
+
"chord",
|
|
1884
|
+
"chore",
|
|
1885
|
+
"chorus",
|
|
1886
|
+
"chose",
|
|
1887
|
+
"chrome",
|
|
1888
|
+
"chubby",
|
|
1889
|
+
"chuck",
|
|
1890
|
+
"chunk",
|
|
1891
|
+
"church",
|
|
1892
|
+
"cider",
|
|
1893
|
+
"cigar",
|
|
1894
|
+
"cinder",
|
|
1895
|
+
"circle",
|
|
1896
|
+
"circus",
|
|
1897
|
+
"cite",
|
|
1898
|
+
"citizen",
|
|
1899
|
+
"city",
|
|
1900
|
+
"civic",
|
|
1901
|
+
// group 5
|
|
1902
|
+
"civil",
|
|
1903
|
+
"clad",
|
|
1904
|
+
"claim",
|
|
1905
|
+
"clam",
|
|
1906
|
+
"clamp",
|
|
1907
|
+
"clan",
|
|
1908
|
+
"clap",
|
|
1909
|
+
"clasp",
|
|
1910
|
+
"class",
|
|
1911
|
+
"clause",
|
|
1912
|
+
"claw",
|
|
1913
|
+
"clay",
|
|
1914
|
+
"clean",
|
|
1915
|
+
"clear",
|
|
1916
|
+
"cleat",
|
|
1917
|
+
"cleft",
|
|
1918
|
+
"clerk",
|
|
1919
|
+
"clever",
|
|
1920
|
+
"click",
|
|
1921
|
+
"client",
|
|
1922
|
+
"cliff",
|
|
1923
|
+
"climate",
|
|
1924
|
+
"climb",
|
|
1925
|
+
"cling",
|
|
1926
|
+
"clinic",
|
|
1927
|
+
"clip",
|
|
1928
|
+
"cloak",
|
|
1929
|
+
"clock",
|
|
1930
|
+
"clod",
|
|
1931
|
+
"clog",
|
|
1932
|
+
"clone",
|
|
1933
|
+
"close",
|
|
1934
|
+
"closet",
|
|
1935
|
+
"cloth",
|
|
1936
|
+
"cloud",
|
|
1937
|
+
"clove",
|
|
1938
|
+
"clown",
|
|
1939
|
+
"club",
|
|
1940
|
+
"cluck",
|
|
1941
|
+
"clue",
|
|
1942
|
+
"clump",
|
|
1943
|
+
"clumsy",
|
|
1944
|
+
"clung",
|
|
1945
|
+
"cluster",
|
|
1946
|
+
"coach",
|
|
1947
|
+
"coal",
|
|
1948
|
+
"coast",
|
|
1949
|
+
"coat",
|
|
1950
|
+
"cobalt",
|
|
1951
|
+
"cobra",
|
|
1952
|
+
"cobweb",
|
|
1953
|
+
"cocoa",
|
|
1954
|
+
"coconut",
|
|
1955
|
+
"cod",
|
|
1956
|
+
"code",
|
|
1957
|
+
"coffee",
|
|
1958
|
+
"coffin",
|
|
1959
|
+
"cog",
|
|
1960
|
+
"coil",
|
|
1961
|
+
"coin",
|
|
1962
|
+
"coke",
|
|
1963
|
+
"cold",
|
|
1964
|
+
"collar",
|
|
1965
|
+
"collie",
|
|
1966
|
+
"colony",
|
|
1967
|
+
"color",
|
|
1968
|
+
"colt",
|
|
1969
|
+
"column",
|
|
1970
|
+
"comb",
|
|
1971
|
+
"combat",
|
|
1972
|
+
"come",
|
|
1973
|
+
"comedy",
|
|
1974
|
+
"comet",
|
|
1975
|
+
"comfort",
|
|
1976
|
+
"comic",
|
|
1977
|
+
"comma",
|
|
1978
|
+
"common",
|
|
1979
|
+
"compact",
|
|
1980
|
+
"company",
|
|
1981
|
+
"compare",
|
|
1982
|
+
// group 6
|
|
1983
|
+
"compass",
|
|
1984
|
+
"compel",
|
|
1985
|
+
"complex",
|
|
1986
|
+
"comply",
|
|
1987
|
+
"comrade",
|
|
1988
|
+
"concise",
|
|
1989
|
+
"concrete",
|
|
1990
|
+
"condor",
|
|
1991
|
+
"cone",
|
|
1992
|
+
"confer",
|
|
1993
|
+
"conga",
|
|
1994
|
+
"conic",
|
|
1995
|
+
"connect",
|
|
1996
|
+
"consul",
|
|
1997
|
+
"contest",
|
|
1998
|
+
"context",
|
|
1999
|
+
"contract",
|
|
2000
|
+
"control",
|
|
2001
|
+
"convert",
|
|
2002
|
+
"convex",
|
|
2003
|
+
"convey",
|
|
2004
|
+
"convoy",
|
|
2005
|
+
"cook",
|
|
2006
|
+
"cookie",
|
|
2007
|
+
"cool",
|
|
2008
|
+
"coop",
|
|
2009
|
+
"cope",
|
|
2010
|
+
"copper",
|
|
2011
|
+
"copy",
|
|
2012
|
+
"coral",
|
|
2013
|
+
"cord",
|
|
2014
|
+
"core",
|
|
2015
|
+
"cork",
|
|
2016
|
+
"corner",
|
|
2017
|
+
"cornet",
|
|
2018
|
+
"corps",
|
|
2019
|
+
"cosmic",
|
|
2020
|
+
"cost",
|
|
2021
|
+
"costume",
|
|
2022
|
+
"cottage",
|
|
2023
|
+
"cotton",
|
|
2024
|
+
"couch",
|
|
2025
|
+
"cough",
|
|
2026
|
+
"could",
|
|
2027
|
+
"council",
|
|
2028
|
+
"counsel",
|
|
2029
|
+
"count",
|
|
2030
|
+
"counter",
|
|
2031
|
+
"country",
|
|
2032
|
+
"county",
|
|
2033
|
+
"coup",
|
|
2034
|
+
"couple",
|
|
2035
|
+
"courage",
|
|
2036
|
+
"course",
|
|
2037
|
+
"court",
|
|
2038
|
+
"cousin",
|
|
2039
|
+
"cove",
|
|
2040
|
+
"cover",
|
|
2041
|
+
"covet",
|
|
2042
|
+
"coward",
|
|
2043
|
+
"coyote",
|
|
2044
|
+
"crack",
|
|
2045
|
+
"cradle",
|
|
2046
|
+
"craft",
|
|
2047
|
+
"crag",
|
|
2048
|
+
"cram",
|
|
2049
|
+
"cramp",
|
|
2050
|
+
"cranberry",
|
|
2051
|
+
"crane",
|
|
2052
|
+
"crank",
|
|
2053
|
+
"crash",
|
|
2054
|
+
"crate",
|
|
2055
|
+
"crater",
|
|
2056
|
+
"cravat",
|
|
2057
|
+
"crave",
|
|
2058
|
+
"craw",
|
|
2059
|
+
"crawl",
|
|
2060
|
+
"crayon",
|
|
2061
|
+
"craze",
|
|
2062
|
+
"crazy",
|
|
2063
|
+
"creak",
|
|
2064
|
+
"cream",
|
|
2065
|
+
"create",
|
|
2066
|
+
"credit",
|
|
2067
|
+
"creed",
|
|
2068
|
+
"creek",
|
|
2069
|
+
"creep",
|
|
2070
|
+
"crepe",
|
|
2071
|
+
"cress",
|
|
2072
|
+
"crest",
|
|
2073
|
+
// group 7
|
|
2074
|
+
"crew",
|
|
2075
|
+
"crib",
|
|
2076
|
+
"cricket",
|
|
2077
|
+
"cried",
|
|
2078
|
+
"crier",
|
|
2079
|
+
"crime",
|
|
2080
|
+
"crimson",
|
|
2081
|
+
"cringe",
|
|
2082
|
+
"cripple",
|
|
2083
|
+
"crisis",
|
|
2084
|
+
"crisp",
|
|
2085
|
+
"critic",
|
|
2086
|
+
"croak",
|
|
2087
|
+
"crock",
|
|
2088
|
+
"crocus",
|
|
2089
|
+
"crony",
|
|
2090
|
+
"crook",
|
|
2091
|
+
"crop",
|
|
2092
|
+
"cross",
|
|
2093
|
+
"croup",
|
|
2094
|
+
"crow",
|
|
2095
|
+
"crowd",
|
|
2096
|
+
"crown",
|
|
2097
|
+
"crude",
|
|
2098
|
+
"cruel",
|
|
2099
|
+
"crumb",
|
|
2100
|
+
"crumple",
|
|
2101
|
+
"crush",
|
|
2102
|
+
"crust",
|
|
2103
|
+
"crutch",
|
|
2104
|
+
"cry",
|
|
2105
|
+
"crypt",
|
|
2106
|
+
"crystal",
|
|
2107
|
+
"cub",
|
|
2108
|
+
"cube",
|
|
2109
|
+
"cuckoo",
|
|
2110
|
+
"cucumber",
|
|
2111
|
+
"cuff",
|
|
2112
|
+
"cult",
|
|
2113
|
+
"culture",
|
|
2114
|
+
"cupboard",
|
|
2115
|
+
"curb",
|
|
2116
|
+
"curd",
|
|
2117
|
+
"cure",
|
|
2118
|
+
"curfew",
|
|
2119
|
+
"curl",
|
|
2120
|
+
"currant",
|
|
2121
|
+
"current",
|
|
2122
|
+
"curry",
|
|
2123
|
+
"curse",
|
|
2124
|
+
"curve",
|
|
2125
|
+
"cushion",
|
|
2126
|
+
"custard",
|
|
2127
|
+
"custom",
|
|
2128
|
+
"cut",
|
|
2129
|
+
"cute",
|
|
2130
|
+
"cutter",
|
|
2131
|
+
"cycle",
|
|
2132
|
+
"cyclone",
|
|
2133
|
+
"cynic",
|
|
2134
|
+
"cypress",
|
|
2135
|
+
"dad",
|
|
2136
|
+
"dagger",
|
|
2137
|
+
"daily",
|
|
2138
|
+
"dairy",
|
|
2139
|
+
"dale",
|
|
2140
|
+
"dally",
|
|
2141
|
+
"dam",
|
|
2142
|
+
"damage",
|
|
2143
|
+
"dame",
|
|
2144
|
+
"damp",
|
|
2145
|
+
"dance",
|
|
2146
|
+
"dandy",
|
|
2147
|
+
"danger",
|
|
2148
|
+
"dapple",
|
|
2149
|
+
"dare",
|
|
2150
|
+
"dark",
|
|
2151
|
+
"darling",
|
|
2152
|
+
"darn",
|
|
2153
|
+
"dart",
|
|
2154
|
+
// group 8
|
|
2155
|
+
"dash",
|
|
2156
|
+
"date",
|
|
2157
|
+
"datum",
|
|
2158
|
+
"daub",
|
|
2159
|
+
"daughter",
|
|
2160
|
+
"dawn",
|
|
2161
|
+
"daze",
|
|
2162
|
+
"dazzle",
|
|
2163
|
+
"deacon",
|
|
2164
|
+
"dead",
|
|
2165
|
+
"deaf",
|
|
2166
|
+
"deal",
|
|
2167
|
+
"dealer",
|
|
2168
|
+
"dean",
|
|
2169
|
+
"dear",
|
|
2170
|
+
"death",
|
|
2171
|
+
"debar",
|
|
2172
|
+
"debate",
|
|
2173
|
+
"debit",
|
|
2174
|
+
"debris",
|
|
2175
|
+
"debt",
|
|
2176
|
+
"decade",
|
|
2177
|
+
"decay",
|
|
2178
|
+
"decent",
|
|
2179
|
+
"decide",
|
|
2180
|
+
"deck",
|
|
2181
|
+
"declare",
|
|
2182
|
+
"decline",
|
|
2183
|
+
"decor",
|
|
2184
|
+
"decoy",
|
|
2185
|
+
"decrease",
|
|
2186
|
+
"decree",
|
|
2187
|
+
"deduct",
|
|
2188
|
+
"deed",
|
|
2189
|
+
"deep",
|
|
2190
|
+
"defeat",
|
|
2191
|
+
"defect",
|
|
2192
|
+
"defend",
|
|
2193
|
+
"defer",
|
|
2194
|
+
"deficit",
|
|
2195
|
+
"defile",
|
|
2196
|
+
"define",
|
|
2197
|
+
"deform",
|
|
2198
|
+
"defray",
|
|
2199
|
+
"defy",
|
|
2200
|
+
"degree",
|
|
2201
|
+
"delay",
|
|
2202
|
+
"delegate",
|
|
2203
|
+
"delight",
|
|
2204
|
+
"deliver",
|
|
2205
|
+
"dell",
|
|
2206
|
+
"delta",
|
|
2207
|
+
"deluge",
|
|
2208
|
+
"delve",
|
|
2209
|
+
"demand",
|
|
2210
|
+
"demean",
|
|
2211
|
+
"demerit",
|
|
2212
|
+
"demise",
|
|
2213
|
+
"demo",
|
|
2214
|
+
"demote",
|
|
2215
|
+
"demur",
|
|
2216
|
+
"den",
|
|
2217
|
+
"denial",
|
|
2218
|
+
"denote",
|
|
2219
|
+
"denounce",
|
|
2220
|
+
"dense",
|
|
2221
|
+
"density",
|
|
2222
|
+
"dent",
|
|
2223
|
+
"dental",
|
|
2224
|
+
"dentist",
|
|
2225
|
+
"deny",
|
|
2226
|
+
"depart",
|
|
2227
|
+
"depend",
|
|
2228
|
+
"depict",
|
|
2229
|
+
"deplore",
|
|
2230
|
+
"deport",
|
|
2231
|
+
"depose",
|
|
2232
|
+
"deposit",
|
|
2233
|
+
// group 9
|
|
2234
|
+
"depot",
|
|
2235
|
+
"depth",
|
|
2236
|
+
"deputy",
|
|
2237
|
+
"derby",
|
|
2238
|
+
"derive",
|
|
2239
|
+
"dervish",
|
|
2240
|
+
"descend",
|
|
2241
|
+
"descent",
|
|
2242
|
+
"describe",
|
|
2243
|
+
"desert",
|
|
2244
|
+
"deserve",
|
|
2245
|
+
"design",
|
|
2246
|
+
"desire",
|
|
2247
|
+
"desolate",
|
|
2248
|
+
"despair",
|
|
2249
|
+
"despise",
|
|
2250
|
+
"despite",
|
|
2251
|
+
"despot",
|
|
2252
|
+
"dessert",
|
|
2253
|
+
"destiny",
|
|
2254
|
+
"destroy",
|
|
2255
|
+
"detach",
|
|
2256
|
+
"detail",
|
|
2257
|
+
"detain",
|
|
2258
|
+
"detect",
|
|
2259
|
+
"deter",
|
|
2260
|
+
"detest",
|
|
2261
|
+
"detour",
|
|
2262
|
+
"deuce",
|
|
2263
|
+
"develop",
|
|
2264
|
+
"deviate",
|
|
2265
|
+
"device",
|
|
2266
|
+
"devil",
|
|
2267
|
+
"devise",
|
|
2268
|
+
"devoid",
|
|
2269
|
+
"devote",
|
|
2270
|
+
"devour",
|
|
2271
|
+
"devout",
|
|
2272
|
+
"dew",
|
|
2273
|
+
"diagram",
|
|
2274
|
+
"dial",
|
|
2275
|
+
"dialect",
|
|
2276
|
+
"dialogue",
|
|
2277
|
+
"diameter",
|
|
2278
|
+
"diamond",
|
|
2279
|
+
"diary",
|
|
2280
|
+
"dice",
|
|
2281
|
+
"dictate",
|
|
2282
|
+
"diction",
|
|
2283
|
+
"dictionary",
|
|
2284
|
+
"did",
|
|
2285
|
+
"die",
|
|
2286
|
+
"diet",
|
|
2287
|
+
"differ",
|
|
2288
|
+
"difficult",
|
|
2289
|
+
"diffuse",
|
|
2290
|
+
"dig",
|
|
2291
|
+
"digest",
|
|
2292
|
+
"digger",
|
|
2293
|
+
"digit",
|
|
2294
|
+
"dignity",
|
|
2295
|
+
"dike",
|
|
2296
|
+
"dilute",
|
|
2297
|
+
"dim",
|
|
2298
|
+
"dime",
|
|
2299
|
+
"diminish",
|
|
2300
|
+
"dimple",
|
|
2301
|
+
"din",
|
|
2302
|
+
"dine",
|
|
2303
|
+
"diner",
|
|
2304
|
+
"dinghy",
|
|
2305
|
+
"dingle",
|
|
2306
|
+
"dinner",
|
|
2307
|
+
"dint",
|
|
2308
|
+
"dip",
|
|
2309
|
+
"diphthong",
|
|
2310
|
+
"diploma",
|
|
2311
|
+
"dire",
|
|
2312
|
+
"direct",
|
|
2313
|
+
"director",
|
|
2314
|
+
// group 10
|
|
2315
|
+
"dirge",
|
|
2316
|
+
"dirk",
|
|
2317
|
+
"dirt",
|
|
2318
|
+
"dirty",
|
|
2319
|
+
"disable",
|
|
2320
|
+
"disarm",
|
|
2321
|
+
"disaster",
|
|
2322
|
+
"disavow",
|
|
2323
|
+
"disband",
|
|
2324
|
+
"discard",
|
|
2325
|
+
"discern",
|
|
2326
|
+
"discharge",
|
|
2327
|
+
"disciple",
|
|
2328
|
+
"discipline",
|
|
2329
|
+
"disclose",
|
|
2330
|
+
"discomfit",
|
|
2331
|
+
"discord",
|
|
2332
|
+
"discount",
|
|
2333
|
+
"discourse",
|
|
2334
|
+
"discover",
|
|
2335
|
+
"discreet",
|
|
2336
|
+
"discrepant",
|
|
2337
|
+
"discretion",
|
|
2338
|
+
"discuss",
|
|
2339
|
+
"disdain",
|
|
2340
|
+
"disease",
|
|
2341
|
+
"disfavor",
|
|
2342
|
+
"disfigure",
|
|
2343
|
+
"disgrace",
|
|
2344
|
+
"disguise",
|
|
2345
|
+
"disgust",
|
|
2346
|
+
"dish",
|
|
2347
|
+
"dishevel",
|
|
2348
|
+
"dishonest",
|
|
2349
|
+
"dishonor",
|
|
2350
|
+
"disinfect",
|
|
2351
|
+
"disinherit",
|
|
2352
|
+
"disintegrate",
|
|
2353
|
+
"dislike",
|
|
2354
|
+
"dislocate",
|
|
2355
|
+
"dislodge",
|
|
2356
|
+
"disloyal",
|
|
2357
|
+
"dismal",
|
|
2358
|
+
"dismantle",
|
|
2359
|
+
"dismay",
|
|
2360
|
+
"dismember",
|
|
2361
|
+
"dismiss",
|
|
2362
|
+
"dismount",
|
|
2363
|
+
"disobey",
|
|
2364
|
+
"disorder",
|
|
2365
|
+
"disown",
|
|
2366
|
+
"disparage",
|
|
2367
|
+
"disparate",
|
|
2368
|
+
"disparity",
|
|
2369
|
+
"dispatch",
|
|
2370
|
+
"dispel",
|
|
2371
|
+
"dispense",
|
|
2372
|
+
"disperse",
|
|
2373
|
+
"displace",
|
|
2374
|
+
"display",
|
|
2375
|
+
"displease",
|
|
2376
|
+
"dispose",
|
|
2377
|
+
"disprove",
|
|
2378
|
+
"dispipe",
|
|
2379
|
+
"disqualify",
|
|
2380
|
+
"disquiet",
|
|
2381
|
+
"disregard",
|
|
2382
|
+
"disrepute",
|
|
2383
|
+
"disrespect",
|
|
2384
|
+
"disrobe",
|
|
2385
|
+
"disrupt",
|
|
2386
|
+
"dissatisfy",
|
|
2387
|
+
"dissect",
|
|
2388
|
+
"dissemble",
|
|
2389
|
+
"disseminate",
|
|
2390
|
+
"dissent",
|
|
2391
|
+
"dissertation",
|
|
2392
|
+
"disservice",
|
|
2393
|
+
"dissident",
|
|
2394
|
+
"dissimilar",
|
|
2395
|
+
"dissipate",
|
|
2396
|
+
"dissolve",
|
|
2397
|
+
"dissonant",
|
|
2398
|
+
"dissuade",
|
|
2399
|
+
"distance",
|
|
2400
|
+
"distant",
|
|
2401
|
+
"distaste",
|
|
2402
|
+
"distemper",
|
|
2403
|
+
"distend",
|
|
2404
|
+
"distich",
|
|
2405
|
+
"distill",
|
|
2406
|
+
"distinct",
|
|
2407
|
+
"distinguish",
|
|
2408
|
+
"distort",
|
|
2409
|
+
"distract",
|
|
2410
|
+
"distrain",
|
|
2411
|
+
"distress",
|
|
2412
|
+
"distribute",
|
|
2413
|
+
"district",
|
|
2414
|
+
"distrust",
|
|
2415
|
+
"disturb",
|
|
2416
|
+
"disunion",
|
|
2417
|
+
"disuse",
|
|
2418
|
+
"ditch",
|
|
2419
|
+
"ditty",
|
|
2420
|
+
"diurnal",
|
|
2421
|
+
"divan",
|
|
2422
|
+
"dive",
|
|
2423
|
+
"diverge",
|
|
2424
|
+
"diverse",
|
|
2425
|
+
"diversion",
|
|
2426
|
+
"diversity",
|
|
2427
|
+
"divert",
|
|
2428
|
+
"divest",
|
|
2429
|
+
"divide",
|
|
2430
|
+
"dividend",
|
|
2431
|
+
"divine",
|
|
2432
|
+
"diviner",
|
|
2433
|
+
"divinity",
|
|
2434
|
+
"divisible",
|
|
2435
|
+
"division",
|
|
2436
|
+
"divisor",
|
|
2437
|
+
"divorce",
|
|
2438
|
+
"divulge",
|
|
2439
|
+
"dizzy",
|
|
2440
|
+
"do",
|
|
2441
|
+
"docile",
|
|
2442
|
+
"dock",
|
|
2443
|
+
"doctor",
|
|
2444
|
+
"doctrine",
|
|
2445
|
+
"document",
|
|
2446
|
+
"dodge",
|
|
2447
|
+
"doe",
|
|
2448
|
+
"doer",
|
|
2449
|
+
"dogma",
|
|
2450
|
+
"dogmatic",
|
|
2451
|
+
"dole",
|
|
2452
|
+
"doll",
|
|
2453
|
+
"dollar",
|
|
2454
|
+
"domain",
|
|
2455
|
+
"dome",
|
|
2456
|
+
"domestic",
|
|
2457
|
+
"domicile",
|
|
2458
|
+
"dominant",
|
|
2459
|
+
"dominate",
|
|
2460
|
+
"domineer",
|
|
2461
|
+
"dominion",
|
|
2462
|
+
"domino",
|
|
2463
|
+
"don",
|
|
2464
|
+
"donation",
|
|
2465
|
+
"done",
|
|
2466
|
+
"donkey",
|
|
2467
|
+
"donor",
|
|
2468
|
+
"doom",
|
|
2469
|
+
"dormant",
|
|
2470
|
+
"dormitory",
|
|
2471
|
+
"dormouse",
|
|
2472
|
+
"dose",
|
|
2473
|
+
"dot",
|
|
2474
|
+
"double",
|
|
2475
|
+
"doublet",
|
|
2476
|
+
"doubt",
|
|
2477
|
+
"doubtful",
|
|
2478
|
+
"dough",
|
|
2479
|
+
"doughnut",
|
|
2480
|
+
"doughty",
|
|
2481
|
+
"dour",
|
|
2482
|
+
"douse",
|
|
2483
|
+
"dove",
|
|
2484
|
+
"dowager",
|
|
2485
|
+
"dowdy",
|
|
2486
|
+
"dower",
|
|
2487
|
+
"downcast",
|
|
2488
|
+
"downfall",
|
|
2489
|
+
"downright",
|
|
2490
|
+
"downy",
|
|
2491
|
+
"dowry"
|
|
2492
|
+
];
|
|
2493
|
+
}
|
|
2494
|
+
allocateAnchor(usedAnchors) {
|
|
2495
|
+
for (const word of this.wordPool) {
|
|
2496
|
+
if (!usedAnchors.has(word)) {
|
|
2497
|
+
usedAnchors.add(word);
|
|
2498
|
+
return word;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
let counter = 1;
|
|
2502
|
+
while (true) {
|
|
2503
|
+
const word = `anchor_${counter}`;
|
|
2504
|
+
if (!usedAnchors.has(word)) {
|
|
2505
|
+
usedAnchors.add(word);
|
|
2506
|
+
return word;
|
|
2507
|
+
}
|
|
2508
|
+
counter++;
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
getAnchoredContent(filePath) {
|
|
2512
|
+
const absolutePath = path4.resolve(filePath);
|
|
2513
|
+
let lineStates = this.cache.get(absolutePath);
|
|
2514
|
+
if (!lineStates) {
|
|
2515
|
+
const content = fs4.readFileSync(absolutePath, "utf8");
|
|
2516
|
+
const hasTrailingNewline = content.endsWith("\n");
|
|
2517
|
+
const lines = hasTrailingNewline ? content.slice(0, -1).split("\n") : content.split("\n");
|
|
2518
|
+
const usedAnchors = /* @__PURE__ */ new Set();
|
|
2519
|
+
lineStates = lines.map((line) => {
|
|
2520
|
+
const anchor = this.allocateAnchor(usedAnchors);
|
|
2521
|
+
return { anchor, text: line };
|
|
2522
|
+
});
|
|
2523
|
+
this.cache.set(absolutePath, lineStates);
|
|
2524
|
+
}
|
|
2525
|
+
return lineStates.map((ls) => `${ls.anchor}\xA7${ls.text}`).join("\n");
|
|
2526
|
+
}
|
|
2527
|
+
applyAnchoredEdit(filePath, startAnchor, endAnchor, content) {
|
|
2528
|
+
const absolutePath = path4.resolve(filePath);
|
|
2529
|
+
let lineStates = this.cache.get(absolutePath);
|
|
2530
|
+
if (!lineStates) {
|
|
2531
|
+
this.getAnchoredContent(absolutePath);
|
|
2532
|
+
lineStates = this.cache.get(absolutePath);
|
|
2533
|
+
}
|
|
2534
|
+
const startIndex = lineStates.findIndex((ls) => ls.anchor === startAnchor);
|
|
2535
|
+
if (startIndex === -1) {
|
|
2536
|
+
throw new Error(`Start anchor "${startAnchor}" not found`);
|
|
2537
|
+
}
|
|
2538
|
+
const endIndex = lineStates.findIndex((ls) => ls.anchor === endAnchor);
|
|
2539
|
+
if (endIndex === -1) {
|
|
2540
|
+
throw new Error(`End anchor "${endAnchor}" not found`);
|
|
2541
|
+
}
|
|
2542
|
+
if (startIndex > endIndex) {
|
|
2543
|
+
throw new Error(`Invalid range: start anchor "${startAnchor}" is after end anchor "${endAnchor}"`);
|
|
2544
|
+
}
|
|
2545
|
+
const originalContent = fs4.readFileSync(absolutePath, "utf8");
|
|
2546
|
+
const hasTrailingNewline = originalContent.endsWith("\n");
|
|
2547
|
+
const oldLines = lineStates.map((ls) => ls.text);
|
|
2548
|
+
const cleanContent = content.endsWith("\n") ? content.slice(0, -1) : content;
|
|
2549
|
+
const newEditLines = cleanContent.split("\n");
|
|
2550
|
+
const updatedLines = [
|
|
2551
|
+
...oldLines.slice(0, startIndex),
|
|
2552
|
+
...newEditLines,
|
|
2553
|
+
...oldLines.slice(endIndex + 1)
|
|
2554
|
+
];
|
|
2555
|
+
const fileContentToWrite = updatedLines.join("\n") + (hasTrailingNewline ? "\n" : "");
|
|
2556
|
+
fs4.writeFileSync(absolutePath, fileContentToWrite, "utf8");
|
|
2557
|
+
const usedAnchors = /* @__PURE__ */ new Set();
|
|
2558
|
+
for (let i = 0; i < lineStates.length; i++) {
|
|
2559
|
+
usedAnchors.add(lineStates[i].anchor);
|
|
2560
|
+
}
|
|
2561
|
+
const oldEditSegment = lineStates.slice(startIndex, endIndex + 1);
|
|
2562
|
+
const oldEditLines = oldEditSegment.map((ls) => ls.text);
|
|
2563
|
+
const diffs = diffLines(oldEditLines.join("\n"), cleanContent);
|
|
2564
|
+
const reconciledEditStates = [];
|
|
2565
|
+
let oldEditIndex = 0;
|
|
2566
|
+
for (const change of diffs) {
|
|
2567
|
+
const changeLines = change.value.split("\n");
|
|
2568
|
+
if (changeLines.length > 1 && changeLines[changeLines.length - 1] === "") {
|
|
2569
|
+
changeLines.pop();
|
|
2570
|
+
}
|
|
2571
|
+
if (change.removed) {
|
|
2572
|
+
oldEditIndex += changeLines.length;
|
|
2573
|
+
} else if (change.added) {
|
|
2574
|
+
for (const line of changeLines) {
|
|
2575
|
+
const anchor = this.allocateAnchor(usedAnchors);
|
|
2576
|
+
reconciledEditStates.push({ anchor, text: line });
|
|
2577
|
+
}
|
|
2578
|
+
} else {
|
|
2579
|
+
for (let i = 0; i < changeLines.length; i++) {
|
|
2580
|
+
const oldLs = oldEditSegment[oldEditIndex];
|
|
2581
|
+
if (oldLs) {
|
|
2582
|
+
reconciledEditStates.push({ anchor: oldLs.anchor, text: oldLs.text });
|
|
2583
|
+
usedAnchors.add(oldLs.anchor);
|
|
2584
|
+
} else {
|
|
2585
|
+
const anchor = this.allocateAnchor(usedAnchors);
|
|
2586
|
+
reconciledEditStates.push({ anchor, text: changeLines[i] });
|
|
2587
|
+
}
|
|
2588
|
+
oldEditIndex++;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
const finalLineStates = [
|
|
2593
|
+
...lineStates.slice(0, startIndex),
|
|
2594
|
+
...reconciledEditStates,
|
|
2595
|
+
...lineStates.slice(endIndex + 1)
|
|
2596
|
+
];
|
|
2597
|
+
this.cache.set(absolutePath, finalLineStates);
|
|
2598
|
+
}
|
|
2599
|
+
};
|
|
2600
|
+
|
|
2601
|
+
// src/core/agents/developer-agent.ts
|
|
2602
|
+
import fs9 from "fs";
|
|
2603
|
+
import path10 from "path";
|
|
2604
|
+
|
|
2605
|
+
// src/core/agents/agent-tools.ts
|
|
2606
|
+
import fs6 from "fs";
|
|
2607
|
+
import path7 from "path";
|
|
2608
|
+
import fg from "fast-glob";
|
|
2609
|
+
import { exec } from "child_process";
|
|
2610
|
+
import { promisify } from "util";
|
|
2611
|
+
import { fileURLToPath } from "url";
|
|
2612
|
+
|
|
2613
|
+
// src/core/ast-editing/editors/code-editor-factory.ts
|
|
2614
|
+
import * as path6 from "path";
|
|
2615
|
+
|
|
2616
|
+
// src/core/ast-editing/editors/typescript-editor.ts
|
|
2617
|
+
import { Project, SyntaxKind } from "ts-morph";
|
|
2618
|
+
import * as path5 from "path";
|
|
2619
|
+
import * as fs5 from "fs";
|
|
2620
|
+
var TypeScriptEditor = class {
|
|
2621
|
+
project;
|
|
2622
|
+
constructor() {
|
|
2623
|
+
this.project = new Project({
|
|
2624
|
+
skipAddingFilesFromTsConfig: true,
|
|
2625
|
+
compilerOptions: {
|
|
2626
|
+
target: 99,
|
|
2627
|
+
// ESNext
|
|
2628
|
+
module: 99
|
|
2629
|
+
// ESNext
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2634
|
+
* Get or add source file to project
|
|
2635
|
+
*/
|
|
2636
|
+
getSourceFile(filePath) {
|
|
2637
|
+
const absolutePath = path5.resolve(filePath);
|
|
2638
|
+
let sourceFile = this.project.getSourceFile(absolutePath);
|
|
2639
|
+
if (!sourceFile) {
|
|
2640
|
+
if (!fs5.existsSync(absolutePath)) {
|
|
2641
|
+
throw new Error(`File not found: ${absolutePath}`);
|
|
2642
|
+
}
|
|
2643
|
+
sourceFile = this.project.addSourceFileAtPath(absolutePath);
|
|
2644
|
+
}
|
|
2645
|
+
return sourceFile;
|
|
2646
|
+
}
|
|
2647
|
+
/**
|
|
2648
|
+
* Get class declaration by name
|
|
2649
|
+
*/
|
|
2650
|
+
getClass(sourceFile, className) {
|
|
2651
|
+
const classDecl = sourceFile.getClass(className);
|
|
2652
|
+
if (!classDecl) {
|
|
2653
|
+
throw new Error(`Class "${className}" not found in ${sourceFile.getFilePath()}`);
|
|
2654
|
+
}
|
|
2655
|
+
return classDecl;
|
|
2656
|
+
}
|
|
2657
|
+
// ═══════════════════════════════════════════════════════
|
|
2658
|
+
// IMPLEMENTATION: listStructure
|
|
2659
|
+
// ═══════════════════════════════════════════════════════
|
|
2660
|
+
async listStructure(filePath) {
|
|
2661
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2662
|
+
const classes = sourceFile.getClasses().map((cls) => ({
|
|
2663
|
+
name: cls.getName() || "<anonymous>",
|
|
2664
|
+
methods: cls.getMethods().map((m) => this.extractMethodInfo(m)),
|
|
2665
|
+
properties: cls.getProperties().map((p) => this.extractPropertyInfo(p)),
|
|
2666
|
+
decorators: cls.getDecorators().map((d) => d.getText()),
|
|
2667
|
+
extendsClass: cls.getExtends()?.getText(),
|
|
2668
|
+
implementsInterfaces: cls.getImplements().map((i) => i.getText())
|
|
2669
|
+
}));
|
|
2670
|
+
const interfaces = sourceFile.getInterfaces().map((iface) => ({
|
|
2671
|
+
name: iface.getName(),
|
|
2672
|
+
properties: iface.getProperties().map((p) => this.extractPropertyInfo(p)),
|
|
2673
|
+
extends: iface.getExtends().map((e) => e.getText())
|
|
2674
|
+
}));
|
|
2675
|
+
const functions = sourceFile.getFunctions().map((fn) => ({
|
|
2676
|
+
name: fn.getName() || "<anonymous>",
|
|
2677
|
+
parameters: fn.getParameters().map((p) => ({
|
|
2678
|
+
name: p.getName(),
|
|
2679
|
+
type: p.getType().getText(),
|
|
2680
|
+
isOptional: p.isOptional()
|
|
2681
|
+
})),
|
|
2682
|
+
returnType: fn.getReturnType().getText(),
|
|
2683
|
+
isAsync: fn.isAsync(),
|
|
2684
|
+
isExported: fn.isExported()
|
|
2685
|
+
}));
|
|
2686
|
+
const imports = sourceFile.getImportDeclarations().map((imp) => ({
|
|
2687
|
+
modulePath: imp.getModuleSpecifierValue(),
|
|
2688
|
+
isDefault: !!imp.getDefaultImport(),
|
|
2689
|
+
namedImports: imp.getNamedImports().map((n) => n.getName()),
|
|
2690
|
+
namespaceImport: imp.getNamespaceImport()?.getText()
|
|
2691
|
+
}));
|
|
2692
|
+
const exports = sourceFile.getExportedDeclarations();
|
|
2693
|
+
const exportInfo = Array.from(exports.entries()).flatMap(
|
|
2694
|
+
([name, declarations]) => declarations.map((decl) => ({
|
|
2695
|
+
name,
|
|
2696
|
+
type: this.getDeclarationType(decl),
|
|
2697
|
+
isDefault: sourceFile.getDefaultExportSymbol()?.getName() === name
|
|
2698
|
+
}))
|
|
2699
|
+
);
|
|
2700
|
+
return {
|
|
2701
|
+
classes,
|
|
2702
|
+
interfaces,
|
|
2703
|
+
functions,
|
|
2704
|
+
imports,
|
|
2705
|
+
exports: exportInfo
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
extractMethodInfo(method) {
|
|
2709
|
+
return {
|
|
2710
|
+
name: method.getName(),
|
|
2711
|
+
parameters: method.getParameters().map((p) => ({
|
|
2712
|
+
name: p.getName(),
|
|
2713
|
+
type: p.getType().getText(),
|
|
2714
|
+
isOptional: p.isOptional()
|
|
2715
|
+
})),
|
|
2716
|
+
returnType: method.getReturnType().getText(),
|
|
2717
|
+
isAsync: method.isAsync(),
|
|
2718
|
+
isStatic: method.isStatic(),
|
|
2719
|
+
visibility: this.getVisibility(method),
|
|
2720
|
+
decorators: method.getDecorators().map((d) => d.getText())
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
extractPropertyInfo(property) {
|
|
2724
|
+
return {
|
|
2725
|
+
name: property.getName(),
|
|
2726
|
+
type: property.getType()?.getText(),
|
|
2727
|
+
visibility: this.getVisibility(property),
|
|
2728
|
+
isReadonly: property.isReadonly(),
|
|
2729
|
+
initializer: property.getInitializer()?.getText()
|
|
2730
|
+
};
|
|
2731
|
+
}
|
|
2732
|
+
getVisibility(node) {
|
|
2733
|
+
if (node.hasModifier?.(SyntaxKind.PrivateKeyword)) return "private";
|
|
2734
|
+
if (node.hasModifier?.(SyntaxKind.ProtectedKeyword)) return "protected";
|
|
2735
|
+
return "public";
|
|
2736
|
+
}
|
|
2737
|
+
getDeclarationType(decl) {
|
|
2738
|
+
if (decl.getKind() === SyntaxKind.ClassDeclaration) return "class";
|
|
2739
|
+
if (decl.getKind() === SyntaxKind.FunctionDeclaration) return "function";
|
|
2740
|
+
if (decl.getKind() === SyntaxKind.InterfaceDeclaration) return "interface";
|
|
2741
|
+
if (decl.getKind() === SyntaxKind.TypeAliasDeclaration) return "type";
|
|
2742
|
+
return "const";
|
|
2743
|
+
}
|
|
2744
|
+
// ═══════════════════════════════════════════════════════
|
|
2745
|
+
// IMPLEMENTATION: Class Operations
|
|
2746
|
+
// ═══════════════════════════════════════════════════════
|
|
2747
|
+
async addClass(filePath, className, options) {
|
|
2748
|
+
try {
|
|
2749
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2750
|
+
sourceFile.addClass({
|
|
2751
|
+
name: className,
|
|
2752
|
+
extends: options?.extendsClass,
|
|
2753
|
+
implements: options?.implementsInterfaces,
|
|
2754
|
+
isExported: true
|
|
2755
|
+
});
|
|
2756
|
+
await sourceFile.save();
|
|
2757
|
+
return true;
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
console.error(`Failed to add class "${className}":`, error);
|
|
2760
|
+
return false;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
async addProperty(filePath, className, propertyCode) {
|
|
2764
|
+
try {
|
|
2765
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2766
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2767
|
+
const closeBrace = classDecl.getEnd() - 1;
|
|
2768
|
+
classDecl.insertText(closeBrace, `
|
|
2769
|
+
${propertyCode}`);
|
|
2770
|
+
classDecl.formatText();
|
|
2771
|
+
await sourceFile.save();
|
|
2772
|
+
return true;
|
|
2773
|
+
} catch (error) {
|
|
2774
|
+
console.error(`Failed to add property to "${className}":`, error);
|
|
2775
|
+
return false;
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
async getProperty(filePath, className, propertyName) {
|
|
2779
|
+
try {
|
|
2780
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2781
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2782
|
+
const property = classDecl.getProperty(propertyName);
|
|
2783
|
+
if (!property) {
|
|
2784
|
+
return void 0;
|
|
2785
|
+
}
|
|
2786
|
+
return property.getText();
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
console.error(`Failed to get property "${propertyName}":`, error);
|
|
2789
|
+
return void 0;
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
async modifyProperty(filePath, className, propertyName, newCode) {
|
|
2793
|
+
try {
|
|
2794
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2795
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2796
|
+
const property = classDecl.getProperty(propertyName);
|
|
2797
|
+
if (!property) {
|
|
2798
|
+
throw new Error(`Property "${propertyName}" not found in class "${className}"`);
|
|
2799
|
+
}
|
|
2800
|
+
property.replaceWithText(newCode);
|
|
2801
|
+
classDecl.formatText();
|
|
2802
|
+
await sourceFile.save();
|
|
2803
|
+
return true;
|
|
2804
|
+
} catch (error) {
|
|
2805
|
+
console.error(`Failed to modify property "${propertyName}":`, error);
|
|
2806
|
+
return false;
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
async removeProperty(filePath, className, propertyName) {
|
|
2810
|
+
try {
|
|
2811
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2812
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2813
|
+
const property = classDecl.getProperty(propertyName);
|
|
2814
|
+
if (!property) {
|
|
2815
|
+
throw new Error(`Property "${propertyName}" not found in class "${className}"`);
|
|
2816
|
+
}
|
|
2817
|
+
property.remove();
|
|
2818
|
+
await sourceFile.save();
|
|
2819
|
+
return true;
|
|
2820
|
+
} catch (error) {
|
|
2821
|
+
console.error(`Failed to remove property "${propertyName}":`, error);
|
|
2822
|
+
return false;
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
async addMethod(filePath, className, methodCode) {
|
|
2826
|
+
try {
|
|
2827
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2828
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2829
|
+
const closeBrace = classDecl.getEnd() - 1;
|
|
2830
|
+
classDecl.insertText(closeBrace, `
|
|
2831
|
+
${methodCode}
|
|
2832
|
+
`);
|
|
2833
|
+
classDecl.formatText();
|
|
2834
|
+
await sourceFile.save();
|
|
2835
|
+
return true;
|
|
2836
|
+
} catch (error) {
|
|
2837
|
+
console.error(`Failed to add method to "${className}":`, error);
|
|
2838
|
+
return false;
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
2841
|
+
async modifyMethod(filePath, className, methodName, newBody) {
|
|
2842
|
+
try {
|
|
2843
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2844
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2845
|
+
const method = classDecl.getMethod(methodName);
|
|
2846
|
+
if (!method) {
|
|
2847
|
+
throw new Error(`Method "${methodName}" not found in class "${className}"`);
|
|
2848
|
+
}
|
|
2849
|
+
method.setBodyText(newBody);
|
|
2850
|
+
method.formatText();
|
|
2851
|
+
await sourceFile.save();
|
|
2852
|
+
return true;
|
|
2853
|
+
} catch (error) {
|
|
2854
|
+
console.error(`Failed to modify method "${methodName}":`, error);
|
|
2855
|
+
return false;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
async removeMethod(filePath, className, methodName) {
|
|
2859
|
+
try {
|
|
2860
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2861
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2862
|
+
const method = classDecl.getMethod(methodName);
|
|
2863
|
+
if (!method) {
|
|
2864
|
+
throw new Error(`Method "${methodName}" not found in class "${className}"`);
|
|
2865
|
+
}
|
|
2866
|
+
method.remove();
|
|
2867
|
+
await sourceFile.save();
|
|
2868
|
+
return true;
|
|
2869
|
+
} catch (error) {
|
|
2870
|
+
console.error(`Failed to remove method "${methodName}":`, error);
|
|
2871
|
+
return false;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
async addDecorator(filePath, className, decoratorCode) {
|
|
2875
|
+
try {
|
|
2876
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2877
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2878
|
+
const start = classDecl.getStart();
|
|
2879
|
+
sourceFile.insertText(start, `${decoratorCode}
|
|
2880
|
+
`);
|
|
2881
|
+
sourceFile.formatText();
|
|
2882
|
+
await sourceFile.save();
|
|
2883
|
+
return true;
|
|
2884
|
+
} catch (error) {
|
|
2885
|
+
console.error(`Failed to add decorator to "${className}":`, error);
|
|
2886
|
+
return false;
|
|
2887
|
+
}
|
|
2888
|
+
}
|
|
2889
|
+
async getMethod(filePath, className, methodName) {
|
|
2890
|
+
try {
|
|
2891
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2892
|
+
const classDecl = this.getClass(sourceFile, className);
|
|
2893
|
+
const method = classDecl.getMethod(methodName);
|
|
2894
|
+
if (!method) {
|
|
2895
|
+
return void 0;
|
|
2896
|
+
}
|
|
2897
|
+
return method.getText();
|
|
2898
|
+
} catch (error) {
|
|
2899
|
+
console.error(`Failed to get method "${methodName}":`, error);
|
|
2900
|
+
return void 0;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
// ═══════════════════════════════════════════════════════
|
|
2904
|
+
// IMPLEMENTATION: Interface/Type Operations
|
|
2905
|
+
// ═══════════════════════════════════════════════════════
|
|
2906
|
+
async addInterface(filePath, interfaceCode) {
|
|
2907
|
+
try {
|
|
2908
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2909
|
+
sourceFile.insertText(sourceFile.getEnd(), `
|
|
2910
|
+
|
|
2911
|
+
${interfaceCode}`);
|
|
2912
|
+
sourceFile.formatText();
|
|
2913
|
+
await sourceFile.save();
|
|
2914
|
+
return true;
|
|
2915
|
+
} catch (error) {
|
|
2916
|
+
console.error(`Failed to add interface:`, error);
|
|
2917
|
+
return false;
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
async addTypeAlias(filePath, typeCode) {
|
|
2921
|
+
try {
|
|
2922
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2923
|
+
sourceFile.insertText(sourceFile.getEnd(), `
|
|
2924
|
+
|
|
2925
|
+
${typeCode}`);
|
|
2926
|
+
sourceFile.formatText();
|
|
2927
|
+
await sourceFile.save();
|
|
2928
|
+
return true;
|
|
2929
|
+
} catch (error) {
|
|
2930
|
+
console.error(`Failed to add type alias:`, error);
|
|
2931
|
+
return false;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
// ═══════════════════════════════════════════════════════
|
|
2935
|
+
// IMPLEMENTATION: Function Operations
|
|
2936
|
+
// ═══════════════════════════════════════════════════════
|
|
2937
|
+
async addFunction(filePath, functionCode) {
|
|
2938
|
+
try {
|
|
2939
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2940
|
+
sourceFile.insertText(sourceFile.getEnd(), `
|
|
2941
|
+
|
|
2942
|
+
${functionCode}`);
|
|
2943
|
+
sourceFile.formatText();
|
|
2944
|
+
await sourceFile.save();
|
|
2945
|
+
return true;
|
|
2946
|
+
} catch (error) {
|
|
2947
|
+
console.error(`Failed to add function:`, error);
|
|
2948
|
+
return false;
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
async removeFunction(filePath, functionName) {
|
|
2952
|
+
try {
|
|
2953
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2954
|
+
const func = sourceFile.getFunction(functionName);
|
|
2955
|
+
if (!func) {
|
|
2956
|
+
throw new Error(`Function "${functionName}" not found`);
|
|
2957
|
+
}
|
|
2958
|
+
func.remove();
|
|
2959
|
+
await sourceFile.save();
|
|
2960
|
+
return true;
|
|
2961
|
+
} catch (error) {
|
|
2962
|
+
console.error(`Failed to remove function "${functionName}":`, error);
|
|
2963
|
+
return false;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
// ═══════════════════════════════════════════════════════
|
|
2967
|
+
// IMPLEMENTATION: Import/Export Operations
|
|
2968
|
+
// ═══════════════════════════════════════════════════════
|
|
2969
|
+
async addImport(filePath, importStatement) {
|
|
2970
|
+
try {
|
|
2971
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2972
|
+
const lastImport = sourceFile.getImportDeclarations().pop();
|
|
2973
|
+
const pos = lastImport ? lastImport.getEnd() : 0;
|
|
2974
|
+
sourceFile.insertText(pos, `
|
|
2975
|
+
${importStatement}`);
|
|
2976
|
+
this.organizeImports(filePath);
|
|
2977
|
+
await sourceFile.save();
|
|
2978
|
+
return true;
|
|
2979
|
+
} catch (error) {
|
|
2980
|
+
console.error(`Failed to add import:`, error);
|
|
2981
|
+
return false;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
async removeImport(filePath, modulePath) {
|
|
2985
|
+
try {
|
|
2986
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
2987
|
+
const importDecls = sourceFile.getImportDeclarations().filter((imp) => imp.getModuleSpecifierValue() === modulePath);
|
|
2988
|
+
if (importDecls.length === 0) {
|
|
2989
|
+
throw new Error(`Import from "${modulePath}" not found`);
|
|
2990
|
+
}
|
|
2991
|
+
importDecls.forEach((d) => d.remove());
|
|
2992
|
+
await sourceFile.save();
|
|
2993
|
+
return true;
|
|
2994
|
+
} catch (error) {
|
|
2995
|
+
console.error(`Failed to remove import from "${modulePath}":`, error);
|
|
2996
|
+
return false;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
async organizeImports(filePath) {
|
|
3000
|
+
try {
|
|
3001
|
+
const sourceFile = this.getSourceFile(filePath);
|
|
3002
|
+
sourceFile.organizeImports();
|
|
3003
|
+
const imports = sourceFile.getImportDeclarations();
|
|
3004
|
+
const importStructure = imports.map((i) => i.getStructure());
|
|
3005
|
+
importStructure.sort((a, b) => {
|
|
3006
|
+
return a.moduleSpecifier.localeCompare(b.moduleSpecifier);
|
|
3007
|
+
});
|
|
3008
|
+
imports.forEach((i) => i.remove());
|
|
3009
|
+
sourceFile.addImportDeclarations(importStructure);
|
|
3010
|
+
await sourceFile.save();
|
|
3011
|
+
return true;
|
|
3012
|
+
} catch (error) {
|
|
3013
|
+
console.error(`Failed to organize imports:`, error);
|
|
3014
|
+
return false;
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
};
|
|
3018
|
+
|
|
3019
|
+
// src/core/ast-editing/editors/code-editor-factory.ts
|
|
3020
|
+
var CodeEditorFactory = class {
|
|
3021
|
+
static tsEditor = null;
|
|
3022
|
+
/**
|
|
3023
|
+
* Returns appropriate editor for the file, or null if AST editing not supported
|
|
3024
|
+
*/
|
|
3025
|
+
static getEditor(filePath) {
|
|
3026
|
+
const ext = path6.extname(filePath).toLowerCase();
|
|
3027
|
+
if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
|
|
3028
|
+
if (!this.tsEditor) {
|
|
3029
|
+
this.tsEditor = new TypeScriptEditor();
|
|
3030
|
+
}
|
|
3031
|
+
return this.tsEditor;
|
|
3032
|
+
}
|
|
3033
|
+
return null;
|
|
3034
|
+
}
|
|
3035
|
+
/**
|
|
3036
|
+
* Clear cached editors (useful for testing)
|
|
3037
|
+
*/
|
|
3038
|
+
static clearCache() {
|
|
3039
|
+
this.tsEditor = null;
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
// src/core/agents/agent-tools.ts
|
|
3044
|
+
var execAsync = promisify(exec);
|
|
3045
|
+
function detectLineEnding(content) {
|
|
3046
|
+
const crlf = content.split("\r\n").length - 1;
|
|
3047
|
+
const lf = content.split("\n").length - 1 - crlf;
|
|
3048
|
+
return crlf > lf ? "\r\n" : "\n";
|
|
3049
|
+
}
|
|
3050
|
+
function handleListFiles(dirPath) {
|
|
3051
|
+
try {
|
|
3052
|
+
const fullPath = path7.resolve(process.cwd(), dirPath);
|
|
3053
|
+
if (!fs6.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
|
|
3054
|
+
const items = fs6.readdirSync(fullPath, { withFileTypes: true });
|
|
3055
|
+
return items.map((item) => {
|
|
3056
|
+
return `${item.isDirectory() ? "[DIR]" : "[FILE]"} ${item.name}`;
|
|
3057
|
+
}).join("\n");
|
|
3058
|
+
} catch (e) {
|
|
3059
|
+
return `Error listing files: ${e.message}`;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function handleReadFile(filePath, showLineNumbers = true) {
|
|
3063
|
+
try {
|
|
3064
|
+
const fullPath = path7.resolve(process.cwd(), filePath);
|
|
3065
|
+
if (!fs6.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
|
|
3066
|
+
const stats = fs6.statSync(fullPath);
|
|
3067
|
+
if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
|
|
3068
|
+
const content = fs6.readFileSync(fullPath, "utf-8");
|
|
3069
|
+
if (showLineNumbers) {
|
|
3070
|
+
const lines = content.split("\n");
|
|
3071
|
+
return lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n");
|
|
3072
|
+
}
|
|
3073
|
+
return content;
|
|
3074
|
+
} catch (e) {
|
|
3075
|
+
return `Error reading file: ${e.message}`;
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
3078
|
+
function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
|
|
3079
|
+
try {
|
|
3080
|
+
if (!fs6.existsSync(filePath)) {
|
|
3081
|
+
tui2.log.error(`\u274C File not found for modification: ${filePath}`);
|
|
3082
|
+
return false;
|
|
3083
|
+
}
|
|
3084
|
+
const currentFileContent = fs6.readFileSync(filePath, "utf-8");
|
|
3085
|
+
const lineEnding = detectLineEnding(currentFileContent);
|
|
3086
|
+
const lines = currentFileContent.split(lineEnding);
|
|
3087
|
+
if (startLine < 1 || startLine > lines.length) {
|
|
3088
|
+
tui2.log.error(`\u274C Invalid start line: ${startLine}. File has ${lines.length} lines.`);
|
|
3089
|
+
return false;
|
|
3090
|
+
}
|
|
3091
|
+
if (endLine < startLine || endLine > lines.length) {
|
|
3092
|
+
tui2.log.error(`\u274C Invalid end line: ${endLine}. Must be >= startLine and <= file length.`);
|
|
3093
|
+
return false;
|
|
3094
|
+
}
|
|
3095
|
+
const before = lines.slice(0, startLine - 1);
|
|
3096
|
+
const after = lines.slice(endLine);
|
|
3097
|
+
const newLines = newContent.split(lineEnding);
|
|
3098
|
+
const normalizedNewLines = newContent.replace(/\r\n/g, "\n").split("\n");
|
|
3099
|
+
const result = [...before, ...normalizedNewLines, ...after].join(lineEnding);
|
|
3100
|
+
const BOM = "\uFEFF";
|
|
3101
|
+
const finalContent = result.startsWith(BOM) ? result : BOM + result;
|
|
3102
|
+
fs6.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
|
|
3103
|
+
tui2.log.success(`\u2705 Replaced lines ${startLine}-${endLine} in ${filePath}`);
|
|
3104
|
+
return true;
|
|
3105
|
+
} catch (e) {
|
|
3106
|
+
tui2.log.error(`\u274C Error replacing line range: ${e.message}`);
|
|
3107
|
+
return false;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
function handleSearchFile(pattern) {
|
|
3111
|
+
try {
|
|
3112
|
+
const entries = fg.sync(pattern, { dot: true });
|
|
3113
|
+
if (entries.length === 0) return "No files found matching pattern.";
|
|
3114
|
+
return entries.slice(0, 50).join("\n");
|
|
3115
|
+
} catch (e) {
|
|
3116
|
+
return `Error searching files: ${e.message}`;
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
function handleSearchCode(globPattern, query, isRegex = false) {
|
|
3120
|
+
const MAX_MATCHES = 50;
|
|
3121
|
+
const MAX_FILE_SIZE_BYTES = 500 * 1024;
|
|
3122
|
+
try {
|
|
3123
|
+
const files = fg.sync(globPattern, { dot: true, absolute: false });
|
|
3124
|
+
if (files.length === 0) return `No files found matching pattern: "${globPattern}"`;
|
|
3125
|
+
let searchRegex;
|
|
3126
|
+
try {
|
|
3127
|
+
searchRegex = isRegex ? new RegExp(query, "gi") : new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
|
|
3128
|
+
} catch {
|
|
3129
|
+
return `Error: Invalid regex pattern: "${query}"`;
|
|
3130
|
+
}
|
|
3131
|
+
const results = [];
|
|
3132
|
+
let totalMatches = 0;
|
|
3133
|
+
for (const filePath of files) {
|
|
3134
|
+
if (totalMatches >= MAX_MATCHES) break;
|
|
3135
|
+
try {
|
|
3136
|
+
const fullPath = path7.resolve(process.cwd(), filePath);
|
|
3137
|
+
const stats = fs6.statSync(fullPath);
|
|
3138
|
+
if (stats.size > MAX_FILE_SIZE_BYTES) continue;
|
|
3139
|
+
const content = fs6.readFileSync(fullPath, "utf-8");
|
|
3140
|
+
const lines = content.split("\n");
|
|
3141
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3142
|
+
if (totalMatches >= MAX_MATCHES) break;
|
|
3143
|
+
searchRegex.lastIndex = 0;
|
|
3144
|
+
if (searchRegex.test(lines[i])) {
|
|
3145
|
+
results.push(`${filePath}:${i + 1}: ${lines[i].trim()}`);
|
|
3146
|
+
totalMatches++;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
} catch {
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
if (results.length === 0) {
|
|
3153
|
+
return `No matches found for "${query}" in files matching "${globPattern}"`;
|
|
3154
|
+
}
|
|
3155
|
+
const limited = totalMatches >= MAX_MATCHES ? ` (limited to ${MAX_MATCHES})` : "";
|
|
3156
|
+
return `Found ${totalMatches} match(es) for "${query}" in "${globPattern}"${limited}:
|
|
3157
|
+
${results.join("\n")}`;
|
|
3158
|
+
} catch (e) {
|
|
3159
|
+
return `Error searching code: ${e.message}`;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
function startSmartReplace(filePath, newContent, targetContent, tui2) {
|
|
3163
|
+
if (!fs6.existsSync(filePath)) {
|
|
3164
|
+
tui2.log.error(`\u274C File not found for modification: ${filePath}`);
|
|
3165
|
+
return false;
|
|
3166
|
+
}
|
|
3167
|
+
const currentFileContent = fs6.readFileSync(filePath, "utf-8");
|
|
3168
|
+
const normalizedTarget = targetContent.replace(/\r\n/g, "\n");
|
|
3169
|
+
const normalizedContent = currentFileContent.replace(/\r\n/g, "\n");
|
|
3170
|
+
if (!normalizedContent.includes(normalizedTarget)) {
|
|
3171
|
+
tui2.log.error(`\u274C Target content not found in ${filePath} (checked with normalized line endings). Modification aborted.`);
|
|
3172
|
+
console.log(colors.dim("--- Target Content Expected ---"));
|
|
3173
|
+
console.log(targetContent.substring(0, 200) + "...");
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
const occurrences = currentFileContent.split(targetContent).length - 1;
|
|
3177
|
+
if (occurrences > 1) {
|
|
3178
|
+
tui2.log.error(`\u274C Ambiguous target: Found ${occurrences} occurrences in ${filePath}. Modification aborted.`);
|
|
3179
|
+
return false;
|
|
3180
|
+
}
|
|
3181
|
+
const BOM = "\uFEFF";
|
|
3182
|
+
const updatedContent = currentFileContent.replace(targetContent, newContent);
|
|
3183
|
+
const finalContent = updatedContent.startsWith(BOM) ? updatedContent : BOM + updatedContent;
|
|
3184
|
+
fs6.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
|
|
3185
|
+
tui2.log.success(`\u2705 Smart Replace Applied: ${filePath}`);
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
async function handleRunCommand(command) {
|
|
3189
|
+
const { spawn } = await import("child_process");
|
|
3190
|
+
try {
|
|
3191
|
+
tui.log.info(`\u{1F4BB} Executing: ${colors.dim(command)}`);
|
|
3192
|
+
return new Promise((resolve2) => {
|
|
3193
|
+
const child = spawn(command, {
|
|
3194
|
+
shell: true,
|
|
3195
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3196
|
+
cwd: process.cwd()
|
|
3197
|
+
});
|
|
3198
|
+
let stdout = "";
|
|
3199
|
+
let stderr = "";
|
|
3200
|
+
const timer = setTimeout(() => {
|
|
3201
|
+
child.kill();
|
|
3202
|
+
resolve2(`Error: Command timed out after 5 minutes.
|
|
3203
|
+
Output so far:
|
|
3204
|
+
${stdout}
|
|
3205
|
+
${stderr}`);
|
|
3206
|
+
}, 5 * 60 * 1e3);
|
|
3207
|
+
child.stdout.on("data", (data) => {
|
|
3208
|
+
const chunk = data.toString();
|
|
3209
|
+
stdout += chunk;
|
|
3210
|
+
});
|
|
3211
|
+
child.stderr.on("data", (data) => {
|
|
3212
|
+
stderr += data.toString();
|
|
3213
|
+
});
|
|
3214
|
+
child.on("close", (code) => {
|
|
3215
|
+
clearTimeout(timer);
|
|
3216
|
+
if (code === 0) {
|
|
3217
|
+
resolve2(stdout.trim() || "Command executed successfully (no output).");
|
|
3218
|
+
} else {
|
|
3219
|
+
resolve2(`Command failed with exit code ${code}.
|
|
3220
|
+
STDERR:
|
|
3221
|
+
${stderr}
|
|
3222
|
+
STDOUT:
|
|
3223
|
+
${stdout}`);
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
child.on("error", (err) => {
|
|
3227
|
+
clearTimeout(timer);
|
|
3228
|
+
resolve2(`Error executing command: ${err.message}`);
|
|
3229
|
+
});
|
|
3230
|
+
});
|
|
3231
|
+
} catch (e) {
|
|
3232
|
+
return `Error launching command: ${e.message}`;
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
function resolveAstGrepCommand() {
|
|
3236
|
+
const isWin = process.platform === "win32";
|
|
3237
|
+
const binName = isWin ? "sg.cmd" : "sg";
|
|
3238
|
+
try {
|
|
3239
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
3240
|
+
let dir = path7.dirname(currentFile);
|
|
3241
|
+
for (let i = 0; i < 5; i++) {
|
|
3242
|
+
const candidate = path7.join(dir, "node_modules", ".bin", binName);
|
|
3243
|
+
if (fs6.existsSync(candidate)) {
|
|
3244
|
+
return `"${candidate}"`;
|
|
3245
|
+
}
|
|
3246
|
+
const parent = path7.dirname(dir);
|
|
3247
|
+
if (parent === dir) break;
|
|
3248
|
+
dir = parent;
|
|
3249
|
+
}
|
|
3250
|
+
} catch (e) {
|
|
3251
|
+
}
|
|
3252
|
+
const cwdBin = path7.resolve(process.cwd(), "node_modules", ".bin", binName);
|
|
3253
|
+
if (fs6.existsSync(cwdBin)) {
|
|
3254
|
+
return `"${cwdBin}"`;
|
|
3255
|
+
}
|
|
3256
|
+
return "npx sg";
|
|
3257
|
+
}
|
|
3258
|
+
async function astGrepSearch(pattern, filePath, language, tui2) {
|
|
3259
|
+
const { spawn } = await import("child_process");
|
|
3260
|
+
try {
|
|
3261
|
+
if (!fs6.existsSync(filePath)) {
|
|
3262
|
+
return `\u274C File not found: ${filePath}`;
|
|
3263
|
+
}
|
|
3264
|
+
const sgCmd = resolveAstGrepCommand();
|
|
3265
|
+
const cmd = `${sgCmd} run -p "${pattern}" -l ${language} --json ${filePath}`;
|
|
3266
|
+
tui2.log.info(`\u{1F50D} [AST-GREP] Searching: ${cmd}`);
|
|
3267
|
+
return new Promise((resolve2) => {
|
|
3268
|
+
const child = spawn(cmd, {
|
|
3269
|
+
shell: true,
|
|
3270
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3271
|
+
cwd: process.cwd(),
|
|
3272
|
+
env: { ...process.env, NO_COLOR: "true" }
|
|
3273
|
+
// Avoid ANSI codes in JSON output
|
|
3274
|
+
});
|
|
3275
|
+
let stdout = "";
|
|
3276
|
+
let stderr = "";
|
|
3277
|
+
child.stdout.on("data", (data) => stdout += data.toString());
|
|
3278
|
+
child.stderr.on("data", (data) => stderr += data.toString());
|
|
3279
|
+
child.on("close", (code) => {
|
|
3280
|
+
if (code === 0 && stdout) {
|
|
3281
|
+
resolve2(stdout);
|
|
3282
|
+
} else if (code === 1 && !stderr) {
|
|
3283
|
+
resolve2("No structural matches found.");
|
|
3284
|
+
} else {
|
|
3285
|
+
if (!stdout && !stderr) resolve2("No structural matches found.");
|
|
3286
|
+
else {
|
|
3287
|
+
tui2.log.error(`\u274C ast-grep search error (code ${code}): ${stderr}`);
|
|
3288
|
+
resolve2(`Error executing ast-grep search: ${stderr || stdout}`);
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
});
|
|
3292
|
+
child.on("error", (err) => {
|
|
3293
|
+
resolve2(`Error executing ast-grep search: ${err.message}`);
|
|
3294
|
+
});
|
|
3295
|
+
});
|
|
3296
|
+
} catch (e) {
|
|
3297
|
+
tui2.log.error(`\u274C ast-grep search exception: ${e.message}`);
|
|
3298
|
+
return `Error executing ast-grep search: ${e.message}`;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
|
|
3302
|
+
const { spawn } = await import("child_process");
|
|
3303
|
+
try {
|
|
3304
|
+
if (!fs6.existsSync(filePath)) {
|
|
3305
|
+
tui2.log.error(`\u274C File not found for AST modification: ${filePath}`);
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
const sgCmd = resolveAstGrepCommand();
|
|
3309
|
+
const cmd = `${sgCmd} run -p "${pattern}" -r "${fix}" -l ${language} ${filePath} --update-all`;
|
|
3310
|
+
tui2.log.info(`\u270F\uFE0F [AST-GREP] Rewriting: pattern="${pattern}" fix="${fix.substring(0, 50)}..."`);
|
|
3311
|
+
return new Promise((resolve2) => {
|
|
3312
|
+
const child = spawn(cmd, {
|
|
3313
|
+
shell: true,
|
|
3314
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3315
|
+
cwd: process.cwd()
|
|
3316
|
+
});
|
|
3317
|
+
let stderr = "";
|
|
3318
|
+
child.stderr.on("data", (data) => stderr += data.toString());
|
|
3319
|
+
child.on("close", (code) => {
|
|
3320
|
+
if (code === 0) {
|
|
3321
|
+
tui2.log.success(`\u2705 AST Rewrite applied to ${filePath}`);
|
|
3322
|
+
resolve2(true);
|
|
3323
|
+
} else {
|
|
3324
|
+
tui2.log.error(`\u274C AST Rewrite failed (code ${code}): ${stderr}`);
|
|
3325
|
+
resolve2(false);
|
|
3326
|
+
}
|
|
3327
|
+
});
|
|
3328
|
+
child.on("error", (err) => {
|
|
3329
|
+
tui2.log.error(`\u274C AST Rewrite spawn error: ${err.message}`);
|
|
3330
|
+
resolve2(false);
|
|
3331
|
+
});
|
|
3332
|
+
});
|
|
3333
|
+
} catch (e) {
|
|
3334
|
+
tui2.log.error(`\u274C Unexpected error in astGrepRewrite: ${e.message}`);
|
|
3335
|
+
return false;
|
|
3336
|
+
}
|
|
3337
|
+
}
|
|
3338
|
+
async function astListStructure(filePath) {
|
|
3339
|
+
try {
|
|
3340
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3341
|
+
if (!editor) {
|
|
3342
|
+
return `[AST Error] File type not supported: ${filePath}. Use read_file instead.`;
|
|
3343
|
+
}
|
|
3344
|
+
const structure = await editor.listStructure(filePath);
|
|
3345
|
+
let output = `[AST Structure of ${filePath}]
|
|
3346
|
+
|
|
3347
|
+
`;
|
|
3348
|
+
if (structure.classes.length > 0) {
|
|
3349
|
+
output += `CLASSES:
|
|
3350
|
+
`;
|
|
3351
|
+
structure.classes.forEach((cls) => {
|
|
3352
|
+
output += ` - ${cls.name}`;
|
|
3353
|
+
if (cls.extendsClass) output += ` extends ${cls.extendsClass}`;
|
|
3354
|
+
if (cls.implementsInterfaces.length > 0) {
|
|
3355
|
+
output += ` implements ${cls.implementsInterfaces.join(", ")}`;
|
|
3356
|
+
}
|
|
3357
|
+
output += `
|
|
3358
|
+
`;
|
|
3359
|
+
if (cls.decorators.length > 0) {
|
|
3360
|
+
output += ` Decorators: ${cls.decorators.join(", ")}
|
|
3361
|
+
`;
|
|
3362
|
+
}
|
|
3363
|
+
if (cls.properties.length > 0) {
|
|
3364
|
+
output += ` Properties:
|
|
3365
|
+
`;
|
|
3366
|
+
cls.properties.forEach((prop) => {
|
|
3367
|
+
output += ` - ${prop.visibility} ${prop.name}: ${prop.type}
|
|
3368
|
+
`;
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
if (cls.methods.length > 0) {
|
|
3372
|
+
output += ` Methods:
|
|
3373
|
+
`;
|
|
3374
|
+
cls.methods.forEach((method) => {
|
|
3375
|
+
const params = method.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
3376
|
+
output += ` - ${method.visibility} ${method.name}(${params}): ${method.returnType}
|
|
3377
|
+
`;
|
|
3378
|
+
});
|
|
3379
|
+
}
|
|
3380
|
+
output += `
|
|
3381
|
+
`;
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
if (structure.interfaces.length > 0) {
|
|
3385
|
+
output += `INTERFACES:
|
|
3386
|
+
`;
|
|
3387
|
+
structure.interfaces.forEach((iface) => {
|
|
3388
|
+
output += ` - ${iface.name}
|
|
3389
|
+
`;
|
|
3390
|
+
iface.properties.forEach((prop) => {
|
|
3391
|
+
output += ` ${prop.name}: ${prop.type}
|
|
3392
|
+
`;
|
|
3393
|
+
});
|
|
3394
|
+
});
|
|
3395
|
+
output += `
|
|
3396
|
+
`;
|
|
3397
|
+
}
|
|
3398
|
+
if (structure.functions.length > 0) {
|
|
3399
|
+
output += `FUNCTIONS:
|
|
3400
|
+
`;
|
|
3401
|
+
structure.functions.forEach((fn) => {
|
|
3402
|
+
const params = fn.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
|
|
3403
|
+
output += ` - ${fn.name}(${params}): ${fn.returnType}
|
|
3404
|
+
`;
|
|
3405
|
+
});
|
|
3406
|
+
output += `
|
|
3407
|
+
`;
|
|
3408
|
+
}
|
|
3409
|
+
if (structure.imports.length > 0) {
|
|
3410
|
+
output += `IMPORTS:
|
|
3411
|
+
`;
|
|
3412
|
+
structure.imports.forEach((imp) => {
|
|
3413
|
+
output += ` - from "${imp.modulePath}": ${imp.namedImports.join(", ")}
|
|
3414
|
+
`;
|
|
3415
|
+
});
|
|
3416
|
+
}
|
|
3417
|
+
return output;
|
|
3418
|
+
} catch (error) {
|
|
3419
|
+
return `[AST Error] ${error.message}`;
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
async function astAddMethod(filePath, className, methodCode) {
|
|
3423
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3424
|
+
if (!editor) {
|
|
3425
|
+
throw new Error(`No AST editor available for ${filePath}`);
|
|
3426
|
+
}
|
|
3427
|
+
return await editor.addMethod(filePath, className, methodCode);
|
|
3428
|
+
}
|
|
3429
|
+
async function astGetMethod(filePath, className, methodName) {
|
|
3430
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3431
|
+
if (!editor) {
|
|
3432
|
+
throw new Error(`No AST editor available for ${filePath}`);
|
|
3433
|
+
}
|
|
3434
|
+
const methodContent = await editor.getMethod(filePath, className, methodName);
|
|
3435
|
+
if (!methodContent) {
|
|
3436
|
+
return `Method "${methodName}" not found in class "${className}"`;
|
|
3437
|
+
}
|
|
3438
|
+
return methodContent;
|
|
3439
|
+
}
|
|
3440
|
+
async function astAddClass(filePath, className, extendsClass, implementsInterfaces) {
|
|
3441
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3442
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3443
|
+
return await editor.addClass(filePath, className, { extendsClass, implementsInterfaces });
|
|
3444
|
+
}
|
|
3445
|
+
async function astAddProperty(filePath, className, propertyCode) {
|
|
3446
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3447
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3448
|
+
return await editor.addProperty(filePath, className, propertyCode);
|
|
3449
|
+
}
|
|
3450
|
+
async function astGetProperty(filePath, className, propertyName) {
|
|
3451
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3452
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3453
|
+
const propContent = await editor.getProperty(filePath, className, propertyName);
|
|
3454
|
+
if (!propContent) {
|
|
3455
|
+
return `Property "${propertyName}" not found in class "${className}"`;
|
|
3456
|
+
}
|
|
3457
|
+
return propContent;
|
|
3458
|
+
}
|
|
3459
|
+
async function astModifyProperty(filePath, className, propertyName, propertyCode) {
|
|
3460
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3461
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3462
|
+
return await editor.modifyProperty(filePath, className, propertyName, propertyCode);
|
|
3463
|
+
}
|
|
3464
|
+
async function astRemoveProperty(filePath, className, propertyName) {
|
|
3465
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3466
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3467
|
+
return await editor.removeProperty(filePath, className, propertyName);
|
|
3468
|
+
}
|
|
3469
|
+
async function astModifyMethod(filePath, className, methodName, newBody) {
|
|
3470
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3471
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3472
|
+
return await editor.modifyMethod(filePath, className, methodName, newBody);
|
|
3473
|
+
}
|
|
3474
|
+
async function astRemoveMethod(filePath, className, methodName) {
|
|
3475
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3476
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3477
|
+
return await editor.removeMethod(filePath, className, methodName);
|
|
3478
|
+
}
|
|
3479
|
+
async function astAddDecorator(filePath, className, decoratorCode) {
|
|
3480
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3481
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3482
|
+
return await editor.addDecorator(filePath, className, decoratorCode);
|
|
3483
|
+
}
|
|
3484
|
+
async function astAddInterface(filePath, interfaceCode) {
|
|
3485
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3486
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3487
|
+
return await editor.addInterface(filePath, interfaceCode);
|
|
3488
|
+
}
|
|
3489
|
+
async function astAddTypeAlias(filePath, typeCode) {
|
|
3490
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3491
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3492
|
+
return await editor.addTypeAlias(filePath, typeCode);
|
|
3493
|
+
}
|
|
3494
|
+
async function astAddFunction(filePath, functionCode) {
|
|
3495
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3496
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3497
|
+
return await editor.addFunction(filePath, functionCode);
|
|
3498
|
+
}
|
|
3499
|
+
async function astRemoveFunction(filePath, functionName) {
|
|
3500
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3501
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3502
|
+
return await editor.removeFunction(filePath, functionName);
|
|
3503
|
+
}
|
|
3504
|
+
async function astAddImport(filePath, importStatement) {
|
|
3505
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3506
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3507
|
+
return await editor.addImport(filePath, importStatement);
|
|
3508
|
+
}
|
|
3509
|
+
async function astRemoveImport(filePath, modulePath) {
|
|
3510
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3511
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3512
|
+
return await editor.removeImport(filePath, modulePath);
|
|
3513
|
+
}
|
|
3514
|
+
async function astOrganizeImports(filePath) {
|
|
3515
|
+
const editor = CodeEditorFactory.getEditor(filePath);
|
|
3516
|
+
if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
|
|
3517
|
+
return await editor.organizeImports(filePath);
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
// src/core/workflow/skill-manager.ts
|
|
3521
|
+
import fs7 from "fs/promises";
|
|
3522
|
+
import path8 from "path";
|
|
3523
|
+
import os2 from "os";
|
|
3524
|
+
var SkillManager = class {
|
|
3525
|
+
activeSkills = /* @__PURE__ */ new Set();
|
|
3526
|
+
skillPrompts = /* @__PURE__ */ new Map();
|
|
3527
|
+
async loadSkillFromFile(filePath) {
|
|
3528
|
+
const content = await fs7.readFile(filePath, "utf-8");
|
|
3529
|
+
const cleanContent = content.replace(/^---[\s\S]*?---\s*/, "");
|
|
3530
|
+
return cleanContent;
|
|
3531
|
+
}
|
|
3532
|
+
async activateSkill(skillName) {
|
|
3533
|
+
if (this.activeSkills.has(skillName)) {
|
|
3534
|
+
return `Skill ${skillName} is already active.`;
|
|
3535
|
+
}
|
|
3536
|
+
this.reset();
|
|
3537
|
+
const globalPath = path8.join(os2.homedir(), ".shark", "skills", skillName, "SKILL.md");
|
|
3538
|
+
const localPath = path8.join(process.cwd(), ".agents", "skills", skillName, "SKILL.md");
|
|
3539
|
+
let skillPath = "";
|
|
3540
|
+
try {
|
|
3541
|
+
await fs7.access(localPath);
|
|
3542
|
+
skillPath = localPath;
|
|
3543
|
+
} catch {
|
|
3544
|
+
try {
|
|
3545
|
+
await fs7.access(globalPath);
|
|
3546
|
+
skillPath = globalPath;
|
|
3547
|
+
} catch {
|
|
3548
|
+
throw new Error(`Skill '${skillName}' not found globally or locally.`);
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
const prompt = await this.loadSkillFromFile(skillPath);
|
|
3552
|
+
this.activeSkills.add(skillName);
|
|
3553
|
+
this.skillPrompts.set(skillName, prompt);
|
|
3554
|
+
return prompt;
|
|
3555
|
+
}
|
|
3556
|
+
getSystemInstructionExtension() {
|
|
3557
|
+
if (this.activeSkills.size === 0) return "";
|
|
3558
|
+
let extension = "\n\n<EXTREMELY_IMPORTANT>\n";
|
|
3559
|
+
for (const [name, prompt] of this.skillPrompts.entries()) {
|
|
3560
|
+
extension += `
|
|
3561
|
+
--- ACTIVE SKILL: ${name} ---
|
|
3562
|
+
${prompt}
|
|
3563
|
+
`;
|
|
3564
|
+
}
|
|
3565
|
+
extension += "\n</EXTREMELY_IMPORTANT>\n";
|
|
3566
|
+
return extension;
|
|
3567
|
+
}
|
|
3568
|
+
async listAvailableSkills() {
|
|
3569
|
+
const globalSkillsDir = path8.join(os2.homedir(), ".shark", "skills");
|
|
3570
|
+
const localSkillsDir = path8.join(process.cwd(), ".agents", "skills");
|
|
3571
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
3572
|
+
const readDir = async (dir) => {
|
|
3573
|
+
try {
|
|
3574
|
+
const entries = await fs7.readdir(dir, { withFileTypes: true });
|
|
3575
|
+
for (const entry of entries) {
|
|
3576
|
+
if (entry.isDirectory()) {
|
|
3577
|
+
const skillMdPath = path8.join(dir, entry.name, "SKILL.md");
|
|
3578
|
+
try {
|
|
3579
|
+
await fs7.access(skillMdPath);
|
|
3580
|
+
skillNames.add(entry.name);
|
|
3581
|
+
} catch {
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
} catch {
|
|
3586
|
+
}
|
|
3587
|
+
};
|
|
3588
|
+
await readDir(globalSkillsDir);
|
|
3589
|
+
await readDir(localSkillsDir);
|
|
3590
|
+
return Array.from(skillNames).sort();
|
|
3591
|
+
}
|
|
3592
|
+
reset() {
|
|
3593
|
+
this.activeSkills.clear();
|
|
3594
|
+
this.skillPrompts.clear();
|
|
3595
|
+
}
|
|
3596
|
+
};
|
|
3597
|
+
var skillManager = new SkillManager();
|
|
3598
|
+
|
|
3599
|
+
// src/core/workflow/subagent-manager.ts
|
|
3600
|
+
import crypto2 from "crypto";
|
|
3601
|
+
import fs8 from "fs";
|
|
3602
|
+
import path9 from "path";
|
|
3603
|
+
import { fork } from "child_process";
|
|
3604
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3605
|
+
var SubagentManager = class {
|
|
3606
|
+
subagents = /* @__PURE__ */ new Map();
|
|
3607
|
+
customTypes = /* @__PURE__ */ new Map();
|
|
3608
|
+
registerSubagent(id, type, role, parentId) {
|
|
3609
|
+
this.subagents.set(id, { id, type, role, status: "running", parentId });
|
|
3610
|
+
}
|
|
3611
|
+
terminateSubagent(id, success = true, isCancelled = false) {
|
|
3612
|
+
const state = this.subagents.get(id);
|
|
3613
|
+
if (state) {
|
|
3614
|
+
if (state.status === "cancelled") {
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
if (isCancelled) {
|
|
3618
|
+
state.status = "cancelled";
|
|
3619
|
+
} else {
|
|
3620
|
+
state.status = success ? "completed" : "failed";
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
isSubagentActive(id) {
|
|
3625
|
+
return this.subagents.get(id)?.status === "running";
|
|
3626
|
+
}
|
|
3627
|
+
hasSubagent(id) {
|
|
3628
|
+
return this.subagents.has(id);
|
|
3629
|
+
}
|
|
3630
|
+
getSubagentState(id) {
|
|
3631
|
+
return this.subagents.get(id);
|
|
3632
|
+
}
|
|
3633
|
+
updateSubagentSummary(id, summary) {
|
|
3634
|
+
const state = this.subagents.get(id);
|
|
3635
|
+
if (state) {
|
|
3636
|
+
state.summary = summary;
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
sendMessage(recipient, message) {
|
|
3640
|
+
const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", recipient);
|
|
3641
|
+
fs8.mkdirSync(mailboxDir, { recursive: true });
|
|
3642
|
+
const filePath = path9.join(mailboxDir, `${Date.now()}-${crypto2.randomUUID()}.json`);
|
|
3643
|
+
fs8.writeFileSync(filePath, JSON.stringify({ message }), "utf-8");
|
|
3644
|
+
}
|
|
3645
|
+
retrieveMessages(id) {
|
|
3646
|
+
const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", id);
|
|
3647
|
+
if (!fs8.existsSync(mailboxDir)) {
|
|
3648
|
+
return [];
|
|
3649
|
+
}
|
|
3650
|
+
const files = fs8.readdirSync(mailboxDir);
|
|
3651
|
+
files.sort();
|
|
3652
|
+
const messages = [];
|
|
3653
|
+
for (const file of files) {
|
|
3654
|
+
const filePath = path9.join(mailboxDir, file);
|
|
3655
|
+
try {
|
|
3656
|
+
const content = fs8.readFileSync(filePath, "utf-8");
|
|
3657
|
+
const data = JSON.parse(content);
|
|
3658
|
+
if (data && typeof data.message === "string") {
|
|
3659
|
+
messages.push(data.message);
|
|
3660
|
+
}
|
|
3661
|
+
} catch (e) {
|
|
3662
|
+
}
|
|
3663
|
+
try {
|
|
3664
|
+
fs8.unlinkSync(filePath);
|
|
3665
|
+
} catch (e) {
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
return messages;
|
|
3669
|
+
}
|
|
3670
|
+
peekMessages(id) {
|
|
3671
|
+
const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", id);
|
|
3672
|
+
if (!fs8.existsSync(mailboxDir)) {
|
|
3673
|
+
return [];
|
|
3674
|
+
}
|
|
3675
|
+
const files = fs8.readdirSync(mailboxDir);
|
|
3676
|
+
files.sort();
|
|
3677
|
+
const messages = [];
|
|
3678
|
+
for (const file of files) {
|
|
3679
|
+
const filePath = path9.join(mailboxDir, file);
|
|
3680
|
+
try {
|
|
3681
|
+
const content = fs8.readFileSync(filePath, "utf-8");
|
|
3682
|
+
const data = JSON.parse(content);
|
|
3683
|
+
if (data && typeof data.message === "string") {
|
|
3684
|
+
messages.push(data.message);
|
|
3685
|
+
}
|
|
3686
|
+
} catch (e) {
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
return messages;
|
|
3690
|
+
}
|
|
3691
|
+
defineSubagentType(name, description, systemPrompt, options = {}) {
|
|
3692
|
+
this.customTypes.set(name, {
|
|
3693
|
+
name,
|
|
3694
|
+
description,
|
|
3695
|
+
systemPrompt,
|
|
3696
|
+
...options
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
getCustomSubagentType(name) {
|
|
3700
|
+
return this.customTypes.get(name);
|
|
3701
|
+
}
|
|
3702
|
+
getSubagentLogs(id, maxLines = 50) {
|
|
3703
|
+
if (!/^[a-zA-Z0-9-]+$/.test(id)) {
|
|
3704
|
+
throw new Error("Invalid subagent ID format");
|
|
3705
|
+
}
|
|
3706
|
+
const projectRoot = process.cwd();
|
|
3707
|
+
const logFile = path9.resolve(projectRoot, "_sharkrc", "history", `subagent-${id}-console.log`);
|
|
3708
|
+
if (!fs8.existsSync(logFile)) {
|
|
3709
|
+
return "No console logs found for this subagent.";
|
|
3710
|
+
}
|
|
3711
|
+
try {
|
|
3712
|
+
let content = fs8.readFileSync(logFile, "utf-8");
|
|
3713
|
+
if (content.endsWith("\n")) {
|
|
3714
|
+
content = content.slice(0, -1);
|
|
3715
|
+
}
|
|
3716
|
+
const lines = content.split("\n");
|
|
3717
|
+
const tail = lines.slice(-maxLines);
|
|
3718
|
+
return tail.join("\n");
|
|
3719
|
+
} catch (e) {
|
|
3720
|
+
return `Failed to read subagent logs: ${e.message}`;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
getActiveSubagents() {
|
|
3724
|
+
return Array.from(this.subagents.values()).filter((s) => s.status === "running");
|
|
3725
|
+
}
|
|
3726
|
+
getActiveSubagentsForParent(parentId) {
|
|
3727
|
+
return Array.from(this.subagents.values()).filter((s) => s.status === "running" && s.parentId === parentId);
|
|
3728
|
+
}
|
|
3729
|
+
killSubagent(id) {
|
|
3730
|
+
const state = this.subagents.get(id);
|
|
3731
|
+
if (state) {
|
|
3732
|
+
if (state.childProcess) {
|
|
3733
|
+
try {
|
|
3734
|
+
state.childProcess.kill("SIGTERM");
|
|
3735
|
+
} catch (e) {
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
this.terminateSubagent(id, false, true);
|
|
3739
|
+
if (state.parentId) {
|
|
3740
|
+
const cancelMsg = `[Subagent Notification] Subagent ${state.role} (${id}) has finished with status: CANCELLED. Summary: Terminated by parent agent.`;
|
|
3741
|
+
this.sendMessage(state.parentId, cancelMsg);
|
|
3742
|
+
}
|
|
3743
|
+
}
|
|
3744
|
+
}
|
|
3745
|
+
killAllSubagents() {
|
|
3746
|
+
for (const [id, state] of this.subagents.entries()) {
|
|
3747
|
+
if (state.status === "running") {
|
|
3748
|
+
this.killSubagent(id);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
async invokeSubagents(subagents, parentId, parentQueue) {
|
|
3753
|
+
const invoked = [];
|
|
3754
|
+
for (const sub of subagents) {
|
|
3755
|
+
const id = `subagent-${crypto2.randomUUID()}`;
|
|
3756
|
+
this.registerSubagent(id, sub.TypeName, sub.Role, parentId);
|
|
3757
|
+
const promise = (async () => {
|
|
3758
|
+
try {
|
|
3759
|
+
const projectRoot = process.cwd();
|
|
3760
|
+
const __filename2 = fileURLToPath2(import.meta.url);
|
|
3761
|
+
const __dirname2 = path9.dirname(__filename2);
|
|
3762
|
+
let packageRoot = __dirname2;
|
|
3763
|
+
while (true) {
|
|
3764
|
+
const pkgPath = path9.join(packageRoot, "package.json");
|
|
3765
|
+
if (fs8.existsSync(pkgPath)) {
|
|
3766
|
+
try {
|
|
3767
|
+
const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
3768
|
+
if (pkg && pkg.name === "shark-ai") {
|
|
3769
|
+
break;
|
|
3770
|
+
}
|
|
3771
|
+
} catch (e) {
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
const parent = path9.dirname(packageRoot);
|
|
3775
|
+
if (parent === packageRoot) {
|
|
3776
|
+
break;
|
|
3777
|
+
}
|
|
3778
|
+
packageRoot = parent;
|
|
3779
|
+
}
|
|
3780
|
+
const pathToSharkJs = path9.resolve(packageRoot, "dist", "bin", "shark.js");
|
|
3781
|
+
const customType = this.customTypes.get(sub.TypeName);
|
|
3782
|
+
let customContext = `Voc\xEA est\xE1 executando em modo SUBAGENTE.
|
|
3783
|
+
`;
|
|
3784
|
+
customContext += `- Seu ID \xE9: ${id}
|
|
3785
|
+
`;
|
|
3786
|
+
customContext += `- O ID do seu Agente Pai \xE9: ${parentId}
|
|
3787
|
+
`;
|
|
3788
|
+
customContext += `- Voc\xEA N\xC3O tem um terminal interativo com o usu\xE1rio humano. N\xE3o use 'talk_with_user' para interagir.
|
|
3789
|
+
`;
|
|
3790
|
+
customContext += `- Para reportar progresso intermedi\xE1rio ou tirar d\xFAvidas com seu pai, use a a\xE7\xE3o 'send_message' com Recipient='${parentId}'.
|
|
3791
|
+
`;
|
|
3792
|
+
customContext += `- Para concluir a tarefa e enviar o resultado detalhado em markdown, use obrigatoriamente a a\xE7\xE3o 'complete_task' com suas descobertas no campo 'content'.
|
|
3793
|
+
`;
|
|
3794
|
+
if (customType) {
|
|
3795
|
+
customContext += `Custom Prompt: ${customType.systemPrompt}
|
|
3796
|
+
`;
|
|
3797
|
+
}
|
|
3798
|
+
const instruction = customContext + "\n\n" + sub.Prompt;
|
|
3799
|
+
const args = ["dev", "-t", instruction, "--taskId", id, "--auto"];
|
|
3800
|
+
const child = fork(pathToSharkJs, args, {
|
|
3801
|
+
cwd: projectRoot,
|
|
3802
|
+
silent: true,
|
|
3803
|
+
env: {
|
|
3804
|
+
...process.env,
|
|
3805
|
+
SHARK_PARENT_ID: parentId,
|
|
3806
|
+
SHARK_SUBAGENT_ROLE: sub.Role
|
|
3807
|
+
}
|
|
3808
|
+
});
|
|
3809
|
+
const state2 = this.subagents.get(id);
|
|
3810
|
+
if (state2) {
|
|
3811
|
+
state2.childProcess = child;
|
|
3812
|
+
}
|
|
3813
|
+
const historyDir = path9.resolve(projectRoot, "_sharkrc", "history");
|
|
3814
|
+
fs8.mkdirSync(historyDir, { recursive: true });
|
|
3815
|
+
const consoleLogFile = path9.join(historyDir, `subagent-${id}-console.log`);
|
|
3816
|
+
const logStream = fs8.createWriteStream(consoleLogFile, { flags: "a" });
|
|
3817
|
+
if (child.stdout) {
|
|
3818
|
+
child.stdout.pipe(logStream);
|
|
3819
|
+
}
|
|
3820
|
+
if (child.stderr) {
|
|
3821
|
+
child.stderr.pipe(logStream);
|
|
3822
|
+
}
|
|
3823
|
+
const exitCode = await new Promise((resolve2) => {
|
|
3824
|
+
child.on("exit", (code) => {
|
|
3825
|
+
resolve2(code);
|
|
3826
|
+
});
|
|
3827
|
+
});
|
|
3828
|
+
logStream.end();
|
|
3829
|
+
const isCancelled = this.subagents.get(id)?.status === "cancelled";
|
|
3830
|
+
const success = exitCode === 0;
|
|
3831
|
+
this.terminateSubagent(id, success);
|
|
3832
|
+
if (isCancelled) {
|
|
3833
|
+
this.updateSubagentSummary(id, "Terminated by parent agent.");
|
|
3834
|
+
tui.log.message(`
|
|
3835
|
+
Subagent ${sub.Role} (${id}) cancelled.`);
|
|
3836
|
+
} else if (!success) {
|
|
3837
|
+
this.updateSubagentSummary(id, "Failed");
|
|
3838
|
+
const fallbackMsg = `[Subagent Notification] Subagent ${sub.Role} (${id}) has finished with status: FAILED. Summary: Subagent process exited with code ${exitCode}`;
|
|
3839
|
+
const mailboxDir = path9.resolve(projectRoot, ".shark", "mailbox", parentId);
|
|
3840
|
+
const hasMessages = fs8.existsSync(mailboxDir) && fs8.readdirSync(mailboxDir).length > 0;
|
|
3841
|
+
if (!hasMessages) {
|
|
3842
|
+
this.sendMessage(parentId, fallbackMsg);
|
|
3843
|
+
}
|
|
3844
|
+
tui.log.error(`Subagent ${sub.Role} (${id}) failed.`);
|
|
3845
|
+
} else {
|
|
3846
|
+
this.updateSubagentSummary(id, "Completed");
|
|
3847
|
+
const parentMsgs = this.peekMessages(parentId);
|
|
3848
|
+
const subagentMsg = parentMsgs.find((m) => m.includes(`(${id})`));
|
|
3849
|
+
if (subagentMsg) {
|
|
3850
|
+
tui.log.message(`
|
|
3851
|
+
${subagentMsg}`);
|
|
3852
|
+
} else {
|
|
3853
|
+
tui.log.success(`Subagent ${sub.Role} (${id}) completed successfully.`);
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
if (parentQueue) {
|
|
3857
|
+
let summaryContent = "Tarefa conclu\xEDda sem resumo.";
|
|
3858
|
+
let statusVal = success ? "completed" : "failed";
|
|
3859
|
+
if (isCancelled) {
|
|
3860
|
+
summaryContent = "Terminated by parent agent.";
|
|
3861
|
+
statusVal = "cancelled";
|
|
3862
|
+
} else if (success) {
|
|
3863
|
+
const parentMsgs = this.peekMessages(parentId);
|
|
3864
|
+
const subagentMsg = parentMsgs.find((m) => m.includes(`(${id})`));
|
|
3865
|
+
if (subagentMsg) {
|
|
3866
|
+
summaryContent = subagentMsg;
|
|
3867
|
+
}
|
|
3868
|
+
} else {
|
|
3869
|
+
summaryContent = `Subagente falhou com c\xF3digo de sa\xEDda ${exitCode}`;
|
|
3870
|
+
}
|
|
3871
|
+
parentQueue.push({
|
|
3872
|
+
type: "subagent_notification",
|
|
3873
|
+
content: summaryContent,
|
|
3874
|
+
timestamp: Date.now(),
|
|
3875
|
+
metadata: {
|
|
3876
|
+
subagentId: id,
|
|
3877
|
+
role: sub.Role,
|
|
3878
|
+
status: statusVal
|
|
3879
|
+
}
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
} catch (error) {
|
|
3883
|
+
console.error(`Subagent ${id} failed to spawn:`, error);
|
|
3884
|
+
this.terminateSubagent(id, false);
|
|
3885
|
+
const errorMsg = error instanceof Error ? error.message : "Unknown error";
|
|
3886
|
+
this.sendMessage(
|
|
3887
|
+
parentId,
|
|
3888
|
+
`[Subagent Notification] Subagent ${sub.Role} (${id}) has finished with status: FAILED. Summary: Subagent spawn failed: ${errorMsg}`
|
|
3889
|
+
);
|
|
3890
|
+
tui.log.error(`Subagent ${sub.Role} (${id}) failed to spawn.`);
|
|
3891
|
+
if (parentQueue) {
|
|
3892
|
+
parentQueue.push({
|
|
3893
|
+
type: "subagent_notification",
|
|
3894
|
+
content: `Subagente falhou ao iniciar: ${errorMsg}`,
|
|
3895
|
+
timestamp: Date.now(),
|
|
3896
|
+
metadata: {
|
|
3897
|
+
subagentId: id,
|
|
3898
|
+
role: sub.Role,
|
|
3899
|
+
status: "failed"
|
|
3900
|
+
}
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
})();
|
|
3905
|
+
const state = this.subagents.get(id);
|
|
3906
|
+
if (state) {
|
|
3907
|
+
state.promise = promise;
|
|
3908
|
+
}
|
|
3909
|
+
invoked.push({ id, TypeName: sub.TypeName, Role: sub.Role });
|
|
3910
|
+
}
|
|
3911
|
+
return invoked;
|
|
3912
|
+
}
|
|
3913
|
+
};
|
|
3914
|
+
var subagentManager = new SubagentManager();
|
|
3915
|
+
|
|
3916
|
+
// src/core/workflow/message-queue.ts
|
|
3917
|
+
var MessageQueue = class {
|
|
3918
|
+
queue = [];
|
|
3919
|
+
pendingResolvers = [];
|
|
3920
|
+
push(message) {
|
|
3921
|
+
if (this.pendingResolvers.length > 0) {
|
|
3922
|
+
const resolve2 = this.pendingResolvers.shift();
|
|
3923
|
+
resolve2(message);
|
|
3924
|
+
} else {
|
|
3925
|
+
this.queue.push(message);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
async next() {
|
|
3929
|
+
if (this.queue.length > 0) {
|
|
3930
|
+
return this.queue.shift();
|
|
3931
|
+
}
|
|
3932
|
+
return new Promise((resolve2) => {
|
|
3933
|
+
this.pendingResolvers.push(resolve2);
|
|
3934
|
+
});
|
|
3935
|
+
}
|
|
3936
|
+
isEmpty() {
|
|
3937
|
+
return this.queue.length === 0;
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
|
|
3941
|
+
// src/core/agents/developer-agent.ts
|
|
3942
|
+
async function promptUser(message, initialValue, placeholder, prefix = "") {
|
|
3943
|
+
let userReply = await tui.text({ message: `${prefix}${message}`, initialValue, placeholder });
|
|
3944
|
+
while (userReply === "/skills") {
|
|
3945
|
+
const availableSkills = await skillManager.listAvailableSkills();
|
|
3946
|
+
const options = availableSkills.map((name) => ({ value: name, label: name }));
|
|
3947
|
+
if (options.length === 0) {
|
|
3948
|
+
tui.log.warning("Nenhuma skill encontrada. Execute `shark super` para instalar as skills.");
|
|
3949
|
+
} else {
|
|
3950
|
+
const selectedSkill = await tui.select({
|
|
3951
|
+
message: "Selecione a Skill do Superpowers para ativar:",
|
|
3952
|
+
options
|
|
3953
|
+
});
|
|
3954
|
+
if (!tui.isCancel(selectedSkill)) {
|
|
3955
|
+
await skillManager.activateSkill(selectedSkill);
|
|
3956
|
+
tui.log.success(`\u2714 Skill '${selectedSkill}' ativada com sucesso!`);
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
userReply = await tui.text({
|
|
3960
|
+
message: `${prefix}${message}`,
|
|
3961
|
+
initialValue,
|
|
3962
|
+
placeholder: "digite a instru\xE7\xE3o da tarefa..."
|
|
3963
|
+
});
|
|
3964
|
+
}
|
|
3965
|
+
return userReply;
|
|
3966
|
+
}
|
|
3967
|
+
async function waitForInputOrNotification(queue, promptMessage = "Your answer:", subagentPrefix = "", timeoutMs) {
|
|
3968
|
+
let cancelled = false;
|
|
3969
|
+
let resolvePromptPromise = null;
|
|
3970
|
+
let timerId = null;
|
|
3971
|
+
const promptPromise = new Promise((resolve2) => {
|
|
3972
|
+
resolvePromptPromise = resolve2;
|
|
3973
|
+
});
|
|
3974
|
+
const runPrompt = async () => {
|
|
3975
|
+
try {
|
|
3976
|
+
const userReply = await promptUser(promptMessage, void 0, void 0, subagentPrefix);
|
|
3977
|
+
if (!cancelled && resolvePromptPromise) {
|
|
3978
|
+
resolvePromptPromise({
|
|
3979
|
+
type: "user",
|
|
3980
|
+
content: userReply,
|
|
3981
|
+
timestamp: Date.now()
|
|
3982
|
+
});
|
|
3983
|
+
}
|
|
3984
|
+
} catch (e) {
|
|
3985
|
+
}
|
|
3986
|
+
};
|
|
3987
|
+
runPrompt();
|
|
3988
|
+
const queuePromise = queue.next();
|
|
3989
|
+
const promises = [promptPromise, queuePromise];
|
|
3990
|
+
if (timeoutMs !== void 0 && timeoutMs !== null) {
|
|
3991
|
+
const timeoutPromise = new Promise((resolve2) => {
|
|
3992
|
+
timerId = setTimeout(() => {
|
|
3993
|
+
resolve2({
|
|
3994
|
+
type: "timeout",
|
|
3995
|
+
content: "Wait timeout expired.",
|
|
3996
|
+
timestamp: Date.now()
|
|
3997
|
+
});
|
|
3998
|
+
}, timeoutMs);
|
|
3999
|
+
});
|
|
4000
|
+
promises.push(timeoutPromise);
|
|
4001
|
+
}
|
|
4002
|
+
const winner = await Promise.race(promises);
|
|
4003
|
+
if (timerId) {
|
|
4004
|
+
clearTimeout(timerId);
|
|
4005
|
+
}
|
|
4006
|
+
if (winner.type === "subagent_notification" || winner.type === "timeout") {
|
|
4007
|
+
cancelled = true;
|
|
4008
|
+
process.stdin.emit("data", "\r");
|
|
4009
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
4010
|
+
if (process.stdout.isTTY) {
|
|
4011
|
+
process.stdout.write("\x1B[1A\x1B[2K\x1B[1A\x1B[2K");
|
|
131
4012
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
4013
|
+
}
|
|
4014
|
+
return winner;
|
|
4015
|
+
}
|
|
4016
|
+
function formatRoleForUI(role) {
|
|
4017
|
+
const limit = 20;
|
|
4018
|
+
if (role.length <= limit) return role;
|
|
4019
|
+
return role.substring(0, limit - 3) + "...";
|
|
4020
|
+
}
|
|
4021
|
+
async function interactiveDeveloperAgent(options = {}) {
|
|
4022
|
+
const isAuto = options.auto === true || process.argv.includes("--auto");
|
|
4023
|
+
const isSubagent = !!options.taskId && (options.taskId.startsWith("subagent-") || subagentManager.hasSubagent(options.taskId));
|
|
4024
|
+
const projectRoot = process.cwd();
|
|
4025
|
+
const messageQueue = new MessageQueue();
|
|
4026
|
+
let currentTask = options.taskInstruction;
|
|
4027
|
+
if (!currentTask) {
|
|
4028
|
+
if (isSubagent) {
|
|
4029
|
+
currentTask = "Subagent Task";
|
|
4030
|
+
} else {
|
|
4031
|
+
const userTask = await promptUser(
|
|
4032
|
+
"O que voc\xEA gostaria que o Shark Dev fizesse?",
|
|
4033
|
+
void 0,
|
|
4034
|
+
"ex: crie uma API REST simples ou digite /skills para ativar diretrizes"
|
|
4035
|
+
);
|
|
4036
|
+
if (tui.isCancel(userTask) || !userTask) {
|
|
4037
|
+
return { success: false, summary: "Task execution cancelled." };
|
|
4038
|
+
}
|
|
4039
|
+
currentTask = userTask;
|
|
136
4040
|
}
|
|
137
4041
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (value.trim().length < 2) return "Project name must be at least 2 characters";
|
|
4042
|
+
let subagentPrefix = "";
|
|
4043
|
+
if (options.taskId) {
|
|
4044
|
+
const subState = subagentManager.getSubagentState(options.taskId);
|
|
4045
|
+
if (subState) {
|
|
4046
|
+
subagentPrefix = `[Subagent: ${formatRoleForUI(subState.role)}] `;
|
|
144
4047
|
}
|
|
145
|
-
});
|
|
146
|
-
if (tui.isCancel(projectName)) {
|
|
147
|
-
tui.outro("Initialization cancelled.");
|
|
148
|
-
return;
|
|
149
4048
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
message:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
4049
|
+
const log = {
|
|
4050
|
+
info: (msg) => tui.log.info(`${subagentPrefix}${msg}`),
|
|
4051
|
+
warning: (msg) => tui.log.warning(`${subagentPrefix}${msg}`),
|
|
4052
|
+
error: (msg) => tui.log.error(`${subagentPrefix}${msg}`),
|
|
4053
|
+
success: (msg) => tui.log.success(`${subagentPrefix}${msg}`),
|
|
4054
|
+
message: (msg) => tui.log.message(`${subagentPrefix}${msg}`)
|
|
4055
|
+
};
|
|
4056
|
+
let contextContent = "";
|
|
4057
|
+
const defaultContextPath = path10.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
4058
|
+
const specificContextPath = options.context ? path10.resolve(projectRoot, options.context) : defaultContextPath;
|
|
4059
|
+
if (fs9.existsSync(specificContextPath)) {
|
|
4060
|
+
try {
|
|
4061
|
+
contextContent = fs9.readFileSync(specificContextPath, "utf-8");
|
|
4062
|
+
} catch (e) {
|
|
4063
|
+
log.warning(`Failed to read context file: ${e}`);
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
let basePrompt = ``;
|
|
4067
|
+
if (contextContent) {
|
|
4068
|
+
basePrompt += `
|
|
4069
|
+
|
|
4070
|
+
--- PROJECT CONTEXT ---
|
|
4071
|
+
${contextContent}
|
|
4072
|
+
-----------------------
|
|
4073
|
+
`;
|
|
4074
|
+
}
|
|
4075
|
+
if (options.history) {
|
|
4076
|
+
basePrompt += `
|
|
4077
|
+
|
|
4078
|
+
--- PREVIOUS EXECUTION SUMMARY ---
|
|
4079
|
+
${options.history}
|
|
4080
|
+
----------------------------------
|
|
4081
|
+
`;
|
|
161
4082
|
}
|
|
4083
|
+
basePrompt += `
|
|
4084
|
+
|
|
4085
|
+
\u{1F7E2} EXECUTION MODE
|
|
4086
|
+
|
|
4087
|
+
You are a highly skilled Developer Agent.
|
|
4088
|
+
\u{1F449} **CURRENT TASK**: "${currentTask}"
|
|
4089
|
+
|
|
4090
|
+
Your goal is to address the user's request:
|
|
4091
|
+
- If the request is a question, a request for explanation, or a discussion, answer the user using the 'talk_with_user' action. You can search the codebase or read files first to answer accurately. Once the explanation/discussion is complete, output a final response starting with "TASK_COMPLETED:" followed by a brief summary.
|
|
4092
|
+
- If the request is to implement changes, debug, or write code:
|
|
4093
|
+
1. Implement the necessary changes.
|
|
4094
|
+
2. Verify (compile/test).
|
|
4095
|
+
3. When you are confident the task is done, output a final response starting with "TASK_COMPLETED:" followed by a brief technical summary of what you did.
|
|
4096
|
+
`;
|
|
4097
|
+
let nextPrompt = basePrompt;
|
|
4098
|
+
let keepGoing = true;
|
|
4099
|
+
let finalSummary = "";
|
|
4100
|
+
const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
|
|
4101
|
+
const anchorManager = new AnchorStateManager();
|
|
162
4102
|
const spinner = tui.spinner();
|
|
163
|
-
|
|
4103
|
+
const handleCleanupSignal = (exitCode) => {
|
|
4104
|
+
const currentId = options.taskId || "parent";
|
|
4105
|
+
const active = subagentManager.getActiveSubagentsForParent(currentId);
|
|
4106
|
+
if (active.length > 0) {
|
|
4107
|
+
for (const sub of active) {
|
|
4108
|
+
subagentManager.killSubagent(sub.id);
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
process.exit(exitCode);
|
|
4112
|
+
};
|
|
4113
|
+
const sigIntHandler = () => handleCleanupSignal(130);
|
|
4114
|
+
const sigTermHandler = () => handleCleanupSignal(143);
|
|
4115
|
+
process.on("SIGINT", sigIntHandler);
|
|
4116
|
+
process.on("SIGTERM", sigTermHandler);
|
|
164
4117
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
currentStage: "business_analysis",
|
|
170
|
-
stageStatus: "pending",
|
|
171
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
172
|
-
artifacts: [],
|
|
173
|
-
metadata: {
|
|
174
|
-
initializedBy: "shark-cli",
|
|
175
|
-
version: "0.0.1"
|
|
4118
|
+
while (keepGoing) {
|
|
4119
|
+
if (options.taskId && subagentManager.hasSubagent(options.taskId) && !subagentManager.isSubagentActive(options.taskId)) {
|
|
4120
|
+
log.warning(`Subagent ${options.taskId} was terminated.`);
|
|
4121
|
+
return { success: false, summary: "Subagent terminated by manager." };
|
|
176
4122
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
4123
|
+
const recipientId = options.taskId || "parent";
|
|
4124
|
+
const mailboxMessages = subagentManager.retrieveMessages(recipientId);
|
|
4125
|
+
let currentTurnPrompt = nextPrompt;
|
|
4126
|
+
if (mailboxMessages.length > 0) {
|
|
4127
|
+
currentTurnPrompt += `
|
|
4128
|
+
|
|
4129
|
+
\u2709\uFE0F NEW MAILBOX MESSAGES:
|
|
4130
|
+
${mailboxMessages.map((m) => `- ${m}`).join("\n")}
|
|
4131
|
+
`;
|
|
4132
|
+
}
|
|
4133
|
+
const myId = options.taskId || "parent";
|
|
4134
|
+
const allSubagents = subagentManager.getActiveSubagentsForParent(myId);
|
|
4135
|
+
if (allSubagents.length > 0) {
|
|
4136
|
+
let panel = `
|
|
4137
|
+
|
|
4138
|
+
--- CURRENT ACTIVE SUBAGENTS ---
|
|
4139
|
+
`;
|
|
4140
|
+
panel += `You have ${allSubagents.length} active subagent(s) running in the background:
|
|
4141
|
+
`;
|
|
4142
|
+
for (const sub of allSubagents) {
|
|
4143
|
+
panel += `- ID: ${sub.id} | Role: ${sub.role} | Status: ${sub.status}
|
|
4144
|
+
`;
|
|
4145
|
+
}
|
|
4146
|
+
panel += `Use the 'wait' action if you have no other work and are waiting for these subagents to complete.
|
|
4147
|
+
`;
|
|
4148
|
+
panel += `--------------------------------
|
|
4149
|
+
`;
|
|
4150
|
+
currentTurnPrompt += panel;
|
|
4151
|
+
}
|
|
4152
|
+
const promptToSend = currentTurnPrompt + skillManager.getSystemInstructionExtension();
|
|
4153
|
+
try {
|
|
4154
|
+
const activeSubagents = subagentManager.getActiveSubagents();
|
|
4155
|
+
const activeCount = activeSubagents.length;
|
|
4156
|
+
const spinnerText = activeCount > 0 ? `\u{1F988} Shark Dev working... (Active subagents: ${activeCount})` : "\u{1F988} Shark Dev working...";
|
|
4157
|
+
spinner.start(spinnerText);
|
|
4158
|
+
const existingConversationId = await conversationManager.getConversationId(conversationKey);
|
|
4159
|
+
const provider = ProviderResolver.getProvider("developer_agent");
|
|
4160
|
+
const response = await provider.streamChat(promptToSend, {
|
|
4161
|
+
conversationId: existingConversationId,
|
|
4162
|
+
agentType: "developer_agent",
|
|
4163
|
+
onChunk: () => {
|
|
4164
|
+
}
|
|
4165
|
+
});
|
|
4166
|
+
if (response.conversation_id) {
|
|
4167
|
+
await conversationManager.saveConversationId(conversationKey, response.conversation_id);
|
|
4168
|
+
}
|
|
4169
|
+
spinner.stop("Response received");
|
|
4170
|
+
if (response.summary) {
|
|
4171
|
+
if (options.taskId) {
|
|
4172
|
+
subagentManager.updateSubagentSummary(options.taskId, response.summary);
|
|
4173
|
+
}
|
|
4174
|
+
log.info(`\u{1F4CC} Status: ${response.summary}`);
|
|
4175
|
+
}
|
|
4176
|
+
if (response.message && response.message.includes("TASK_COMPLETED:")) {
|
|
4177
|
+
finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
|
|
4178
|
+
log.success(`\u2714 Task Completed: ${finalSummary}`);
|
|
4179
|
+
if (options.taskId) {
|
|
4180
|
+
subagentManager.updateSubagentSummary(options.taskId, finalSummary);
|
|
4181
|
+
keepGoing = false;
|
|
4182
|
+
break;
|
|
4183
|
+
}
|
|
4184
|
+
if (!options.taskInstruction) {
|
|
4185
|
+
let nextMsg;
|
|
4186
|
+
if (!messageQueue.isEmpty()) {
|
|
4187
|
+
nextMsg = await messageQueue.next();
|
|
4188
|
+
} else {
|
|
4189
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix);
|
|
4190
|
+
}
|
|
4191
|
+
if (nextMsg.type === "user") {
|
|
4192
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4193
|
+
keepGoing = false;
|
|
4194
|
+
break;
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
nextPrompt = nextMsg.content;
|
|
4198
|
+
continue;
|
|
4199
|
+
} else {
|
|
4200
|
+
keepGoing = false;
|
|
4201
|
+
break;
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
if (response.message && response.message.includes("TASK_FAILED:")) {
|
|
4205
|
+
const failureReason = response.message.split("TASK_FAILED:")[1].trim();
|
|
4206
|
+
log.error(`\u274C Agent reported task failure: ${failureReason}`);
|
|
4207
|
+
if (options.taskId) {
|
|
4208
|
+
if (process.env.SHARK_PARENT_ID) {
|
|
4209
|
+
const parentId = process.env.SHARK_PARENT_ID;
|
|
4210
|
+
const role = process.env.SHARK_SUBAGENT_ROLE || "Subagent";
|
|
4211
|
+
subagentManager.sendMessage(
|
|
4212
|
+
parentId,
|
|
4213
|
+
`[Subagent Notification] Subagent ${role} (${options.taskId}) has finished with status: FAILED. Summary: ${failureReason}`
|
|
4214
|
+
);
|
|
4215
|
+
}
|
|
4216
|
+
return { success: false, summary: failureReason };
|
|
4217
|
+
}
|
|
4218
|
+
if (!options.taskInstruction) {
|
|
4219
|
+
let nextMsg;
|
|
4220
|
+
if (!messageQueue.isEmpty()) {
|
|
4221
|
+
nextMsg = await messageQueue.next();
|
|
4222
|
+
} else {
|
|
4223
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix);
|
|
4224
|
+
}
|
|
4225
|
+
if (nextMsg.type === "user") {
|
|
4226
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4227
|
+
return { success: false, summary: failureReason };
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
nextPrompt = nextMsg.content;
|
|
4231
|
+
continue;
|
|
4232
|
+
} else {
|
|
4233
|
+
return { success: false, summary: failureReason };
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
const action = response.action;
|
|
4237
|
+
if (!action) {
|
|
4238
|
+
if (isSubagent) {
|
|
4239
|
+
log.warning("No action returned by the subagent. Exiting loop.");
|
|
4240
|
+
keepGoing = false;
|
|
4241
|
+
break;
|
|
4242
|
+
}
|
|
4243
|
+
if (response.message) {
|
|
4244
|
+
log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
4245
|
+
console.log(response.message);
|
|
4246
|
+
let nextMsg;
|
|
4247
|
+
if (!messageQueue.isEmpty()) {
|
|
4248
|
+
nextMsg = await messageQueue.next();
|
|
4249
|
+
} else {
|
|
4250
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix);
|
|
4251
|
+
}
|
|
4252
|
+
if (nextMsg.type === "user") {
|
|
4253
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4254
|
+
keepGoing = false;
|
|
4255
|
+
break;
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
nextPrompt = nextMsg.content;
|
|
4259
|
+
} else {
|
|
4260
|
+
log.warning("No action or message returned by the agent.");
|
|
4261
|
+
let nextMsg;
|
|
4262
|
+
if (!messageQueue.isEmpty()) {
|
|
4263
|
+
nextMsg = await messageQueue.next();
|
|
4264
|
+
} else {
|
|
4265
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Agent returned empty response. Type a message to continue or press Ctrl+C to cancel:", subagentPrefix);
|
|
4266
|
+
}
|
|
4267
|
+
if (nextMsg.type === "user") {
|
|
4268
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4269
|
+
keepGoing = false;
|
|
4270
|
+
break;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
nextPrompt = nextMsg.content;
|
|
4274
|
+
}
|
|
4275
|
+
continue;
|
|
4276
|
+
}
|
|
4277
|
+
let resultMsg = "";
|
|
4278
|
+
if (action.type === "read_file") {
|
|
4279
|
+
const filePath = action.path || "";
|
|
4280
|
+
log.info(`\u{1F4D6} Reading (Anchored): ${colors.dim(filePath)}`);
|
|
4281
|
+
try {
|
|
4282
|
+
const content = anchorManager.getAnchoredContent(filePath);
|
|
4283
|
+
resultMsg = `[Action read_file(${filePath}) Success]:
|
|
4284
|
+
${content}`;
|
|
4285
|
+
} catch (e) {
|
|
4286
|
+
resultMsg = `[Action read_file(${filePath}) Failed]: ${e.message}`;
|
|
4287
|
+
}
|
|
4288
|
+
} else if (action.type === "modify_file") {
|
|
4289
|
+
const filePath = action.path || "";
|
|
4290
|
+
log.warning(`\u{1F4DD} Modify (Anchored): ${colors.bold(filePath)}`);
|
|
4291
|
+
let approved = isAuto;
|
|
4292
|
+
if (!approved) {
|
|
4293
|
+
approved = await tui.confirm({ message: `Approve modify_file changes to ${filePath}?` });
|
|
4294
|
+
}
|
|
4295
|
+
if (approved) {
|
|
4296
|
+
try {
|
|
4297
|
+
anchorManager.applyAnchoredEdit(filePath, action.start_anchor || "", action.end_anchor || "", action.content || "");
|
|
4298
|
+
resultMsg = `[Action modify_file(${filePath}) Success]`;
|
|
4299
|
+
} catch (e) {
|
|
4300
|
+
resultMsg = `[Action modify_file(${filePath}) Failed]: ${e.message}`;
|
|
4301
|
+
}
|
|
4302
|
+
} else {
|
|
4303
|
+
resultMsg = `[Action modify_file(${filePath}) User Denied]`;
|
|
4304
|
+
}
|
|
4305
|
+
} else if (action.type === "create_file") {
|
|
4306
|
+
const filePath = action.path || "";
|
|
4307
|
+
log.warning(`\u{1F4DD} Create file: ${colors.bold(filePath)}`);
|
|
4308
|
+
let approved = isAuto;
|
|
4309
|
+
if (!approved) {
|
|
4310
|
+
approved = await tui.confirm({ message: `Approve create_file changes to ${filePath}?` });
|
|
4311
|
+
}
|
|
4312
|
+
if (approved) {
|
|
4313
|
+
try {
|
|
4314
|
+
const resolvedPath = path10.resolve(projectRoot, filePath);
|
|
4315
|
+
const dir = path10.dirname(resolvedPath);
|
|
4316
|
+
if (!fs9.existsSync(dir)) {
|
|
4317
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
4318
|
+
}
|
|
4319
|
+
fs9.writeFileSync(resolvedPath, action.content || "", "utf-8");
|
|
4320
|
+
resultMsg = `[Action create_file(${filePath}) Success]`;
|
|
4321
|
+
} catch (e) {
|
|
4322
|
+
resultMsg = `[Action create_file(${filePath}) Failed]: ${e.message}`;
|
|
4323
|
+
}
|
|
4324
|
+
} else {
|
|
4325
|
+
resultMsg = `[Action create_file(${filePath}) User Denied]`;
|
|
4326
|
+
}
|
|
4327
|
+
} else if (action.type === "delete_file") {
|
|
4328
|
+
const filePath = action.path || "";
|
|
4329
|
+
log.warning(`\u{1F5D1}\uFE0F Delete file: ${colors.bold(filePath)}`);
|
|
4330
|
+
let approved = isAuto;
|
|
4331
|
+
if (!approved) {
|
|
4332
|
+
approved = await tui.confirm({ message: `Approve delete_file changes to ${filePath}?` });
|
|
4333
|
+
}
|
|
4334
|
+
if (approved) {
|
|
4335
|
+
try {
|
|
4336
|
+
const resolvedPath = path10.resolve(projectRoot, filePath);
|
|
4337
|
+
if (fs9.existsSync(resolvedPath)) {
|
|
4338
|
+
fs9.rmSync(resolvedPath, { force: true });
|
|
4339
|
+
}
|
|
4340
|
+
resultMsg = `[Action delete_file(${filePath}) Success]`;
|
|
4341
|
+
} catch (e) {
|
|
4342
|
+
resultMsg = `[Action delete_file(${filePath}) Failed]: ${e.message}`;
|
|
4343
|
+
}
|
|
4344
|
+
} else {
|
|
4345
|
+
resultMsg = `[Action delete_file(${filePath}) User Denied]`;
|
|
4346
|
+
}
|
|
4347
|
+
} else if (action.type === "run_command") {
|
|
4348
|
+
const cmd = action.command || "";
|
|
4349
|
+
log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
|
|
4350
|
+
let approved = isAuto;
|
|
4351
|
+
if (!approved) {
|
|
4352
|
+
approved = await tui.confirm({ message: `Execute run_command: ${cmd}?` });
|
|
4353
|
+
}
|
|
4354
|
+
if (approved) {
|
|
4355
|
+
try {
|
|
4356
|
+
const output = await handleRunCommand(cmd);
|
|
4357
|
+
resultMsg = `[Action run_command(${cmd}) Success]:
|
|
4358
|
+
${output}`;
|
|
4359
|
+
} catch (e) {
|
|
4360
|
+
resultMsg = `[Action run_command(${cmd}) Failed]: ${e.message}`;
|
|
4361
|
+
}
|
|
4362
|
+
} else {
|
|
4363
|
+
resultMsg = `[Action run_command(${cmd}) User Denied]`;
|
|
4364
|
+
}
|
|
4365
|
+
} else if (action.type === "list_files") {
|
|
4366
|
+
const dirPath = action.path || ".";
|
|
4367
|
+
log.info(`\u{1F4C2} Scanning: ${colors.dim(dirPath)}`);
|
|
4368
|
+
try {
|
|
4369
|
+
const result = handleListFiles(dirPath);
|
|
4370
|
+
resultMsg = `[Action list_files(${dirPath}) Success]:
|
|
4371
|
+
${result}`;
|
|
4372
|
+
} catch (e) {
|
|
4373
|
+
resultMsg = `[Action list_files(${dirPath}) Failed]: ${e.message}`;
|
|
4374
|
+
}
|
|
4375
|
+
} else if (action.type === "search_file") {
|
|
4376
|
+
const pattern = action.path || "";
|
|
4377
|
+
log.info(`\u{1F50D} Searching files: ${colors.dim(pattern)}`);
|
|
4378
|
+
try {
|
|
4379
|
+
const result = handleSearchFile(pattern);
|
|
4380
|
+
resultMsg = `[Action search_file(${pattern}) Success]:
|
|
4381
|
+
${result}`;
|
|
4382
|
+
} catch (e) {
|
|
4383
|
+
resultMsg = `[Action search_file(${pattern}) Failed]: ${e.message}`;
|
|
4384
|
+
}
|
|
4385
|
+
} else if (action.type === "search_code") {
|
|
4386
|
+
const glob = action.path || "src/**/*";
|
|
4387
|
+
const query = action.query || "";
|
|
4388
|
+
const isRegex = action.is_regex === true;
|
|
4389
|
+
log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
|
|
4390
|
+
try {
|
|
4391
|
+
const result = handleSearchCode(glob, query, isRegex);
|
|
4392
|
+
resultMsg = `[Action search_code("${query}" in "${glob}") Success]:
|
|
4393
|
+
${result}`;
|
|
4394
|
+
} catch (e) {
|
|
4395
|
+
resultMsg = `[Action search_code("${query}" in "${glob}") Failed]: ${e.message}`;
|
|
4396
|
+
}
|
|
4397
|
+
} else if (action.type === "use_mcp_tool") {
|
|
4398
|
+
resultMsg = `[Action use_mcp_tool Failed]: MCP tools are not configured/available in this agent.`;
|
|
4399
|
+
} else if (action.type === "activate_skill") {
|
|
4400
|
+
const name = action.skill_name || "";
|
|
4401
|
+
log.info(`\u26A1 Activating skill: ${colors.bold(name)}`);
|
|
4402
|
+
try {
|
|
4403
|
+
await skillManager.activateSkill(name);
|
|
4404
|
+
resultMsg = `[System]: Skill '${name}' activated successfully.`;
|
|
4405
|
+
} catch (e) {
|
|
4406
|
+
resultMsg = `[System]: Failed to activate skill '${name}': ${e.message}`;
|
|
4407
|
+
}
|
|
4408
|
+
} else if (action.type === "talk_with_user") {
|
|
4409
|
+
const isSystemError = action.content?.startsWith("[SYSTEM ERROR]");
|
|
4410
|
+
if (isSystemError) {
|
|
4411
|
+
log.error(`\u26A0\uFE0F Detectado erro na resposta do Agente (truncado ou inv\xE1lido).`);
|
|
4412
|
+
log.info(colors.dim(action.content || ""));
|
|
4413
|
+
if (isSubagent) {
|
|
4414
|
+
resultMsg = action.content || "";
|
|
4415
|
+
nextPrompt = resultMsg;
|
|
4416
|
+
continue;
|
|
4417
|
+
} else {
|
|
4418
|
+
let approved = isAuto;
|
|
4419
|
+
if (!approved) {
|
|
4420
|
+
approved = await tui.confirm({ message: `Enviar notifica\xE7\xE3o de erro para o agente tentar se recuperar automaticamente?` });
|
|
4421
|
+
}
|
|
4422
|
+
if (approved) {
|
|
4423
|
+
resultMsg = action.content || "";
|
|
4424
|
+
} else {
|
|
4425
|
+
let nextMsg;
|
|
4426
|
+
if (!messageQueue.isEmpty()) {
|
|
4427
|
+
nextMsg = await messageQueue.next();
|
|
4428
|
+
} else {
|
|
4429
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Seu prompt alternativo para o agente:", subagentPrefix);
|
|
4430
|
+
}
|
|
4431
|
+
if (nextMsg.type === "user") {
|
|
4432
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4433
|
+
keepGoing = false;
|
|
4434
|
+
break;
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
resultMsg = nextMsg.content;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
} else {
|
|
4441
|
+
const contentStr = action.content || "";
|
|
4442
|
+
const hasCompleted = contentStr.includes("TASK_COMPLETED:");
|
|
4443
|
+
if (isSubagent) {
|
|
4444
|
+
const summary = hasCompleted ? contentStr.split("TASK_COMPLETED:")[1].trim() : contentStr;
|
|
4445
|
+
subagentManager.updateSubagentSummary(options.taskId, summary);
|
|
4446
|
+
finalSummary = summary;
|
|
4447
|
+
keepGoing = false;
|
|
4448
|
+
break;
|
|
4449
|
+
}
|
|
4450
|
+
if (hasCompleted) {
|
|
4451
|
+
finalSummary = contentStr.split("TASK_COMPLETED:")[1].trim();
|
|
4452
|
+
log.success(`\u2714 Task Completed: ${finalSummary}`);
|
|
4453
|
+
if (!options.taskInstruction) {
|
|
4454
|
+
let nextMsg2;
|
|
4455
|
+
if (!messageQueue.isEmpty()) {
|
|
4456
|
+
nextMsg2 = await messageQueue.next();
|
|
4457
|
+
} else {
|
|
4458
|
+
nextMsg2 = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix);
|
|
4459
|
+
}
|
|
4460
|
+
if (nextMsg2.type === "user") {
|
|
4461
|
+
if (tui.isCancel(nextMsg2.content)) {
|
|
4462
|
+
keepGoing = false;
|
|
4463
|
+
break;
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
nextPrompt = nextMsg2.content;
|
|
4467
|
+
continue;
|
|
4468
|
+
} else {
|
|
4469
|
+
keepGoing = false;
|
|
4470
|
+
break;
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
log.info(colors.primary("\u{1F916} Shark Dev:"));
|
|
4474
|
+
console.log(contentStr);
|
|
4475
|
+
let nextMsg;
|
|
4476
|
+
if (!messageQueue.isEmpty()) {
|
|
4477
|
+
nextMsg = await messageQueue.next();
|
|
4478
|
+
} else {
|
|
4479
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix);
|
|
4480
|
+
}
|
|
4481
|
+
if (nextMsg.type === "user") {
|
|
4482
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4483
|
+
keepGoing = false;
|
|
4484
|
+
break;
|
|
4485
|
+
}
|
|
4486
|
+
resultMsg = `User Reply: ${nextMsg.content}`;
|
|
4487
|
+
} else {
|
|
4488
|
+
resultMsg = nextMsg.content;
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
} else if (action.type === "define_subagent") {
|
|
4492
|
+
const name = action.name || "";
|
|
4493
|
+
const desc = action.description || "";
|
|
4494
|
+
const sysPrompt = action.system_prompt || "";
|
|
4495
|
+
const opts = {
|
|
4496
|
+
enableWriteTools: action.enable_write_tools ?? void 0,
|
|
4497
|
+
enableSubagentTools: action.enable_subagent_tools ?? void 0,
|
|
4498
|
+
enableMcpTools: action.enable_mcp_tools ?? void 0
|
|
4499
|
+
};
|
|
4500
|
+
log.info(`\u{1F6E0}\uFE0F Defining subagent type: ${colors.bold(name)}`);
|
|
4501
|
+
subagentManager.defineSubagentType(name, desc, sysPrompt, opts);
|
|
4502
|
+
resultMsg = `[Action define_subagent Success]: Defined subagent type '${name}'`;
|
|
4503
|
+
} else if (action.type === "invoke_subagent") {
|
|
4504
|
+
const subagentsToInvoke = action.Subagents || [];
|
|
4505
|
+
log.info(`\u{1F680} Invoking ${subagentsToInvoke.length} subagent(s)`);
|
|
4506
|
+
const parentId = options.taskId || "parent";
|
|
4507
|
+
const invoked = await subagentManager.invokeSubagents(subagentsToInvoke, parentId, messageQueue);
|
|
4508
|
+
resultMsg = `[Action invoke_subagent Success]: Invoked subagents:
|
|
4509
|
+
${invoked.map((s) => `- ID: ${s.id}, Type: ${s.TypeName}, Role: ${s.Role}`).join("\n")}`;
|
|
4510
|
+
} else if (action.type === "send_message") {
|
|
4511
|
+
const recipient = action.Recipient || "";
|
|
4512
|
+
const message = action.Message || "";
|
|
4513
|
+
log.info(`\u2709\uFE0F Sending message to ${colors.bold(recipient)}`);
|
|
4514
|
+
subagentManager.sendMessage(recipient, message);
|
|
4515
|
+
resultMsg = `[Action send_message Success]: Message sent to '${recipient}'`;
|
|
4516
|
+
} else if (action.type === "manage_subagents") {
|
|
4517
|
+
const subAction = action.Action || "";
|
|
4518
|
+
const ids = action.ConversationIds || [];
|
|
4519
|
+
log.info(`\u2699\uFE0F Managing subagents. Action: ${colors.bold(subAction)}`);
|
|
4520
|
+
if (subAction === "list") {
|
|
4521
|
+
const active = subagentManager.getActiveSubagents();
|
|
4522
|
+
resultMsg = `[Action manage_subagents Success]: Active subagents:
|
|
4523
|
+
${active.map((s) => `- ID: ${s.id}, Type: ${s.type}, Role: ${s.role}`).join("\n")}`;
|
|
4524
|
+
} else if (subAction === "read_logs") {
|
|
4525
|
+
const id = ids[0];
|
|
4526
|
+
if (!id) {
|
|
4527
|
+
resultMsg = `[Action manage_subagents Failed]: No subagent ID provided in ConversationIds.`;
|
|
4528
|
+
} else {
|
|
4529
|
+
try {
|
|
4530
|
+
const logs = subagentManager.getSubagentLogs(id);
|
|
4531
|
+
resultMsg = `[Action manage_subagents Success]: Last log lines for subagent ${id}:
|
|
4532
|
+
\`\`\`
|
|
4533
|
+
${logs}
|
|
4534
|
+
\`\`\``;
|
|
4535
|
+
} catch (e) {
|
|
4536
|
+
resultMsg = `[Action manage_subagents Failed]: ${e.message}`;
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
} else if (subAction === "kill") {
|
|
4540
|
+
for (const id of ids) {
|
|
4541
|
+
subagentManager.killSubagent(id);
|
|
4542
|
+
}
|
|
4543
|
+
resultMsg = `[Action manage_subagents Success]: Terminated subagents: ${ids.join(", ")}`;
|
|
4544
|
+
} else if (subAction === "kill_all") {
|
|
4545
|
+
subagentManager.killAllSubagents();
|
|
4546
|
+
resultMsg = `[Action manage_subagents Success]: Terminated all active subagents`;
|
|
4547
|
+
} else {
|
|
4548
|
+
resultMsg = `[Action manage_subagents Failed]: Unknown action '${subAction}'`;
|
|
4549
|
+
}
|
|
4550
|
+
} else if (action.type === "complete_task") {
|
|
4551
|
+
const detailedContent = action.content || "";
|
|
4552
|
+
const taskSummary = action.summary || "Task completed successfully.";
|
|
4553
|
+
if (isSubagent) {
|
|
4554
|
+
subagentManager.updateSubagentSummary(options.taskId, taskSummary);
|
|
4555
|
+
if (process.env.SHARK_PARENT_ID) {
|
|
4556
|
+
subagentManager.sendMessage(
|
|
4557
|
+
process.env.SHARK_PARENT_ID,
|
|
4558
|
+
`[Subagent Notification] Subagent ${process.env.SHARK_SUBAGENT_ROLE || "Subagent"} (${options.taskId}) completed.
|
|
4559
|
+
Result Details:
|
|
4560
|
+
${detailedContent}`
|
|
4561
|
+
);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
finalSummary = taskSummary;
|
|
4565
|
+
keepGoing = false;
|
|
4566
|
+
break;
|
|
4567
|
+
} else if (action.type === "wait") {
|
|
4568
|
+
const durationSeconds = action.duration_seconds || 0;
|
|
4569
|
+
const durationMs = durationSeconds > 0 ? durationSeconds * 1e3 : void 0;
|
|
4570
|
+
log.info(`\u23F3 Waiting for updates (Timeout: ${durationSeconds || "infinite"}s)...`);
|
|
4571
|
+
let nextMsg;
|
|
4572
|
+
if (!messageQueue.isEmpty()) {
|
|
4573
|
+
nextMsg = await messageQueue.next();
|
|
4574
|
+
} else {
|
|
4575
|
+
nextMsg = await waitForInputOrNotification(messageQueue, "Your answer:", subagentPrefix, durationMs);
|
|
4576
|
+
}
|
|
4577
|
+
if (nextMsg.type === "timeout") {
|
|
4578
|
+
resultMsg = `[System]: Wait duration of ${durationSeconds} seconds expired. No notifications received.`;
|
|
4579
|
+
} else if (nextMsg.type === "user") {
|
|
4580
|
+
if (tui.isCancel(nextMsg.content)) {
|
|
4581
|
+
keepGoing = false;
|
|
4582
|
+
break;
|
|
4583
|
+
}
|
|
4584
|
+
resultMsg = `User Reply: ${nextMsg.content}`;
|
|
4585
|
+
} else {
|
|
4586
|
+
resultMsg = nextMsg.content;
|
|
4587
|
+
}
|
|
4588
|
+
} else {
|
|
4589
|
+
resultMsg = `[Unsupported action type: ${action.type}]`;
|
|
4590
|
+
}
|
|
4591
|
+
FileLogger.log("TOOL_EXECUTION", `Action: ${action.type}`, { action, result: resultMsg });
|
|
4592
|
+
nextPrompt = resultMsg;
|
|
4593
|
+
} catch (e) {
|
|
4594
|
+
log.error(e.message);
|
|
4595
|
+
if (options.taskId && process.env.SHARK_PARENT_ID) {
|
|
4596
|
+
const parentId = process.env.SHARK_PARENT_ID;
|
|
4597
|
+
const role = process.env.SHARK_SUBAGENT_ROLE || "Subagent";
|
|
4598
|
+
subagentManager.sendMessage(
|
|
4599
|
+
parentId,
|
|
4600
|
+
`[Subagent Notification] Subagent ${role} (${options.taskId}) has finished with status: FAILED. Summary: Error: ${e.message}`
|
|
4601
|
+
);
|
|
4602
|
+
}
|
|
4603
|
+
keepGoing = false;
|
|
4604
|
+
return { success: false, summary: `Error: ${e.message}` };
|
|
4605
|
+
}
|
|
4606
|
+
}
|
|
4607
|
+
const finalResult = { success: true, summary: finalSummary || "Task completed without summary." };
|
|
4608
|
+
if (options.taskId && process.env.SHARK_PARENT_ID) {
|
|
4609
|
+
const parentId = process.env.SHARK_PARENT_ID;
|
|
4610
|
+
const role = process.env.SHARK_SUBAGENT_ROLE || "Subagent";
|
|
4611
|
+
subagentManager.sendMessage(
|
|
4612
|
+
parentId,
|
|
4613
|
+
`[Subagent Notification] Subagent ${role} (${options.taskId}) has finished with status: COMPLETED. Summary: ${finalResult.summary}`
|
|
4614
|
+
);
|
|
4615
|
+
}
|
|
4616
|
+
log.success("\u2705 Task Scope Completed");
|
|
4617
|
+
return finalResult;
|
|
4618
|
+
} finally {
|
|
4619
|
+
process.off("SIGINT", sigIntHandler);
|
|
4620
|
+
process.off("SIGTERM", sigTermHandler);
|
|
4621
|
+
const currentId = options.taskId || "parent";
|
|
4622
|
+
const myActiveSubagents = subagentManager.getActiveSubagentsForParent(currentId);
|
|
4623
|
+
if (myActiveSubagents.length > 0) {
|
|
4624
|
+
log.info(`\u{1F9F9} Terminating ${myActiveSubagents.length} active child subagent(s) before exit...`);
|
|
4625
|
+
for (const sub of myActiveSubagents) {
|
|
4626
|
+
subagentManager.killSubagent(sub.id);
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
187
4629
|
}
|
|
188
|
-
}
|
|
189
|
-
var initCommand = new Command("init").description("Initialize a new Shark project").action(initAction);
|
|
4630
|
+
}
|
|
190
4631
|
|
|
191
4632
|
// src/commands/dev.ts
|
|
192
|
-
|
|
193
|
-
var devCommand = new Command2("dev").description("Starts the Shark Developer Agent (Shark Dev Orchestration V2)").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").option("-y, --yes", "Automatically approve all actions without prompting").option("--auto", "Automatically approve all actions without prompting").option("--export-schema", "Export the agent response JSON schema").action(async (options) => {
|
|
4633
|
+
var devCommand = new Command2("dev").description("Starts the Shark Developer Agent (Shark Dev Orchestration V2)").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").option("-y, --yes", "Automatically approve all actions without prompting").option("--auto", "Automatically approve all actions without prompting").option("--export-schema", "Export the agent response JSON schema").option("--taskId <id>", "ID of the current subagent task").action(async (options) => {
|
|
194
4634
|
if (options.exportSchema) {
|
|
195
4635
|
console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
|
|
196
4636
|
return;
|
|
@@ -199,7 +4639,8 @@ var devCommand = new Command2("dev").description("Starts the Shark Developer Age
|
|
|
199
4639
|
const result = await interactiveDeveloperAgent({
|
|
200
4640
|
taskInstruction: options.task,
|
|
201
4641
|
context: options.context,
|
|
202
|
-
auto: options.yes || options.auto
|
|
4642
|
+
auto: options.yes || options.auto,
|
|
4643
|
+
taskId: options.taskId
|
|
203
4644
|
});
|
|
204
4645
|
if (!result.success) {
|
|
205
4646
|
console.error("Task execution failed:", result.summary);
|
|
@@ -215,8 +4656,8 @@ var devCommand = new Command2("dev").description("Starts the Shark Developer Age
|
|
|
215
4656
|
import { Command as Command3 } from "commander";
|
|
216
4657
|
|
|
217
4658
|
// src/core/agents/legacy-developer-agent.ts
|
|
218
|
-
import
|
|
219
|
-
import
|
|
4659
|
+
import fs10 from "fs";
|
|
4660
|
+
import path11 from "path";
|
|
220
4661
|
var AGENT_TYPE = "developer_agent";
|
|
221
4662
|
async function validateTypeScript(filePath) {
|
|
222
4663
|
try {
|
|
@@ -245,11 +4686,11 @@ async function interactiveDeveloperAgent2(options = {}) {
|
|
|
245
4686
|
}
|
|
246
4687
|
const projectRoot = process.cwd();
|
|
247
4688
|
let contextContent = "";
|
|
248
|
-
const defaultContextPath =
|
|
249
|
-
const specificContextPath = options.context ?
|
|
250
|
-
if (
|
|
4689
|
+
const defaultContextPath = path11.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
4690
|
+
const specificContextPath = options.context ? path11.resolve(projectRoot, options.context) : defaultContextPath;
|
|
4691
|
+
if (fs10.existsSync(specificContextPath)) {
|
|
251
4692
|
try {
|
|
252
|
-
contextContent =
|
|
4693
|
+
contextContent = fs10.readFileSync(specificContextPath, "utf-8");
|
|
253
4694
|
} catch (e) {
|
|
254
4695
|
tui.log.warning(`Failed to read context file: ${e}`);
|
|
255
4696
|
}
|
|
@@ -391,9 +4832,9 @@ ${result}
|
|
|
391
4832
|
}
|
|
392
4833
|
if (approved) {
|
|
393
4834
|
if (action.type === "create_file") {
|
|
394
|
-
const dir =
|
|
395
|
-
if (!
|
|
396
|
-
|
|
4835
|
+
const dir = path11.dirname(path11.resolve(projectRoot, filePath));
|
|
4836
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
4837
|
+
fs10.writeFileSync(path11.resolve(projectRoot, filePath), action.content || "", "utf-8");
|
|
397
4838
|
executionResults += `[Action create_file]: Success
|
|
398
4839
|
|
|
399
4840
|
`;
|
|
@@ -407,7 +4848,7 @@ ${result}
|
|
|
407
4848
|
|
|
408
4849
|
`;
|
|
409
4850
|
}
|
|
410
|
-
const val = await validateTypeScript(
|
|
4851
|
+
const val = await validateTypeScript(path11.resolve(projectRoot, filePath));
|
|
411
4852
|
if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
|
|
412
4853
|
|
|
413
4854
|
`;
|
|
@@ -549,23 +4990,23 @@ async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE) {
|
|
|
549
4990
|
}
|
|
550
4991
|
|
|
551
4992
|
// src/core/workflow/task-manager.ts
|
|
552
|
-
import
|
|
553
|
-
import
|
|
4993
|
+
import fs11 from "fs";
|
|
4994
|
+
import path12 from "path";
|
|
554
4995
|
var TaskManager = class {
|
|
555
4996
|
projectRoot;
|
|
556
4997
|
specPath;
|
|
557
4998
|
constructor(projectRoot = process.cwd()) {
|
|
558
4999
|
this.projectRoot = projectRoot;
|
|
559
|
-
this.specPath =
|
|
5000
|
+
this.specPath = path12.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
|
|
560
5001
|
}
|
|
561
5002
|
/**
|
|
562
5003
|
* Reads the tech-spec.md file and analyzes its current state.
|
|
563
5004
|
*/
|
|
564
5005
|
analyzeSpecState() {
|
|
565
|
-
if (!
|
|
5006
|
+
if (!fs11.existsSync(this.specPath)) {
|
|
566
5007
|
return { status: "MISSING", allTasks: [] };
|
|
567
5008
|
}
|
|
568
|
-
const content =
|
|
5009
|
+
const content = fs11.readFileSync(this.specPath, "utf-8");
|
|
569
5010
|
const lines = content.split("\n");
|
|
570
5011
|
const tasks = [];
|
|
571
5012
|
let taskIndex = 1;
|
|
@@ -631,7 +5072,7 @@ var TaskManager = class {
|
|
|
631
5072
|
console.error(`Task ${taskId} not found.`);
|
|
632
5073
|
return false;
|
|
633
5074
|
}
|
|
634
|
-
const content =
|
|
5075
|
+
const content = fs11.readFileSync(this.specPath, "utf-8");
|
|
635
5076
|
const lines = content.split("\n");
|
|
636
5077
|
const targetLine = lines[task.line_number];
|
|
637
5078
|
if (!targetLine.includes(task.description)) {
|
|
@@ -640,7 +5081,7 @@ var TaskManager = class {
|
|
|
640
5081
|
}
|
|
641
5082
|
const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
|
|
642
5083
|
lines[task.line_number] = newLine;
|
|
643
|
-
|
|
5084
|
+
fs11.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
644
5085
|
return true;
|
|
645
5086
|
}
|
|
646
5087
|
/**
|
|
@@ -650,19 +5091,19 @@ var TaskManager = class {
|
|
|
650
5091
|
const state = this.analyzeSpecState();
|
|
651
5092
|
const task = state.allTasks.find((t) => t.id === taskId);
|
|
652
5093
|
if (!task) return false;
|
|
653
|
-
const content =
|
|
5094
|
+
const content = fs11.readFileSync(this.specPath, "utf-8");
|
|
654
5095
|
const lines = content.split("\n");
|
|
655
5096
|
let targetLine = lines[task.line_number];
|
|
656
5097
|
targetLine = targetLine.replace("- [ ]", "- [/]");
|
|
657
5098
|
lines[task.line_number] = targetLine;
|
|
658
|
-
|
|
5099
|
+
fs11.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
|
|
659
5100
|
return true;
|
|
660
5101
|
}
|
|
661
5102
|
/**
|
|
662
5103
|
* Completely updates the spec file content (used by Spec Agent).
|
|
663
5104
|
*/
|
|
664
5105
|
updateSpecContent(newContent) {
|
|
665
|
-
|
|
5106
|
+
fs11.writeFileSync(this.specPath, newContent, "utf-8");
|
|
666
5107
|
}
|
|
667
5108
|
getSpecPath() {
|
|
668
5109
|
return this.specPath;
|
|
@@ -670,17 +5111,17 @@ var TaskManager = class {
|
|
|
670
5111
|
};
|
|
671
5112
|
|
|
672
5113
|
// src/core/agents/specification-agent.ts
|
|
673
|
-
import
|
|
674
|
-
import
|
|
5114
|
+
import fs12 from "fs";
|
|
5115
|
+
import path13 from "path";
|
|
675
5116
|
var AGENT_TYPE2 = "specification_agent";
|
|
676
5117
|
async function interactiveSpecificationAgent(options = {}) {
|
|
677
5118
|
FileLogger.init();
|
|
678
5119
|
tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
|
|
679
5120
|
const projectRoot = process.cwd();
|
|
680
|
-
const sharkRcDir =
|
|
681
|
-
if (!
|
|
682
|
-
const outputFile =
|
|
683
|
-
if (!
|
|
5121
|
+
const sharkRcDir = path13.resolve(projectRoot, "_sharkrc");
|
|
5122
|
+
if (!fs12.existsSync(sharkRcDir)) fs12.mkdirSync(sharkRcDir, { recursive: true });
|
|
5123
|
+
const outputFile = path13.resolve(sharkRcDir, "tech-spec.md");
|
|
5124
|
+
if (!fs12.existsSync(outputFile)) {
|
|
684
5125
|
let initialContent = `# Technical Specification: {{PROJECT_NAME}}
|
|
685
5126
|
|
|
686
5127
|
## 1. Technology Stack
|
|
@@ -705,28 +5146,28 @@ async function interactiveSpecificationAgent(options = {}) {
|
|
|
705
5146
|
## 5. Implementation Steps
|
|
706
5147
|
[TO BE FILLED - MUST BE CHECKBOXES]
|
|
707
5148
|
`;
|
|
708
|
-
const projectName =
|
|
5149
|
+
const projectName = path13.basename(projectRoot);
|
|
709
5150
|
initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
|
|
710
5151
|
const BOM = "\uFEFF";
|
|
711
|
-
|
|
5152
|
+
fs12.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
|
|
712
5153
|
tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
713
5154
|
} else {
|
|
714
5155
|
tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
|
|
715
5156
|
}
|
|
716
5157
|
let contextContent = "";
|
|
717
|
-
const contextPath =
|
|
718
|
-
if (
|
|
719
|
-
contextContent =
|
|
5158
|
+
const contextPath = path13.resolve(projectRoot, "_sharkrc", "project-context.md");
|
|
5159
|
+
if (fs12.existsSync(contextPath)) {
|
|
5160
|
+
contextContent = fs12.readFileSync(contextPath, "utf-8");
|
|
720
5161
|
tui.log.info(`\u{1F4D8} Context loaded.`);
|
|
721
5162
|
}
|
|
722
5163
|
let briefingContent = "";
|
|
723
|
-
if (options.briefingPath &&
|
|
724
|
-
briefingContent =
|
|
5164
|
+
if (options.briefingPath && fs12.existsSync(options.briefingPath)) {
|
|
5165
|
+
briefingContent = fs12.readFileSync(options.briefingPath, "utf-8");
|
|
725
5166
|
tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
|
|
726
5167
|
} else {
|
|
727
|
-
const standardBriefing =
|
|
728
|
-
if (
|
|
729
|
-
briefingContent =
|
|
5168
|
+
const standardBriefing = path13.resolve(projectRoot, "_sharkrc", "briefing.md");
|
|
5169
|
+
if (fs12.existsSync(standardBriefing)) {
|
|
5170
|
+
briefingContent = fs12.readFileSync(standardBriefing, "utf-8");
|
|
730
5171
|
tui.log.info(`\u{1F4C4} Briefing loaded.`);
|
|
731
5172
|
}
|
|
732
5173
|
}
|
|
@@ -787,8 +5228,8 @@ async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
|
|
|
787
5228
|
const spinner = tui.spinner();
|
|
788
5229
|
spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
|
|
789
5230
|
let pendingSections = [];
|
|
790
|
-
if (
|
|
791
|
-
const content =
|
|
5231
|
+
if (fs12.existsSync(targetPath)) {
|
|
5232
|
+
const content = fs12.readFileSync(targetPath, "utf-8");
|
|
792
5233
|
if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
|
|
793
5234
|
if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
|
|
794
5235
|
}
|
|
@@ -863,11 +5304,11 @@ ${result}
|
|
|
863
5304
|
|
|
864
5305
|
`;
|
|
865
5306
|
} else if (["create_file", "modify_file"].includes(action.type)) {
|
|
866
|
-
let actionPath =
|
|
867
|
-
const resolvedTargetPath =
|
|
5307
|
+
let actionPath = path13.resolve(action.path || "");
|
|
5308
|
+
const resolvedTargetPath = path13.resolve(targetPath);
|
|
868
5309
|
let isTarget = actionPath === resolvedTargetPath;
|
|
869
|
-
if (!isTarget &&
|
|
870
|
-
tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${
|
|
5310
|
+
if (!isTarget && path13.basename(actionPath) === "tech-spec.md") {
|
|
5311
|
+
tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path13.relative(process.cwd(), targetPath)}`);
|
|
871
5312
|
action.path = targetPath;
|
|
872
5313
|
actionPath = resolvedTargetPath;
|
|
873
5314
|
isTarget = true;
|
|
@@ -883,7 +5324,7 @@ ${result}
|
|
|
883
5324
|
try {
|
|
884
5325
|
if (action.type === "create_file") {
|
|
885
5326
|
const BOM = "\uFEFF";
|
|
886
|
-
|
|
5327
|
+
fs12.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
|
|
887
5328
|
tui.log.success(`\u2705 Created: ${action.path}`);
|
|
888
5329
|
executionResults += `[Action create_file]: Success.
|
|
889
5330
|
`;
|
|
@@ -949,7 +5390,7 @@ Voc\xEA completou a FASE 2 com sucesso.
|
|
|
949
5390
|
Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
|
|
950
5391
|
continue;
|
|
951
5392
|
}
|
|
952
|
-
const content =
|
|
5393
|
+
const content = fs12.existsSync(targetPath) ? fs12.readFileSync(targetPath, "utf-8") : "";
|
|
953
5394
|
if (content.includes("[TO BE")) {
|
|
954
5395
|
const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
|
|
955
5396
|
let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
|
|
@@ -981,7 +5422,7 @@ Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFA
|
|
|
981
5422
|
|
|
982
5423
|
User Reply: ${userReply}`;
|
|
983
5424
|
} else if (executionResults) {
|
|
984
|
-
const content =
|
|
5425
|
+
const content = fs12.existsSync(targetPath) ? fs12.readFileSync(targetPath, "utf-8") : "";
|
|
985
5426
|
let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
|
|
986
5427
|
if (specUpdated) {
|
|
987
5428
|
if (content.includes("[TO BE")) {
|
|
@@ -1169,42 +5610,49 @@ var exportSchemaCommand = new Command4("export-schema").description("Outputs the
|
|
|
1169
5610
|
console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
|
|
1170
5611
|
});
|
|
1171
5612
|
|
|
1172
|
-
// src/commands/
|
|
5613
|
+
// src/commands/export-prompt.ts
|
|
1173
5614
|
import { Command as Command5 } from "commander";
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
5615
|
+
var exportPromptCommand = new Command5("export-prompt").description("Outputs the unified agent system prompt").action(() => {
|
|
5616
|
+
console.log(UNIFIED_SYSTEM_PROMPT);
|
|
5617
|
+
});
|
|
5618
|
+
|
|
5619
|
+
// src/commands/super.ts
|
|
5620
|
+
import { Command as Command6 } from "commander";
|
|
5621
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5622
|
+
import path14 from "path";
|
|
5623
|
+
import os3 from "os";
|
|
5624
|
+
import fs13 from "fs/promises";
|
|
1178
5625
|
var superCommandAction = async (options = {}) => {
|
|
1179
|
-
const __filename2 =
|
|
1180
|
-
const __dirname2 =
|
|
1181
|
-
const packageRoot =
|
|
1182
|
-
const internalSkillsPath =
|
|
1183
|
-
const targetPath = options.local ?
|
|
5626
|
+
const __filename2 = fileURLToPath3(import.meta.url);
|
|
5627
|
+
const __dirname2 = path14.dirname(__filename2);
|
|
5628
|
+
const packageRoot = path14.resolve(__dirname2, "../../");
|
|
5629
|
+
const internalSkillsPath = path14.join(packageRoot, "skills");
|
|
5630
|
+
const targetPath = options.local ? path14.join(process.cwd(), ".agents", "skills") : path14.join(os3.homedir(), ".shark", "skills");
|
|
1184
5631
|
try {
|
|
1185
5632
|
try {
|
|
1186
|
-
await
|
|
5633
|
+
await fs13.rm(targetPath, { recursive: true, force: true });
|
|
1187
5634
|
} catch {
|
|
1188
5635
|
}
|
|
1189
|
-
await
|
|
1190
|
-
await
|
|
5636
|
+
await fs13.mkdir(targetPath, { recursive: true });
|
|
5637
|
+
await fs13.cp(internalSkillsPath, targetPath, { recursive: true, force: true });
|
|
1191
5638
|
console.log(`\u{1F680} Superpowers skills installed successfully to ${targetPath}`);
|
|
1192
5639
|
} catch (error) {
|
|
1193
5640
|
console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
|
|
1194
5641
|
process.exit(1);
|
|
1195
5642
|
}
|
|
1196
5643
|
};
|
|
1197
|
-
var superCommand = new
|
|
5644
|
+
var superCommand = new Command6("super").description("Install Superpowers skills globally or locally").option("-l, --local", "Install skills locally in the current project under .agents/skills").action(superCommandAction);
|
|
1198
5645
|
|
|
1199
5646
|
// src/bin/shark.ts
|
|
1200
5647
|
crashHandler.init();
|
|
1201
|
-
var program = new
|
|
5648
|
+
var program = new Command7();
|
|
1202
5649
|
program.name("shark").description("Shark CLI: AI-Native Collaborative Development Tool").version("0.0.1");
|
|
1203
5650
|
program.addCommand(loginCommand);
|
|
1204
5651
|
program.addCommand(initCommand);
|
|
1205
5652
|
program.addCommand(devCommand);
|
|
1206
5653
|
program.addCommand(legacyCommand);
|
|
1207
5654
|
program.addCommand(exportSchemaCommand);
|
|
5655
|
+
program.addCommand(exportPromptCommand);
|
|
1208
5656
|
program.addCommand(superCommand);
|
|
1209
5657
|
program.command("config").description("Manage global configuration").action(configCommand.action);
|
|
1210
5658
|
process.on("unhandledRejection", (err) => {
|