shark-ai 0.4.26 → 0.4.27

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 CHANGED
@@ -8,7 +8,10 @@ import {
8
8
  loginCommand,
9
9
  tokenStorage,
10
10
  tui
11
- } from "../chunk-NKE5GNDJ.js";
11
+ } from "../chunk-LQ2PEYRD.js";
12
+ import {
13
+ MemboxManager
14
+ } from "../chunk-WJUQF54U.js";
12
15
 
13
16
  // src/core/error/crash-handler.ts
14
17
  import fs from "fs";
@@ -209,7 +212,7 @@ var initAction = async () => {
209
212
  }
210
213
  if (action === "resume") {
211
214
  tui.log.success(`Resuming work on ${colors.primary(existingState.projectName)}...`);
212
- tui.outro(`To continue, run: "shark agent" (Context loaded)`);
215
+ tui.outro(`To continue, run: "shark dev" (Context loaded)`);
213
216
  return;
214
217
  }
215
218
  }
@@ -257,7 +260,7 @@ var initAction = async () => {
257
260
  spinner.stop("Project workflow created!");
258
261
  tui.log.success(`Project ${colors.primary(projectName)} initialized successfully.`);
259
262
  tui.log.message(`Your Project ID: ${colors.dim(newState.projectId)}`);
260
- tui.outro('Ready to start! Run "shark agent" to begin analyzing requirements.');
263
+ tui.outro('Ready to start! Run "shark dev" to begin developing features.');
261
264
  } catch (error) {
262
265
  spinner.stop("Initialization failed.", 1);
263
266
  tui.log.error(error.message);
@@ -271,7 +274,21 @@ import { Command as Command2 } from "commander";
271
274
 
272
275
  // src/core/agents/agent-response-parser.ts
273
276
  import { z as z2 } from "zod";
274
- var AgentActionSchema = z2.object({
277
+ import { jsonrepair } from "jsonrepair";
278
+ var AgentActionSchema = z2.preprocess((val) => {
279
+ if (val && typeof val === "object") {
280
+ if (typeof val.type === "string") {
281
+ val.type = val.type.trim();
282
+ }
283
+ if (typeof val.Action === "string") {
284
+ val.Action = val.Action.trim();
285
+ }
286
+ if (val.Action !== void 0 && (val.type !== "manage_subagents" || val.Action === "")) {
287
+ val.Action = null;
288
+ }
289
+ }
290
+ return val;
291
+ }, z2.object({
275
292
  type: z2.enum([
276
293
  "create_file",
277
294
  "modify_file",
@@ -370,7 +387,7 @@ var AgentActionSchema = z2.object({
370
387
  enable_write_tools: z2.boolean().nullable().optional(),
371
388
  enable_subagent_tools: z2.boolean().nullable().optional(),
372
389
  enable_mcp_tools: z2.boolean().nullable().optional()
373
- });
390
+ }));
374
391
  var AgentCommandSchema = z2.object({
375
392
  command: z2.string(),
376
393
  description: z2.string(),
@@ -538,6 +555,17 @@ function parseAgentResponse(rawResponse) {
538
555
  } else if (normalizedAction && (!normalizedActions || normalizedActions.length === 0)) {
539
556
  normalizedActions = [normalizedAction];
540
557
  }
558
+ if (normalizedAction && typeof normalizedAction.content === "object" && normalizedAction.content !== null) {
559
+ normalizedAction.content = JSON.stringify(normalizedAction.content);
560
+ }
561
+ if (Array.isArray(normalizedActions)) {
562
+ normalizedActions = normalizedActions.map((act) => {
563
+ if (act && act.content && typeof act.content === "object" && act.content !== null) {
564
+ return { ...act, content: JSON.stringify(act.content) };
565
+ }
566
+ return act;
567
+ });
568
+ }
541
569
  if (!normalizedAction && !normalizedActions) {
542
570
  FileLogger.log("PARSER", "No Action/Actions Found - Constructing Default");
543
571
  const content = parsedObj.message || (typeof parsedObj === "object" ? JSON.stringify(parsedObj) : String(parsedObj));
@@ -589,8 +617,25 @@ function extractFirstJson(str) {
589
617
  try {
590
618
  return JSON.parse(str);
591
619
  } catch (e) {
620
+ const isValidStructure = (val) => {
621
+ return val && typeof val === "object" && !Array.isArray(val) && ("action" in val || "actions" in val || "summary" in val || "type" in val);
622
+ };
623
+ try {
624
+ const repaired = JSON.parse(jsonrepair(str));
625
+ if (isValidStructure(repaired)) {
626
+ return repaired;
627
+ }
628
+ } catch {
629
+ }
592
630
  const firstOpen = str.indexOf("{");
593
631
  if (firstOpen === -1) throw e;
632
+ try {
633
+ const repaired = JSON.parse(jsonrepair(str.substring(firstOpen)));
634
+ if (isValidStructure(repaired)) {
635
+ return repaired;
636
+ }
637
+ } catch {
638
+ }
594
639
  let balance = 0;
595
640
  let inString = false;
596
641
  let escape = false;
@@ -617,7 +662,11 @@ function extractFirstJson(str) {
617
662
  try {
618
663
  return JSON.parse(potentialJson);
619
664
  } catch (innerE) {
620
- throw e;
665
+ try {
666
+ return JSON.parse(jsonrepair(potentialJson));
667
+ } catch {
668
+ throw e;
669
+ }
621
670
  }
622
671
  }
623
672
  }
@@ -921,76 +970,176 @@ var AGENT_RESPONSE_JSON_SCHEMA = {
921
970
  },
922
971
  "required": ["type"]
923
972
  },
924
- "summary": { "type": "string" }
973
+ "summary": {
974
+ "type": "string",
975
+ "description": "Resumo de uma \xFAnica frase muito curta e sucinta do que voc\xEA realizou nesta rodada. Evite explica\xE7\xF5es longas."
976
+ }
925
977
  },
926
978
  "required": ["action"]
927
979
  };
928
980
 
981
+ // src/core/workflow/history-manager.ts
982
+ import fs3 from "fs";
983
+ import path3 from "path";
984
+ var HistoryManager = class {
985
+ static getHistoryDir() {
986
+ const dir = path3.resolve(process.cwd(), "_sharkrc", "history");
987
+ if (!fs3.existsSync(dir)) {
988
+ fs3.mkdirSync(dir, { recursive: true });
989
+ }
990
+ return dir;
991
+ }
992
+ static getFilePath(conversationId) {
993
+ return path3.resolve(this.getHistoryDir(), `${conversationId}.json`);
994
+ }
995
+ static async getHistory(conversationId) {
996
+ const filePath = this.getFilePath(conversationId);
997
+ if (!fs3.existsSync(filePath)) {
998
+ return [];
999
+ }
1000
+ try {
1001
+ const raw = fs3.readFileSync(filePath, "utf-8");
1002
+ const parsed = JSON.parse(raw);
1003
+ return Array.isArray(parsed) ? parsed : [];
1004
+ } catch {
1005
+ return [];
1006
+ }
1007
+ }
1008
+ static async saveHistory(conversationId, messages) {
1009
+ const filePath = this.getFilePath(conversationId);
1010
+ fs3.writeFileSync(filePath, JSON.stringify(messages, null, 2), "utf-8");
1011
+ }
1012
+ static async deleteHistory(conversationId) {
1013
+ const filePath = this.getFilePath(conversationId);
1014
+ if (fs3.existsSync(filePath)) {
1015
+ fs3.unlinkSync(filePath);
1016
+ }
1017
+ }
1018
+ static async appendMessage(conversationId, message) {
1019
+ const history = await this.getHistory(conversationId);
1020
+ history.push(message);
1021
+ await this.saveHistory(conversationId, history);
1022
+ }
1023
+ static getRawHistoryPath(conversationId) {
1024
+ return this.getFilePath(conversationId);
1025
+ }
1026
+ static async getRawHistory(conversationId) {
1027
+ return this.getHistory(conversationId);
1028
+ }
1029
+ static async saveRawHistory(conversationId, history) {
1030
+ await this.saveHistory(conversationId, history);
1031
+ }
1032
+ };
1033
+
1034
+ // src/core/api/stackspot-provider.ts
1035
+ import crypto from "crypto";
1036
+
1037
+ // src/core/workflow/skill-manager.ts
1038
+ import fs4 from "fs/promises";
1039
+ import path4 from "path";
1040
+ import os2 from "os";
1041
+ var SkillManager = class {
1042
+ activeSkills = /* @__PURE__ */ new Set();
1043
+ skillPrompts = /* @__PURE__ */ new Map();
1044
+ async loadSkillFromFile(filePath) {
1045
+ const content = await fs4.readFile(filePath, "utf-8");
1046
+ const cleanContent = content.replace(/^---[\s\S]*?---\s*/, "");
1047
+ return cleanContent;
1048
+ }
1049
+ async activateSkill(skillName) {
1050
+ if (this.activeSkills.has(skillName)) {
1051
+ return `Skill ${skillName} is already active.`;
1052
+ }
1053
+ this.reset();
1054
+ const globalPath = path4.join(os2.homedir(), ".shark", "skills", skillName, "SKILL.md");
1055
+ const localPath = path4.join(process.cwd(), ".agents", "skills", skillName, "SKILL.md");
1056
+ let skillPath = "";
1057
+ try {
1058
+ await fs4.access(localPath);
1059
+ skillPath = localPath;
1060
+ } catch {
1061
+ try {
1062
+ await fs4.access(globalPath);
1063
+ skillPath = globalPath;
1064
+ } catch {
1065
+ throw new Error(`Skill '${skillName}' not found globally or locally.`);
1066
+ }
1067
+ }
1068
+ const prompt = await this.loadSkillFromFile(skillPath);
1069
+ this.activeSkills.add(skillName);
1070
+ this.skillPrompts.set(skillName, prompt);
1071
+ return prompt;
1072
+ }
1073
+ getSystemInstructionExtension() {
1074
+ if (this.activeSkills.size === 0) return "";
1075
+ let extension = "\n\n<EXTREMELY_IMPORTANT>\n";
1076
+ for (const [name, prompt] of this.skillPrompts.entries()) {
1077
+ extension += `
1078
+ --- ACTIVE SKILL: ${name} ---
1079
+ ${prompt}
1080
+ `;
1081
+ }
1082
+ extension += "\n</EXTREMELY_IMPORTANT>\n";
1083
+ return extension;
1084
+ }
1085
+ async listAvailableSkills() {
1086
+ const globalSkillsDir = path4.join(os2.homedir(), ".shark", "skills");
1087
+ const localSkillsDir = path4.join(process.cwd(), ".agents", "skills");
1088
+ const skillNames = /* @__PURE__ */ new Set();
1089
+ const readDir = async (dir) => {
1090
+ try {
1091
+ const entries = await fs4.readdir(dir, { withFileTypes: true });
1092
+ for (const entry of entries) {
1093
+ if (entry.isDirectory()) {
1094
+ const skillMdPath = path4.join(dir, entry.name, "SKILL.md");
1095
+ try {
1096
+ await fs4.access(skillMdPath);
1097
+ skillNames.add(entry.name);
1098
+ } catch {
1099
+ }
1100
+ }
1101
+ }
1102
+ } catch {
1103
+ }
1104
+ };
1105
+ await readDir(globalSkillsDir);
1106
+ await readDir(localSkillsDir);
1107
+ return Array.from(skillNames).sort();
1108
+ }
1109
+ reset() {
1110
+ this.activeSkills.clear();
1111
+ this.skillPrompts.clear();
1112
+ }
1113
+ };
1114
+ var skillManager = new SkillManager();
1115
+
929
1116
  // src/core/api/stackspot-provider.ts
930
1117
  var StackSpotProvider = class {
931
1118
  constructor(agentType) {
932
1119
  this.agentType = agentType;
933
1120
  const config = ConfigManager.getInstance().getConfig();
934
1121
  this.agentId = config.stackspot?.agentId;
1122
+ this.useServerConversation = config.stackspot?.useServerConversation !== false;
935
1123
  }
936
1124
  agentId;
1125
+ useServerConversation = true;
937
1126
  getAgentId() {
938
1127
  const config = ConfigManager.getInstance().getConfig();
939
- const configKeyMapping = {
940
- "developer_agent": "dev",
941
- "business_analyst": "ba",
942
- "specification_agent": "spec",
943
- "qa_agent": "qa",
944
- "scan_agent": "scan",
945
- "code_review": "codeReview"
946
- };
947
- const mappedKey = configKeyMapping[this.agentType];
948
- if (mappedKey && config.agents?.[mappedKey]) {
949
- return config.agents[mappedKey];
1128
+ if (config.agents?.dev) {
1129
+ return config.agents.dev;
950
1130
  }
951
- const envIdMapping = {
952
- "business_analyst": process.env.STACKSPOT_BA_AGENT_ID,
953
- "developer_agent": process.env.STACKSPOT_DEV_AGENT_ID,
954
- "qa_agent": process.env.STACKSPOT_QA_AGENT_ID,
955
- "specification_agent": process.env.STACKSPOT_SPEC_AGENT_ID,
956
- "scan_agent": process.env.STACKSPOT_SCAN_AGENT_ID,
957
- "code_review": process.env.STACKSPOT_CODE_REVIEW_AGENT_ID
958
- };
959
- const envResolved = envIdMapping[this.agentType];
1131
+ const envResolved = process.env.STACKSPOT_DEV_AGENT_ID;
960
1132
  if (envResolved) {
961
1133
  return envResolved;
962
1134
  }
963
1135
  if (this.agentId && this.agentId !== "01KEQCGJ65YENRA4QBXVN1YFFX") {
964
1136
  return this.agentId;
965
1137
  }
966
- const defaultIdMapping = {
967
- "business_analyst": "01KEJ95G304TNNAKGH5XNEEBVD",
968
- "developer_agent": "01KEQCGJ65YENRA4QBXVN1YFFX",
969
- "qa_agent": "01KEQFJZ3Q3JER11NH22HEZX9X",
970
- "specification_agent": "01KEPXTX37FTB4N672TZST4SGP",
971
- "scan_agent": "01KEQ9AHWB550J2244YBH3QATN",
972
- "code_review": ""
973
- };
974
- const resolved = defaultIdMapping[this.agentType];
975
- if (this.agentType === "code_review") {
976
- if (!resolved) {
977
- throw new Error("Agent ID for 'code_review' is not configured.");
978
- }
979
- return resolved;
980
- }
981
- return resolved || "01KEQCGJ65YENRA4QBXVN1YFFX";
1138
+ return "01KEQCGJ65YENRA4QBXVN1YFFX";
982
1139
  }
983
1140
  getAgentVersion() {
984
1141
  const config = ConfigManager.getInstance().getConfig();
985
- const versionMapping = {
986
- "business_analyst": config.agentVersions?.ba || process.env.STACKSPOT_BA_AGENT_VERSION,
987
- "developer_agent": config.agentVersions?.dev || process.env.STACKSPOT_DEV_AGENT_VERSION,
988
- "qa_agent": config.agentVersions?.qa || process.env.STACKSPOT_QA_AGENT_VERSION,
989
- "specification_agent": config.agentVersions?.spec || process.env.STACKSPOT_SPEC_AGENT_VERSION,
990
- "scan_agent": config.agentVersions?.scan || process.env.STACKSPOT_SCAN_AGENT_VERSION,
991
- "code_review": config.agentVersions?.codeReview || process.env.STACKSPOT_CODE_REVIEW_AGENT_VERSION
992
- };
993
- return versionMapping[this.agentType];
1142
+ return config.agentVersions?.dev || process.env.STACKSPOT_DEV_AGENT_VERSION;
994
1143
  }
995
1144
  async streamChat(prompt, options) {
996
1145
  const realm = await getActiveRealm();
@@ -1003,20 +1152,81 @@ var StackSpotProvider = class {
1003
1152
  if (!token) {
1004
1153
  throw new Error(`No authentication token found for realm '${realm}'. Please run 'shark login'.`);
1005
1154
  }
1155
+ let systemPrompt = UNIFIED_SYSTEM_PROMPT;
1156
+ let retrievedContext = "";
1157
+ const isHelperCall = options.conversationId?.startsWith("membox-");
1158
+ if (!isHelperCall) {
1159
+ const { MemboxManager: MemboxManager2 } = await import("../membox-manager-ZDKXDZPF.js");
1160
+ const memboxManager = new MemboxManager2();
1161
+ const query = options?.searchQuery || prompt;
1162
+ retrievedContext = await memboxManager.retrieveContext(query, []);
1163
+ if (retrievedContext) {
1164
+ systemPrompt = systemPrompt + "\n" + retrievedContext;
1165
+ }
1166
+ }
1006
1167
  const isFirstTurn = !options.conversationId;
1007
- const finalPrompt = isFirstTurn ? `SYSTEM INSTRUCTIONS:
1008
- ${UNIFIED_SYSTEM_PROMPT}
1168
+ let finalPrompt = prompt;
1169
+ const conversationId = options.conversationId || crypto.randomUUID();
1170
+ let history = [];
1171
+ if (!this.useServerConversation) {
1172
+ history = await HistoryManager.getHistory(conversationId);
1173
+ if (history.length === 0) {
1174
+ history.push({
1175
+ role: "system",
1176
+ content: systemPrompt
1177
+ });
1178
+ }
1179
+ history.push({ role: "user", content: prompt });
1180
+ let compiledPrompt = "";
1181
+ for (const msg of history) {
1182
+ if (msg.role === "system") {
1183
+ const skillExtension = skillManager.getSystemInstructionExtension();
1184
+ const fullSystemPrompt = skillExtension ? msg.content + "\n" + skillExtension : msg.content;
1185
+ compiledPrompt += `SYSTEM INSTRUCTIONS:
1186
+ ${fullSystemPrompt}
1187
+
1188
+ `;
1189
+ } else if (msg.role === "user") {
1190
+ compiledPrompt += `USER REQUEST:
1191
+ ${msg.content}
1192
+
1193
+ `;
1194
+ } else if (msg.role === "assistant") {
1195
+ compiledPrompt += `ASSISTANT RESPONSE:
1196
+ ${msg.content}
1197
+
1198
+ `;
1199
+ }
1200
+ }
1201
+ finalPrompt = compiledPrompt;
1202
+ } else {
1203
+ const skillExtension = skillManager.getSystemInstructionExtension();
1204
+ if (isFirstTurn) {
1205
+ const fullSystemPrompt = skillExtension ? systemPrompt + "\n" + skillExtension : systemPrompt;
1206
+ finalPrompt = `SYSTEM INSTRUCTIONS:
1207
+ ${fullSystemPrompt}
1009
1208
 
1010
1209
  USER REQUEST:
1011
- ${prompt}` : prompt;
1210
+ ${prompt}`;
1211
+ } else {
1212
+ finalPrompt = skillExtension ? prompt + "\n" + skillExtension : prompt;
1213
+ if (retrievedContext) {
1214
+ finalPrompt = `[MEM\xD3RIA E CONTEXTO RECUPERADOS]
1215
+ ${retrievedContext}
1216
+
1217
+ [MENSAGEM DO USU\xC1RIO]
1218
+ ${finalPrompt}`;
1219
+ }
1220
+ }
1221
+ }
1012
1222
  const requestPayload = {
1013
1223
  user_prompt: finalPrompt,
1014
1224
  streaming: true,
1015
1225
  stackspot_knowledge: false,
1016
1226
  return_ks_in_response: true,
1017
1227
  deep_search_ks: false,
1018
- use_conversation: true,
1019
- conversation_id: options.conversationId
1228
+ use_conversation: this.useServerConversation,
1229
+ conversation_id: this.useServerConversation ? options.conversationId : conversationId
1020
1230
  };
1021
1231
  const agentVersion = this.getAgentVersion();
1022
1232
  if (agentVersion) {
@@ -1054,7 +1264,7 @@ ${prompt}` : prompt;
1054
1264
  onComplete: async (message, metadata) => {
1055
1265
  rawResponse = {
1056
1266
  message: message || fullMessage,
1057
- conversation_id: metadata?.conversation_id || options.conversationId
1267
+ conversation_id: metadata?.conversation_id || conversationId
1058
1268
  };
1059
1269
  },
1060
1270
  onError: (error) => {
@@ -1064,6 +1274,11 @@ ${prompt}` : prompt;
1064
1274
  );
1065
1275
  FileLogger.log("PROVIDER_RESPONSE", "Raw response from StackSpot API", { rawResponse });
1066
1276
  const parsedResponse = parseAgentResponse(rawResponse);
1277
+ if (!this.useServerConversation) {
1278
+ parsedResponse.conversation_id = conversationId;
1279
+ history.push({ role: "assistant", content: JSON.stringify(parsedResponse) });
1280
+ await HistoryManager.saveHistory(conversationId, history);
1281
+ }
1067
1282
  if (options.onComplete) {
1068
1283
  options.onComplete(parsedResponse);
1069
1284
  }
@@ -1071,46 +1286,8 @@ ${prompt}` : prompt;
1071
1286
  }
1072
1287
  };
1073
1288
 
1074
- // src/core/workflow/history-manager.ts
1075
- import fs3 from "fs";
1076
- import path3 from "path";
1077
- var HistoryManager = class {
1078
- static getHistoryDir() {
1079
- const dir = path3.resolve(process.cwd(), "_sharkrc", "history");
1080
- if (!fs3.existsSync(dir)) {
1081
- fs3.mkdirSync(dir, { recursive: true });
1082
- }
1083
- return dir;
1084
- }
1085
- static getFilePath(conversationId) {
1086
- return path3.resolve(this.getHistoryDir(), `${conversationId}.json`);
1087
- }
1088
- static async getHistory(conversationId) {
1089
- const filePath = this.getFilePath(conversationId);
1090
- if (!fs3.existsSync(filePath)) {
1091
- return [];
1092
- }
1093
- try {
1094
- const raw = fs3.readFileSync(filePath, "utf-8");
1095
- const parsed = JSON.parse(raw);
1096
- return Array.isArray(parsed) ? parsed : [];
1097
- } catch {
1098
- return [];
1099
- }
1100
- }
1101
- static async saveHistory(conversationId, messages) {
1102
- const filePath = this.getFilePath(conversationId);
1103
- fs3.writeFileSync(filePath, JSON.stringify(messages, null, 2), "utf-8");
1104
- }
1105
- static async appendMessage(conversationId, message) {
1106
- const history = await this.getHistory(conversationId);
1107
- history.push(message);
1108
- await this.saveHistory(conversationId, history);
1109
- }
1110
- };
1111
-
1112
1289
  // src/core/api/openai-compatible-provider.ts
1113
- import crypto from "crypto";
1290
+ import crypto2 from "crypto";
1114
1291
  var OpenAICompatibleProvider = class {
1115
1292
  constructor(options) {
1116
1293
  this.options = options;
@@ -1119,7 +1296,7 @@ var OpenAICompatibleProvider = class {
1119
1296
  return UNIFIED_SYSTEM_PROMPT;
1120
1297
  }
1121
1298
  async streamChat(prompt, options) {
1122
- const conversationId = options.conversationId || crypto.randomUUID();
1299
+ const conversationId = options.conversationId || crypto2.randomUUID();
1123
1300
  const history = await HistoryManager.getHistory(conversationId);
1124
1301
  if (history.length === 0) {
1125
1302
  history.push({
@@ -1128,9 +1305,32 @@ var OpenAICompatibleProvider = class {
1128
1305
  });
1129
1306
  }
1130
1307
  history.push({ role: "user", content: prompt });
1308
+ const requestMessages = history.map((msg) => ({ ...msg }));
1309
+ const isHelperCall = conversationId.startsWith("membox-");
1310
+ if (!isHelperCall) {
1311
+ let systemMsg = requestMessages.find((m) => m.role === "system");
1312
+ if (!systemMsg) {
1313
+ systemMsg = {
1314
+ role: "system",
1315
+ content: this.getAgentSystemPrompt(options.agentType)
1316
+ };
1317
+ requestMessages.unshift(systemMsg);
1318
+ }
1319
+ const { MemboxManager: MemboxManager2 } = await import("../membox-manager-ZDKXDZPF.js");
1320
+ const memboxManager = new MemboxManager2();
1321
+ const query = options?.searchQuery || prompt;
1322
+ const retrievedContext = await memboxManager.retrieveContext(query, history);
1323
+ if (retrievedContext) {
1324
+ systemMsg.content = systemMsg.content + "\n" + retrievedContext;
1325
+ }
1326
+ const skillExtension = skillManager.getSystemInstructionExtension();
1327
+ if (skillExtension) {
1328
+ systemMsg.content = systemMsg.content + "\n" + skillExtension;
1329
+ }
1330
+ }
1131
1331
  const requestPayload = {
1132
1332
  model: this.options.model,
1133
- messages: history,
1333
+ messages: requestMessages,
1134
1334
  stream: true,
1135
1335
  temperature: 0.2
1136
1336
  };
@@ -1140,172 +1340,7 @@ var OpenAICompatibleProvider = class {
1140
1340
  json_schema: {
1141
1341
  name: "agent_response",
1142
1342
  strict: true,
1143
- schema: {
1144
- type: "object",
1145
- properties: {
1146
- action: {
1147
- type: "object",
1148
- properties: {
1149
- type: {
1150
- type: "string",
1151
- enum: [
1152
- "create_file",
1153
- "modify_file",
1154
- "read_file",
1155
- "list_files",
1156
- "search_file",
1157
- "search_code",
1158
- "delete_file",
1159
- "run_command",
1160
- "talk_with_user",
1161
- "use_mcp_tool",
1162
- "activate_skill",
1163
- "define_subagent",
1164
- "invoke_subagent",
1165
- "send_message",
1166
- "manage_subagents",
1167
- "complete_task",
1168
- "list_structure",
1169
- "modify_ast",
1170
- "search_ast",
1171
- "ast_list_structure",
1172
- "ast_get_method",
1173
- "ast_add_method",
1174
- "ast_modify_method",
1175
- "ast_remove_method",
1176
- "ast_add_class",
1177
- "ast_get_property",
1178
- "ast_add_property",
1179
- "ast_modify_property",
1180
- "ast_remove_property",
1181
- "ast_add_decorator",
1182
- "ast_add_interface",
1183
- "ast_add_type_alias",
1184
- "ast_add_function",
1185
- "ast_remove_function",
1186
- "ast_add_import",
1187
- "ast_remove_import",
1188
- "ast_organize_imports"
1189
- ]
1190
- },
1191
- path: { type: ["string", "null"] },
1192
- content: { type: ["string", "null"] },
1193
- start_anchor: { type: ["string", "null"] },
1194
- end_anchor: { type: ["string", "null"] },
1195
- command: { type: ["string", "null"] },
1196
- query: { type: ["string", "null"] },
1197
- tool_name: { type: ["string", "null"] },
1198
- tool_args: { type: ["string", "null"] },
1199
- line_range: {
1200
- type: ["array", "null"],
1201
- items: { type: "number" }
1202
- },
1203
- target_content: { type: ["string", "null"] },
1204
- is_regex: { type: ["boolean", "null"] },
1205
- pattern: { type: ["string", "null"] },
1206
- fix: { type: ["string", "null"] },
1207
- language: { type: ["string", "null"] },
1208
- file_path: { type: ["string", "null"] },
1209
- class_name: { type: ["string", "null"] },
1210
- method_name: { type: ["string", "null"] },
1211
- method_code: { type: ["string", "null"] },
1212
- property_name: { type: ["string", "null"] },
1213
- property_code: { type: ["string", "null"] },
1214
- extends_class: { type: ["string", "null"] },
1215
- implements_interfaces: {
1216
- type: ["array", "null"],
1217
- items: { type: "string" }
1218
- },
1219
- decorator_code: { type: ["string", "null"] },
1220
- interface_code: { type: ["string", "null"] },
1221
- type_code: { type: ["string", "null"] },
1222
- function_name: { type: ["string", "null"] },
1223
- function_code: { type: ["string", "null"] },
1224
- import_statement: { type: ["string", "null"] },
1225
- module_path: { type: ["string", "null"] },
1226
- new_body: { type: ["string", "null"] },
1227
- confirmed: { type: ["boolean", "null"] },
1228
- skill_name: { type: ["string", "null"] },
1229
- Subagents: {
1230
- type: ["array", "null"],
1231
- items: {
1232
- type: "object",
1233
- properties: {
1234
- TypeName: { type: "string" },
1235
- Role: { type: "string" },
1236
- Prompt: { type: "string" }
1237
- },
1238
- required: ["TypeName", "Role", "Prompt"],
1239
- additionalProperties: false
1240
- }
1241
- },
1242
- Recipient: { type: ["string", "null"] },
1243
- Message: { type: ["string", "null"] },
1244
- Action: { type: ["string", "null"] },
1245
- ConversationIds: {
1246
- type: ["array", "null"],
1247
- items: { type: "string" }
1248
- },
1249
- name: { type: ["string", "null"] },
1250
- description: { type: ["string", "null"] },
1251
- system_prompt: { type: ["string", "null"] },
1252
- enable_write_tools: { type: ["boolean", "null"] },
1253
- enable_subagent_tools: { type: ["boolean", "null"] },
1254
- enable_mcp_tools: { type: ["boolean", "null"] }
1255
- },
1256
- required: [
1257
- "type",
1258
- "path",
1259
- "content",
1260
- "start_anchor",
1261
- "end_anchor",
1262
- "command",
1263
- "query",
1264
- "tool_name",
1265
- "tool_args",
1266
- "line_range",
1267
- "target_content",
1268
- "is_regex",
1269
- "pattern",
1270
- "fix",
1271
- "language",
1272
- "file_path",
1273
- "class_name",
1274
- "method_name",
1275
- "method_code",
1276
- "property_name",
1277
- "property_code",
1278
- "extends_class",
1279
- "implements_interfaces",
1280
- "decorator_code",
1281
- "interface_code",
1282
- "type_code",
1283
- "function_name",
1284
- "function_code",
1285
- "import_statement",
1286
- "module_path",
1287
- "new_body",
1288
- "confirmed",
1289
- "skill_name",
1290
- "Subagents",
1291
- "Recipient",
1292
- "Message",
1293
- "Action",
1294
- "ConversationIds",
1295
- "name",
1296
- "description",
1297
- "system_prompt",
1298
- "enable_write_tools",
1299
- "enable_subagent_tools",
1300
- "enable_mcp_tools"
1301
- ],
1302
- additionalProperties: false
1303
- },
1304
- summary: { type: "string" }
1305
- },
1306
- required: ["action", "summary"],
1307
- additionalProperties: false
1308
- }
1343
+ schema: toStrictOpenAISchema(AGENT_RESPONSE_JSON_SCHEMA)
1309
1344
  }
1310
1345
  };
1311
1346
  } else {
@@ -1326,20 +1361,26 @@ var OpenAICompatibleProvider = class {
1326
1361
  headers: sanitizedHeaders,
1327
1362
  payload: requestPayload
1328
1363
  });
1329
- const res = await fetch(`${this.options.baseURL}/chat/completions`, {
1330
- method: "POST",
1331
- headers,
1332
- body: JSON.stringify(requestPayload)
1333
- });
1334
- if (!res.ok) {
1335
- const errBody = await res.text();
1336
- throw new Error(`OpenAI API request failed: ${res.status} ${res.statusText} - ${errBody}`);
1337
- }
1338
- const reader = res.body?.getReader();
1339
- if (!reader) {
1340
- throw new Error("Response body reader is undefined");
1341
- }
1364
+ const controller = new AbortController();
1365
+ const timeoutId = setTimeout(() => controller.abort(), 3e5);
1366
+ let reader = void 0;
1342
1367
  try {
1368
+ const res = await fetch(`${this.options.baseURL}/chat/completions`, {
1369
+ method: "POST",
1370
+ headers,
1371
+ body: JSON.stringify(requestPayload),
1372
+ signal: controller.signal
1373
+ });
1374
+ if (!res.ok) {
1375
+ clearTimeout(timeoutId);
1376
+ const errBody = await res.text();
1377
+ throw new Error(`OpenAI API request failed: ${res.status} ${res.statusText} - ${errBody}`);
1378
+ }
1379
+ reader = res.body?.getReader();
1380
+ if (!reader) {
1381
+ clearTimeout(timeoutId);
1382
+ throw new Error("Response body reader is undefined");
1383
+ }
1343
1384
  const decoder = new TextDecoder();
1344
1385
  let fullContent = "";
1345
1386
  let done = false;
@@ -1395,6 +1436,7 @@ var OpenAICompatibleProvider = class {
1395
1436
  }
1396
1437
  }
1397
1438
  }
1439
+ clearTimeout(timeoutId);
1398
1440
  FileLogger.log("PROVIDER_RESPONSE", "Raw response from OpenAI Compatible API", { fullContent });
1399
1441
  const parsedResponse = parseAgentResponse(fullContent);
1400
1442
  parsedResponse.conversation_id = conversationId;
@@ -1404,13 +1446,66 @@ var OpenAICompatibleProvider = class {
1404
1446
  options.onComplete(parsedResponse);
1405
1447
  }
1406
1448
  return parsedResponse;
1449
+ } catch (error) {
1450
+ clearTimeout(timeoutId);
1451
+ if (error.name === "AbortError") {
1452
+ throw new Error(`OpenAI API request timed out after 5 minutes.`);
1453
+ }
1454
+ throw error;
1407
1455
  } finally {
1408
- if (typeof reader.releaseLock === "function") {
1456
+ if (reader && typeof reader.releaseLock === "function") {
1409
1457
  reader.releaseLock();
1410
1458
  }
1411
1459
  }
1412
1460
  }
1413
1461
  };
1462
+ function toStrictOpenAISchema(schema) {
1463
+ if (!schema || typeof schema !== "object") {
1464
+ return schema;
1465
+ }
1466
+ const cloned = JSON.parse(JSON.stringify(schema));
1467
+ delete cloned.$schema;
1468
+ delete cloned.title;
1469
+ function processNode(node) {
1470
+ if (!node || typeof node !== "object") {
1471
+ return;
1472
+ }
1473
+ if (node.type === "object") {
1474
+ node.additionalProperties = false;
1475
+ if (node.properties) {
1476
+ const originalRequired = node.required || [];
1477
+ const allProperties = Object.keys(node.properties);
1478
+ node.required = allProperties;
1479
+ for (const key of allProperties) {
1480
+ const prop = node.properties[key];
1481
+ if (!originalRequired.includes(key)) {
1482
+ if (prop.type) {
1483
+ if (Array.isArray(prop.type)) {
1484
+ if (!prop.type.includes("null")) {
1485
+ prop.type.push("null");
1486
+ }
1487
+ } else if (typeof prop.type === "string") {
1488
+ if (prop.type !== "null") {
1489
+ prop.type = [prop.type, "null"];
1490
+ }
1491
+ }
1492
+ }
1493
+ }
1494
+ if (prop.enum && Array.isArray(prop.enum)) {
1495
+ if (!prop.enum.includes(null)) {
1496
+ prop.enum.push(null);
1497
+ }
1498
+ }
1499
+ processNode(prop);
1500
+ }
1501
+ }
1502
+ } else if (node.type === "array" && node.items) {
1503
+ processNode(node.items);
1504
+ }
1505
+ }
1506
+ processNode(cloned);
1507
+ return cloned;
1508
+ }
1414
1509
 
1415
1510
  // src/core/api/provider-resolver.ts
1416
1511
  var ProviderResolver = class {
@@ -1499,8 +1594,8 @@ var ConversationManager = class _ConversationManager {
1499
1594
  var conversationManager = ConversationManager.getInstance();
1500
1595
 
1501
1596
  // src/core/workflow/anchor-state-manager.ts
1502
- import fs4 from "fs";
1503
- import path4 from "path";
1597
+ import fs5 from "fs";
1598
+ import path5 from "path";
1504
1599
  import { diffLines } from "diff";
1505
1600
  var AnchorStateManager = class {
1506
1601
  cache = /* @__PURE__ */ new Map();
@@ -2515,10 +2610,10 @@ var AnchorStateManager = class {
2515
2610
  }
2516
2611
  }
2517
2612
  getAnchoredContent(filePath) {
2518
- const absolutePath = path4.resolve(filePath);
2613
+ const absolutePath = path5.resolve(filePath);
2519
2614
  let lineStates = this.cache.get(absolutePath);
2520
2615
  if (!lineStates) {
2521
- const content = fs4.readFileSync(absolutePath, "utf8");
2616
+ const content = fs5.readFileSync(absolutePath, "utf8");
2522
2617
  const hasTrailingNewline = content.endsWith("\n");
2523
2618
  const lines = hasTrailingNewline ? content.slice(0, -1).split("\n") : content.split("\n");
2524
2619
  const usedAnchors = /* @__PURE__ */ new Set();
@@ -2531,7 +2626,7 @@ var AnchorStateManager = class {
2531
2626
  return lineStates.map((ls) => `${ls.anchor}\xA7${ls.text}`).join("\n");
2532
2627
  }
2533
2628
  applyAnchoredEdit(filePath, startAnchor, endAnchor, content) {
2534
- const absolutePath = path4.resolve(filePath);
2629
+ const absolutePath = path5.resolve(filePath);
2535
2630
  let lineStates = this.cache.get(absolutePath);
2536
2631
  if (!lineStates) {
2537
2632
  this.getAnchoredContent(absolutePath);
@@ -2548,7 +2643,7 @@ var AnchorStateManager = class {
2548
2643
  if (startIndex > endIndex) {
2549
2644
  throw new Error(`Invalid range: start anchor "${startAnchor}" is after end anchor "${endAnchor}"`);
2550
2645
  }
2551
- const originalContent = fs4.readFileSync(absolutePath, "utf8");
2646
+ const originalContent = fs5.readFileSync(absolutePath, "utf8");
2552
2647
  const hasTrailingNewline = originalContent.endsWith("\n");
2553
2648
  const oldLines = lineStates.map((ls) => ls.text);
2554
2649
  const cleanContent = content.endsWith("\n") ? content.slice(0, -1) : content;
@@ -2559,7 +2654,7 @@ var AnchorStateManager = class {
2559
2654
  ...oldLines.slice(endIndex + 1)
2560
2655
  ];
2561
2656
  const fileContentToWrite = updatedLines.join("\n") + (hasTrailingNewline ? "\n" : "");
2562
- fs4.writeFileSync(absolutePath, fileContentToWrite, "utf8");
2657
+ fs5.writeFileSync(absolutePath, fileContentToWrite, "utf8");
2563
2658
  const usedAnchors = /* @__PURE__ */ new Set();
2564
2659
  for (let i = 0; i < lineStates.length; i++) {
2565
2660
  usedAnchors.add(lineStates[i].anchor);
@@ -2605,457 +2700,19 @@ var AnchorStateManager = class {
2605
2700
  };
2606
2701
 
2607
2702
  // src/core/agents/developer-agent.ts
2608
- import fs9 from "fs";
2609
- import path10 from "path";
2703
+ import fs8 from "fs";
2704
+ import path8 from "path";
2610
2705
 
2611
2706
  // src/core/agents/agent-tools.ts
2612
2707
  import fs6 from "fs";
2613
- import path7 from "path";
2708
+ import path6 from "path";
2614
2709
  import fg from "fast-glob";
2615
2710
  import { exec } from "child_process";
2616
2711
  import { promisify } from "util";
2617
- import { fileURLToPath } from "url";
2618
-
2619
- // src/core/ast-editing/editors/code-editor-factory.ts
2620
- import * as path6 from "path";
2621
-
2622
- // src/core/ast-editing/editors/typescript-editor.ts
2623
- import { Project, SyntaxKind } from "ts-morph";
2624
- import * as path5 from "path";
2625
- import * as fs5 from "fs";
2626
- var TypeScriptEditor = class {
2627
- project;
2628
- constructor() {
2629
- this.project = new Project({
2630
- skipAddingFilesFromTsConfig: true,
2631
- compilerOptions: {
2632
- target: 99,
2633
- // ESNext
2634
- module: 99
2635
- // ESNext
2636
- }
2637
- });
2638
- }
2639
- /**
2640
- * Get or add source file to project
2641
- */
2642
- getSourceFile(filePath) {
2643
- const absolutePath = path5.resolve(filePath);
2644
- let sourceFile = this.project.getSourceFile(absolutePath);
2645
- if (!sourceFile) {
2646
- if (!fs5.existsSync(absolutePath)) {
2647
- throw new Error(`File not found: ${absolutePath}`);
2648
- }
2649
- sourceFile = this.project.addSourceFileAtPath(absolutePath);
2650
- }
2651
- return sourceFile;
2652
- }
2653
- /**
2654
- * Get class declaration by name
2655
- */
2656
- getClass(sourceFile, className) {
2657
- const classDecl = sourceFile.getClass(className);
2658
- if (!classDecl) {
2659
- throw new Error(`Class "${className}" not found in ${sourceFile.getFilePath()}`);
2660
- }
2661
- return classDecl;
2662
- }
2663
- // ═══════════════════════════════════════════════════════
2664
- // IMPLEMENTATION: listStructure
2665
- // ═══════════════════════════════════════════════════════
2666
- async listStructure(filePath) {
2667
- const sourceFile = this.getSourceFile(filePath);
2668
- const classes = sourceFile.getClasses().map((cls) => ({
2669
- name: cls.getName() || "<anonymous>",
2670
- methods: cls.getMethods().map((m) => this.extractMethodInfo(m)),
2671
- properties: cls.getProperties().map((p) => this.extractPropertyInfo(p)),
2672
- decorators: cls.getDecorators().map((d) => d.getText()),
2673
- extendsClass: cls.getExtends()?.getText(),
2674
- implementsInterfaces: cls.getImplements().map((i) => i.getText())
2675
- }));
2676
- const interfaces = sourceFile.getInterfaces().map((iface) => ({
2677
- name: iface.getName(),
2678
- properties: iface.getProperties().map((p) => this.extractPropertyInfo(p)),
2679
- extends: iface.getExtends().map((e) => e.getText())
2680
- }));
2681
- const functions = sourceFile.getFunctions().map((fn) => ({
2682
- name: fn.getName() || "<anonymous>",
2683
- parameters: fn.getParameters().map((p) => ({
2684
- name: p.getName(),
2685
- type: p.getType().getText(),
2686
- isOptional: p.isOptional()
2687
- })),
2688
- returnType: fn.getReturnType().getText(),
2689
- isAsync: fn.isAsync(),
2690
- isExported: fn.isExported()
2691
- }));
2692
- const imports = sourceFile.getImportDeclarations().map((imp) => ({
2693
- modulePath: imp.getModuleSpecifierValue(),
2694
- isDefault: !!imp.getDefaultImport(),
2695
- namedImports: imp.getNamedImports().map((n) => n.getName()),
2696
- namespaceImport: imp.getNamespaceImport()?.getText()
2697
- }));
2698
- const exports = sourceFile.getExportedDeclarations();
2699
- const exportInfo = Array.from(exports.entries()).flatMap(
2700
- ([name, declarations]) => declarations.map((decl) => ({
2701
- name,
2702
- type: this.getDeclarationType(decl),
2703
- isDefault: sourceFile.getDefaultExportSymbol()?.getName() === name
2704
- }))
2705
- );
2706
- return {
2707
- classes,
2708
- interfaces,
2709
- functions,
2710
- imports,
2711
- exports: exportInfo
2712
- };
2713
- }
2714
- extractMethodInfo(method) {
2715
- return {
2716
- name: method.getName(),
2717
- parameters: method.getParameters().map((p) => ({
2718
- name: p.getName(),
2719
- type: p.getType().getText(),
2720
- isOptional: p.isOptional()
2721
- })),
2722
- returnType: method.getReturnType().getText(),
2723
- isAsync: method.isAsync(),
2724
- isStatic: method.isStatic(),
2725
- visibility: this.getVisibility(method),
2726
- decorators: method.getDecorators().map((d) => d.getText())
2727
- };
2728
- }
2729
- extractPropertyInfo(property) {
2730
- return {
2731
- name: property.getName(),
2732
- type: property.getType()?.getText(),
2733
- visibility: this.getVisibility(property),
2734
- isReadonly: property.isReadonly(),
2735
- initializer: property.getInitializer()?.getText()
2736
- };
2737
- }
2738
- getVisibility(node) {
2739
- if (node.hasModifier?.(SyntaxKind.PrivateKeyword)) return "private";
2740
- if (node.hasModifier?.(SyntaxKind.ProtectedKeyword)) return "protected";
2741
- return "public";
2742
- }
2743
- getDeclarationType(decl) {
2744
- if (decl.getKind() === SyntaxKind.ClassDeclaration) return "class";
2745
- if (decl.getKind() === SyntaxKind.FunctionDeclaration) return "function";
2746
- if (decl.getKind() === SyntaxKind.InterfaceDeclaration) return "interface";
2747
- if (decl.getKind() === SyntaxKind.TypeAliasDeclaration) return "type";
2748
- return "const";
2749
- }
2750
- // ═══════════════════════════════════════════════════════
2751
- // IMPLEMENTATION: Class Operations
2752
- // ═══════════════════════════════════════════════════════
2753
- async addClass(filePath, className, options) {
2754
- try {
2755
- const sourceFile = this.getSourceFile(filePath);
2756
- sourceFile.addClass({
2757
- name: className,
2758
- extends: options?.extendsClass,
2759
- implements: options?.implementsInterfaces,
2760
- isExported: true
2761
- });
2762
- await sourceFile.save();
2763
- return true;
2764
- } catch (error) {
2765
- console.error(`Failed to add class "${className}":`, error);
2766
- return false;
2767
- }
2768
- }
2769
- async addProperty(filePath, className, propertyCode) {
2770
- try {
2771
- const sourceFile = this.getSourceFile(filePath);
2772
- const classDecl = this.getClass(sourceFile, className);
2773
- const closeBrace = classDecl.getEnd() - 1;
2774
- classDecl.insertText(closeBrace, `
2775
- ${propertyCode}`);
2776
- classDecl.formatText();
2777
- await sourceFile.save();
2778
- return true;
2779
- } catch (error) {
2780
- console.error(`Failed to add property to "${className}":`, error);
2781
- return false;
2782
- }
2783
- }
2784
- async getProperty(filePath, className, propertyName) {
2785
- try {
2786
- const sourceFile = this.getSourceFile(filePath);
2787
- const classDecl = this.getClass(sourceFile, className);
2788
- const property = classDecl.getProperty(propertyName);
2789
- if (!property) {
2790
- return void 0;
2791
- }
2792
- return property.getText();
2793
- } catch (error) {
2794
- console.error(`Failed to get property "${propertyName}":`, error);
2795
- return void 0;
2796
- }
2797
- }
2798
- async modifyProperty(filePath, className, propertyName, newCode) {
2799
- try {
2800
- const sourceFile = this.getSourceFile(filePath);
2801
- const classDecl = this.getClass(sourceFile, className);
2802
- const property = classDecl.getProperty(propertyName);
2803
- if (!property) {
2804
- throw new Error(`Property "${propertyName}" not found in class "${className}"`);
2805
- }
2806
- property.replaceWithText(newCode);
2807
- classDecl.formatText();
2808
- await sourceFile.save();
2809
- return true;
2810
- } catch (error) {
2811
- console.error(`Failed to modify property "${propertyName}":`, error);
2812
- return false;
2813
- }
2814
- }
2815
- async removeProperty(filePath, className, propertyName) {
2816
- try {
2817
- const sourceFile = this.getSourceFile(filePath);
2818
- const classDecl = this.getClass(sourceFile, className);
2819
- const property = classDecl.getProperty(propertyName);
2820
- if (!property) {
2821
- throw new Error(`Property "${propertyName}" not found in class "${className}"`);
2822
- }
2823
- property.remove();
2824
- await sourceFile.save();
2825
- return true;
2826
- } catch (error) {
2827
- console.error(`Failed to remove property "${propertyName}":`, error);
2828
- return false;
2829
- }
2830
- }
2831
- async addMethod(filePath, className, methodCode) {
2832
- try {
2833
- const sourceFile = this.getSourceFile(filePath);
2834
- const classDecl = this.getClass(sourceFile, className);
2835
- const closeBrace = classDecl.getEnd() - 1;
2836
- classDecl.insertText(closeBrace, `
2837
- ${methodCode}
2838
- `);
2839
- classDecl.formatText();
2840
- await sourceFile.save();
2841
- return true;
2842
- } catch (error) {
2843
- console.error(`Failed to add method to "${className}":`, error);
2844
- return false;
2845
- }
2846
- }
2847
- async modifyMethod(filePath, className, methodName, newBody) {
2848
- try {
2849
- const sourceFile = this.getSourceFile(filePath);
2850
- const classDecl = this.getClass(sourceFile, className);
2851
- const method = classDecl.getMethod(methodName);
2852
- if (!method) {
2853
- throw new Error(`Method "${methodName}" not found in class "${className}"`);
2854
- }
2855
- method.setBodyText(newBody);
2856
- method.formatText();
2857
- await sourceFile.save();
2858
- return true;
2859
- } catch (error) {
2860
- console.error(`Failed to modify method "${methodName}":`, error);
2861
- return false;
2862
- }
2863
- }
2864
- async removeMethod(filePath, className, methodName) {
2865
- try {
2866
- const sourceFile = this.getSourceFile(filePath);
2867
- const classDecl = this.getClass(sourceFile, className);
2868
- const method = classDecl.getMethod(methodName);
2869
- if (!method) {
2870
- throw new Error(`Method "${methodName}" not found in class "${className}"`);
2871
- }
2872
- method.remove();
2873
- await sourceFile.save();
2874
- return true;
2875
- } catch (error) {
2876
- console.error(`Failed to remove method "${methodName}":`, error);
2877
- return false;
2878
- }
2879
- }
2880
- async addDecorator(filePath, className, decoratorCode) {
2881
- try {
2882
- const sourceFile = this.getSourceFile(filePath);
2883
- const classDecl = this.getClass(sourceFile, className);
2884
- const start = classDecl.getStart();
2885
- sourceFile.insertText(start, `${decoratorCode}
2886
- `);
2887
- sourceFile.formatText();
2888
- await sourceFile.save();
2889
- return true;
2890
- } catch (error) {
2891
- console.error(`Failed to add decorator to "${className}":`, error);
2892
- return false;
2893
- }
2894
- }
2895
- async getMethod(filePath, className, methodName) {
2896
- try {
2897
- const sourceFile = this.getSourceFile(filePath);
2898
- const classDecl = this.getClass(sourceFile, className);
2899
- const method = classDecl.getMethod(methodName);
2900
- if (!method) {
2901
- return void 0;
2902
- }
2903
- return method.getText();
2904
- } catch (error) {
2905
- console.error(`Failed to get method "${methodName}":`, error);
2906
- return void 0;
2907
- }
2908
- }
2909
- // ═══════════════════════════════════════════════════════
2910
- // IMPLEMENTATION: Interface/Type Operations
2911
- // ═══════════════════════════════════════════════════════
2912
- async addInterface(filePath, interfaceCode) {
2913
- try {
2914
- const sourceFile = this.getSourceFile(filePath);
2915
- sourceFile.insertText(sourceFile.getEnd(), `
2916
-
2917
- ${interfaceCode}`);
2918
- sourceFile.formatText();
2919
- await sourceFile.save();
2920
- return true;
2921
- } catch (error) {
2922
- console.error(`Failed to add interface:`, error);
2923
- return false;
2924
- }
2925
- }
2926
- async addTypeAlias(filePath, typeCode) {
2927
- try {
2928
- const sourceFile = this.getSourceFile(filePath);
2929
- sourceFile.insertText(sourceFile.getEnd(), `
2930
-
2931
- ${typeCode}`);
2932
- sourceFile.formatText();
2933
- await sourceFile.save();
2934
- return true;
2935
- } catch (error) {
2936
- console.error(`Failed to add type alias:`, error);
2937
- return false;
2938
- }
2939
- }
2940
- // ═══════════════════════════════════════════════════════
2941
- // IMPLEMENTATION: Function Operations
2942
- // ═══════════════════════════════════════════════════════
2943
- async addFunction(filePath, functionCode) {
2944
- try {
2945
- const sourceFile = this.getSourceFile(filePath);
2946
- sourceFile.insertText(sourceFile.getEnd(), `
2947
-
2948
- ${functionCode}`);
2949
- sourceFile.formatText();
2950
- await sourceFile.save();
2951
- return true;
2952
- } catch (error) {
2953
- console.error(`Failed to add function:`, error);
2954
- return false;
2955
- }
2956
- }
2957
- async removeFunction(filePath, functionName) {
2958
- try {
2959
- const sourceFile = this.getSourceFile(filePath);
2960
- const func = sourceFile.getFunction(functionName);
2961
- if (!func) {
2962
- throw new Error(`Function "${functionName}" not found`);
2963
- }
2964
- func.remove();
2965
- await sourceFile.save();
2966
- return true;
2967
- } catch (error) {
2968
- console.error(`Failed to remove function "${functionName}":`, error);
2969
- return false;
2970
- }
2971
- }
2972
- // ═══════════════════════════════════════════════════════
2973
- // IMPLEMENTATION: Import/Export Operations
2974
- // ═══════════════════════════════════════════════════════
2975
- async addImport(filePath, importStatement) {
2976
- try {
2977
- const sourceFile = this.getSourceFile(filePath);
2978
- const lastImport = sourceFile.getImportDeclarations().pop();
2979
- const pos = lastImport ? lastImport.getEnd() : 0;
2980
- sourceFile.insertText(pos, `
2981
- ${importStatement}`);
2982
- this.organizeImports(filePath);
2983
- await sourceFile.save();
2984
- return true;
2985
- } catch (error) {
2986
- console.error(`Failed to add import:`, error);
2987
- return false;
2988
- }
2989
- }
2990
- async removeImport(filePath, modulePath) {
2991
- try {
2992
- const sourceFile = this.getSourceFile(filePath);
2993
- const importDecls = sourceFile.getImportDeclarations().filter((imp) => imp.getModuleSpecifierValue() === modulePath);
2994
- if (importDecls.length === 0) {
2995
- throw new Error(`Import from "${modulePath}" not found`);
2996
- }
2997
- importDecls.forEach((d) => d.remove());
2998
- await sourceFile.save();
2999
- return true;
3000
- } catch (error) {
3001
- console.error(`Failed to remove import from "${modulePath}":`, error);
3002
- return false;
3003
- }
3004
- }
3005
- async organizeImports(filePath) {
3006
- try {
3007
- const sourceFile = this.getSourceFile(filePath);
3008
- sourceFile.organizeImports();
3009
- const imports = sourceFile.getImportDeclarations();
3010
- const importStructure = imports.map((i) => i.getStructure());
3011
- importStructure.sort((a, b) => {
3012
- return a.moduleSpecifier.localeCompare(b.moduleSpecifier);
3013
- });
3014
- imports.forEach((i) => i.remove());
3015
- sourceFile.addImportDeclarations(importStructure);
3016
- await sourceFile.save();
3017
- return true;
3018
- } catch (error) {
3019
- console.error(`Failed to organize imports:`, error);
3020
- return false;
3021
- }
3022
- }
3023
- };
3024
-
3025
- // src/core/ast-editing/editors/code-editor-factory.ts
3026
- var CodeEditorFactory = class {
3027
- static tsEditor = null;
3028
- /**
3029
- * Returns appropriate editor for the file, or null if AST editing not supported
3030
- */
3031
- static getEditor(filePath) {
3032
- const ext = path6.extname(filePath).toLowerCase();
3033
- if ([".ts", ".tsx", ".js", ".jsx"].includes(ext)) {
3034
- if (!this.tsEditor) {
3035
- this.tsEditor = new TypeScriptEditor();
3036
- }
3037
- return this.tsEditor;
3038
- }
3039
- return null;
3040
- }
3041
- /**
3042
- * Clear cached editors (useful for testing)
3043
- */
3044
- static clearCache() {
3045
- this.tsEditor = null;
3046
- }
3047
- };
3048
-
3049
- // src/core/agents/agent-tools.ts
3050
2712
  var execAsync = promisify(exec);
3051
- function detectLineEnding(content) {
3052
- const crlf = content.split("\r\n").length - 1;
3053
- const lf = content.split("\n").length - 1 - crlf;
3054
- return crlf > lf ? "\r\n" : "\n";
3055
- }
3056
2713
  function handleListFiles(dirPath) {
3057
2714
  try {
3058
- const fullPath = path7.resolve(process.cwd(), dirPath);
2715
+ const fullPath = path6.resolve(process.cwd(), dirPath);
3059
2716
  if (!fs6.existsSync(fullPath)) return `Error: Directory ${dirPath} does not exist.`;
3060
2717
  const items = fs6.readdirSync(fullPath, { withFileTypes: true });
3061
2718
  return items.map((item) => {
@@ -3065,54 +2722,6 @@ function handleListFiles(dirPath) {
3065
2722
  return `Error listing files: ${e.message}`;
3066
2723
  }
3067
2724
  }
3068
- function handleReadFile(filePath, showLineNumbers = true) {
3069
- try {
3070
- const fullPath = path7.resolve(process.cwd(), filePath);
3071
- if (!fs6.existsSync(fullPath)) return `Error: File ${filePath} does not exist.`;
3072
- const stats = fs6.statSync(fullPath);
3073
- if (stats.size > 100 * 1024) return `Error: File too large to read (${stats.size} bytes). Limit is 100KB.`;
3074
- const content = fs6.readFileSync(fullPath, "utf-8");
3075
- if (showLineNumbers) {
3076
- const lines = content.split("\n");
3077
- return lines.map((line, idx) => `${idx + 1}: ${line}`).join("\n");
3078
- }
3079
- return content;
3080
- } catch (e) {
3081
- return `Error reading file: ${e.message}`;
3082
- }
3083
- }
3084
- function replaceLineRange(filePath, startLine, endLine, newContent, tui2) {
3085
- try {
3086
- if (!fs6.existsSync(filePath)) {
3087
- tui2.log.error(`\u274C File not found for modification: ${filePath}`);
3088
- return false;
3089
- }
3090
- const currentFileContent = fs6.readFileSync(filePath, "utf-8");
3091
- const lineEnding = detectLineEnding(currentFileContent);
3092
- const lines = currentFileContent.split(lineEnding);
3093
- if (startLine < 1 || startLine > lines.length) {
3094
- tui2.log.error(`\u274C Invalid start line: ${startLine}. File has ${lines.length} lines.`);
3095
- return false;
3096
- }
3097
- if (endLine < startLine || endLine > lines.length) {
3098
- tui2.log.error(`\u274C Invalid end line: ${endLine}. Must be >= startLine and <= file length.`);
3099
- return false;
3100
- }
3101
- const before = lines.slice(0, startLine - 1);
3102
- const after = lines.slice(endLine);
3103
- const newLines = newContent.split(lineEnding);
3104
- const normalizedNewLines = newContent.replace(/\r\n/g, "\n").split("\n");
3105
- const result = [...before, ...normalizedNewLines, ...after].join(lineEnding);
3106
- const BOM = "\uFEFF";
3107
- const finalContent = result.startsWith(BOM) ? result : BOM + result;
3108
- fs6.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
3109
- tui2.log.success(`\u2705 Replaced lines ${startLine}-${endLine} in ${filePath}`);
3110
- return true;
3111
- } catch (e) {
3112
- tui2.log.error(`\u274C Error replacing line range: ${e.message}`);
3113
- return false;
3114
- }
3115
- }
3116
2725
  function handleSearchFile(pattern) {
3117
2726
  try {
3118
2727
  const entries = fg.sync(pattern, { dot: true });
@@ -3139,7 +2748,7 @@ function handleSearchCode(globPattern, query, isRegex = false) {
3139
2748
  for (const filePath of files) {
3140
2749
  if (totalMatches >= MAX_MATCHES) break;
3141
2750
  try {
3142
- const fullPath = path7.resolve(process.cwd(), filePath);
2751
+ const fullPath = path6.resolve(process.cwd(), filePath);
3143
2752
  const stats = fs6.statSync(fullPath);
3144
2753
  if (stats.size > MAX_FILE_SIZE_BYTES) continue;
3145
2754
  const content = fs6.readFileSync(fullPath, "utf-8");
@@ -3165,32 +2774,6 @@ ${results.join("\n")}`;
3165
2774
  return `Error searching code: ${e.message}`;
3166
2775
  }
3167
2776
  }
3168
- function startSmartReplace(filePath, newContent, targetContent, tui2) {
3169
- if (!fs6.existsSync(filePath)) {
3170
- tui2.log.error(`\u274C File not found for modification: ${filePath}`);
3171
- return false;
3172
- }
3173
- const currentFileContent = fs6.readFileSync(filePath, "utf-8");
3174
- const normalizedTarget = targetContent.replace(/\r\n/g, "\n");
3175
- const normalizedContent = currentFileContent.replace(/\r\n/g, "\n");
3176
- if (!normalizedContent.includes(normalizedTarget)) {
3177
- tui2.log.error(`\u274C Target content not found in ${filePath} (checked with normalized line endings). Modification aborted.`);
3178
- console.log(colors.dim("--- Target Content Expected ---"));
3179
- console.log(targetContent.substring(0, 200) + "...");
3180
- return false;
3181
- }
3182
- const occurrences = currentFileContent.split(targetContent).length - 1;
3183
- if (occurrences > 1) {
3184
- tui2.log.error(`\u274C Ambiguous target: Found ${occurrences} occurrences in ${filePath}. Modification aborted.`);
3185
- return false;
3186
- }
3187
- const BOM = "\uFEFF";
3188
- const updatedContent = currentFileContent.replace(targetContent, newContent);
3189
- const finalContent = updatedContent.startsWith(BOM) ? updatedContent : BOM + updatedContent;
3190
- fs6.writeFileSync(filePath, finalContent, { encoding: "utf-8" });
3191
- tui2.log.success(`\u2705 Smart Replace Applied: ${filePath}`);
3192
- return true;
3193
- }
3194
2777
  async function handleRunCommand(command) {
3195
2778
  const { spawn } = await import("child_process");
3196
2779
  try {
@@ -3238,458 +2821,95 @@ ${stdout}`);
3238
2821
  return `Error launching command: ${e.message}`;
3239
2822
  }
3240
2823
  }
3241
- function resolveAstGrepCommand() {
3242
- const isWin = process.platform === "win32";
3243
- const binName = isWin ? "sg.cmd" : "sg";
3244
- try {
3245
- const currentFile = fileURLToPath(import.meta.url);
3246
- let dir = path7.dirname(currentFile);
3247
- for (let i = 0; i < 5; i++) {
3248
- const candidate = path7.join(dir, "node_modules", ".bin", binName);
3249
- if (fs6.existsSync(candidate)) {
3250
- return `"${candidate}"`;
2824
+
2825
+ // src/core/workflow/subagent-manager.ts
2826
+ import crypto3 from "crypto";
2827
+ import fs7 from "fs";
2828
+ import path7 from "path";
2829
+ import { fork } from "child_process";
2830
+ import { fileURLToPath } from "url";
2831
+ var SubagentManager = class {
2832
+ subagents = /* @__PURE__ */ new Map();
2833
+ customTypes = /* @__PURE__ */ new Map();
2834
+ messageSeq = 0;
2835
+ registerSubagent(id, type, role, parentId) {
2836
+ this.subagents.set(id, { id, type, role, status: "running", parentId });
2837
+ }
2838
+ terminateSubagent(id, success = true, isCancelled = false) {
2839
+ const state = this.subagents.get(id);
2840
+ if (state) {
2841
+ if (state.status === "cancelled") {
2842
+ return;
2843
+ }
2844
+ if (isCancelled) {
2845
+ state.status = "cancelled";
2846
+ } else {
2847
+ state.status = success ? "completed" : "failed";
3251
2848
  }
3252
- const parent = path7.dirname(dir);
3253
- if (parent === dir) break;
3254
- dir = parent;
3255
2849
  }
3256
- } catch (e) {
3257
2850
  }
3258
- const cwdBin = path7.resolve(process.cwd(), "node_modules", ".bin", binName);
3259
- if (fs6.existsSync(cwdBin)) {
3260
- return `"${cwdBin}"`;
2851
+ isSubagentActive(id) {
2852
+ return this.subagents.get(id)?.status === "running";
2853
+ }
2854
+ hasSubagent(id) {
2855
+ return this.subagents.has(id);
3261
2856
  }
3262
- return "npx sg";
3263
- }
3264
- async function astGrepSearch(pattern, filePath, language, tui2) {
3265
- const { spawn } = await import("child_process");
3266
- try {
3267
- if (!fs6.existsSync(filePath)) {
3268
- return `\u274C File not found: ${filePath}`;
2857
+ getSubagentState(id) {
2858
+ return this.subagents.get(id);
2859
+ }
2860
+ updateSubagentSummary(id, summary) {
2861
+ const state = this.subagents.get(id);
2862
+ if (state) {
2863
+ state.summary = summary;
3269
2864
  }
3270
- const sgCmd = resolveAstGrepCommand();
3271
- const cmd = `${sgCmd} run -p "${pattern}" -l ${language} --json ${filePath}`;
3272
- tui2.log.info(`\u{1F50D} [AST-GREP] Searching: ${cmd}`);
3273
- return new Promise((resolve2) => {
3274
- const child = spawn(cmd, {
3275
- shell: true,
3276
- stdio: ["ignore", "pipe", "pipe"],
3277
- cwd: process.cwd(),
3278
- env: { ...process.env, NO_COLOR: "true" }
3279
- // Avoid ANSI codes in JSON output
3280
- });
3281
- let stdout = "";
3282
- let stderr = "";
3283
- child.stdout.on("data", (data) => stdout += data.toString());
3284
- child.stderr.on("data", (data) => stderr += data.toString());
3285
- child.on("close", (code) => {
3286
- if (code === 0 && stdout) {
3287
- resolve2(stdout);
3288
- } else if (code === 1 && !stderr) {
3289
- resolve2("No structural matches found.");
3290
- } else {
3291
- if (!stdout && !stderr) resolve2("No structural matches found.");
3292
- else {
3293
- tui2.log.error(`\u274C ast-grep search error (code ${code}): ${stderr}`);
3294
- resolve2(`Error executing ast-grep search: ${stderr || stdout}`);
3295
- }
3296
- }
3297
- });
3298
- child.on("error", (err) => {
3299
- resolve2(`Error executing ast-grep search: ${err.message}`);
3300
- });
3301
- });
3302
- } catch (e) {
3303
- tui2.log.error(`\u274C ast-grep search exception: ${e.message}`);
3304
- return `Error executing ast-grep search: ${e.message}`;
3305
2865
  }
3306
- }
3307
- async function astGrepRewrite(pattern, fix, filePath, language, tui2) {
3308
- const { spawn } = await import("child_process");
3309
- try {
3310
- if (!fs6.existsSync(filePath)) {
3311
- tui2.log.error(`\u274C File not found for AST modification: ${filePath}`);
3312
- return false;
2866
+ sendMessage(recipient, message) {
2867
+ const mailboxDir = path7.resolve(process.cwd(), ".shark", "mailbox", recipient);
2868
+ fs7.mkdirSync(mailboxDir, { recursive: true });
2869
+ const seq = (this.messageSeq++).toString().padStart(6, "0");
2870
+ const filePath = path7.join(mailboxDir, `${Date.now()}-${seq}-${crypto3.randomUUID()}.json`);
2871
+ fs7.writeFileSync(filePath, JSON.stringify({ message }), "utf-8");
2872
+ }
2873
+ retrieveMessages(id) {
2874
+ const mailboxDir = path7.resolve(process.cwd(), ".shark", "mailbox", id);
2875
+ if (!fs7.existsSync(mailboxDir)) {
2876
+ return [];
3313
2877
  }
3314
- const sgCmd = resolveAstGrepCommand();
3315
- const cmd = `${sgCmd} run -p "${pattern}" -r "${fix}" -l ${language} ${filePath} --update-all`;
3316
- tui2.log.info(`\u270F\uFE0F [AST-GREP] Rewriting: pattern="${pattern}" fix="${fix.substring(0, 50)}..."`);
3317
- return new Promise((resolve2) => {
3318
- const child = spawn(cmd, {
3319
- shell: true,
3320
- stdio: ["ignore", "pipe", "pipe"],
3321
- cwd: process.cwd()
3322
- });
3323
- let stderr = "";
3324
- child.stderr.on("data", (data) => stderr += data.toString());
3325
- child.on("close", (code) => {
3326
- if (code === 0) {
3327
- tui2.log.success(`\u2705 AST Rewrite applied to ${filePath}`);
3328
- resolve2(true);
3329
- } else {
3330
- tui2.log.error(`\u274C AST Rewrite failed (code ${code}): ${stderr}`);
3331
- resolve2(false);
2878
+ const files = fs7.readdirSync(mailboxDir);
2879
+ files.sort();
2880
+ const messages = [];
2881
+ for (const file of files) {
2882
+ const filePath = path7.join(mailboxDir, file);
2883
+ try {
2884
+ const content = fs7.readFileSync(filePath, "utf-8");
2885
+ const data = JSON.parse(content);
2886
+ if (data && typeof data.message === "string") {
2887
+ messages.push(data.message);
3332
2888
  }
3333
- });
3334
- child.on("error", (err) => {
3335
- tui2.log.error(`\u274C AST Rewrite spawn error: ${err.message}`);
3336
- resolve2(false);
3337
- });
3338
- });
3339
- } catch (e) {
3340
- tui2.log.error(`\u274C Unexpected error in astGrepRewrite: ${e.message}`);
3341
- return false;
2889
+ } catch (e) {
2890
+ }
2891
+ try {
2892
+ fs7.unlinkSync(filePath);
2893
+ } catch (e) {
2894
+ }
2895
+ }
2896
+ return messages;
3342
2897
  }
3343
- }
3344
- async function astListStructure(filePath) {
3345
- try {
3346
- const editor = CodeEditorFactory.getEditor(filePath);
3347
- if (!editor) {
3348
- return `[AST Error] File type not supported: ${filePath}. Use read_file instead.`;
2898
+ peekMessages(id) {
2899
+ const mailboxDir = path7.resolve(process.cwd(), ".shark", "mailbox", id);
2900
+ if (!fs7.existsSync(mailboxDir)) {
2901
+ return [];
3349
2902
  }
3350
- const structure = await editor.listStructure(filePath);
3351
- let output = `[AST Structure of ${filePath}]
3352
-
3353
- `;
3354
- if (structure.classes.length > 0) {
3355
- output += `CLASSES:
3356
- `;
3357
- structure.classes.forEach((cls) => {
3358
- output += ` - ${cls.name}`;
3359
- if (cls.extendsClass) output += ` extends ${cls.extendsClass}`;
3360
- if (cls.implementsInterfaces.length > 0) {
3361
- output += ` implements ${cls.implementsInterfaces.join(", ")}`;
3362
- }
3363
- output += `
3364
- `;
3365
- if (cls.decorators.length > 0) {
3366
- output += ` Decorators: ${cls.decorators.join(", ")}
3367
- `;
3368
- }
3369
- if (cls.properties.length > 0) {
3370
- output += ` Properties:
3371
- `;
3372
- cls.properties.forEach((prop) => {
3373
- output += ` - ${prop.visibility} ${prop.name}: ${prop.type}
3374
- `;
3375
- });
3376
- }
3377
- if (cls.methods.length > 0) {
3378
- output += ` Methods:
3379
- `;
3380
- cls.methods.forEach((method) => {
3381
- const params = method.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
3382
- output += ` - ${method.visibility} ${method.name}(${params}): ${method.returnType}
3383
- `;
3384
- });
3385
- }
3386
- output += `
3387
- `;
3388
- });
3389
- }
3390
- if (structure.interfaces.length > 0) {
3391
- output += `INTERFACES:
3392
- `;
3393
- structure.interfaces.forEach((iface) => {
3394
- output += ` - ${iface.name}
3395
- `;
3396
- iface.properties.forEach((prop) => {
3397
- output += ` ${prop.name}: ${prop.type}
3398
- `;
3399
- });
3400
- });
3401
- output += `
3402
- `;
3403
- }
3404
- if (structure.functions.length > 0) {
3405
- output += `FUNCTIONS:
3406
- `;
3407
- structure.functions.forEach((fn) => {
3408
- const params = fn.parameters.map((p) => `${p.name}: ${p.type}`).join(", ");
3409
- output += ` - ${fn.name}(${params}): ${fn.returnType}
3410
- `;
3411
- });
3412
- output += `
3413
- `;
3414
- }
3415
- if (structure.imports.length > 0) {
3416
- output += `IMPORTS:
3417
- `;
3418
- structure.imports.forEach((imp) => {
3419
- output += ` - from "${imp.modulePath}": ${imp.namedImports.join(", ")}
3420
- `;
3421
- });
3422
- }
3423
- return output;
3424
- } catch (error) {
3425
- return `[AST Error] ${error.message}`;
3426
- }
3427
- }
3428
- async function astAddMethod(filePath, className, methodCode) {
3429
- const editor = CodeEditorFactory.getEditor(filePath);
3430
- if (!editor) {
3431
- throw new Error(`No AST editor available for ${filePath}`);
3432
- }
3433
- return await editor.addMethod(filePath, className, methodCode);
3434
- }
3435
- async function astGetMethod(filePath, className, methodName) {
3436
- const editor = CodeEditorFactory.getEditor(filePath);
3437
- if (!editor) {
3438
- throw new Error(`No AST editor available for ${filePath}`);
3439
- }
3440
- const methodContent = await editor.getMethod(filePath, className, methodName);
3441
- if (!methodContent) {
3442
- return `Method "${methodName}" not found in class "${className}"`;
3443
- }
3444
- return methodContent;
3445
- }
3446
- async function astAddClass(filePath, className, extendsClass, implementsInterfaces) {
3447
- const editor = CodeEditorFactory.getEditor(filePath);
3448
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3449
- return await editor.addClass(filePath, className, { extendsClass, implementsInterfaces });
3450
- }
3451
- async function astAddProperty(filePath, className, propertyCode) {
3452
- const editor = CodeEditorFactory.getEditor(filePath);
3453
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3454
- return await editor.addProperty(filePath, className, propertyCode);
3455
- }
3456
- async function astGetProperty(filePath, className, propertyName) {
3457
- const editor = CodeEditorFactory.getEditor(filePath);
3458
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3459
- const propContent = await editor.getProperty(filePath, className, propertyName);
3460
- if (!propContent) {
3461
- return `Property "${propertyName}" not found in class "${className}"`;
3462
- }
3463
- return propContent;
3464
- }
3465
- async function astModifyProperty(filePath, className, propertyName, propertyCode) {
3466
- const editor = CodeEditorFactory.getEditor(filePath);
3467
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3468
- return await editor.modifyProperty(filePath, className, propertyName, propertyCode);
3469
- }
3470
- async function astRemoveProperty(filePath, className, propertyName) {
3471
- const editor = CodeEditorFactory.getEditor(filePath);
3472
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3473
- return await editor.removeProperty(filePath, className, propertyName);
3474
- }
3475
- async function astModifyMethod(filePath, className, methodName, newBody) {
3476
- const editor = CodeEditorFactory.getEditor(filePath);
3477
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3478
- return await editor.modifyMethod(filePath, className, methodName, newBody);
3479
- }
3480
- async function astRemoveMethod(filePath, className, methodName) {
3481
- const editor = CodeEditorFactory.getEditor(filePath);
3482
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3483
- return await editor.removeMethod(filePath, className, methodName);
3484
- }
3485
- async function astAddDecorator(filePath, className, decoratorCode) {
3486
- const editor = CodeEditorFactory.getEditor(filePath);
3487
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3488
- return await editor.addDecorator(filePath, className, decoratorCode);
3489
- }
3490
- async function astAddInterface(filePath, interfaceCode) {
3491
- const editor = CodeEditorFactory.getEditor(filePath);
3492
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3493
- return await editor.addInterface(filePath, interfaceCode);
3494
- }
3495
- async function astAddTypeAlias(filePath, typeCode) {
3496
- const editor = CodeEditorFactory.getEditor(filePath);
3497
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3498
- return await editor.addTypeAlias(filePath, typeCode);
3499
- }
3500
- async function astAddFunction(filePath, functionCode) {
3501
- const editor = CodeEditorFactory.getEditor(filePath);
3502
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3503
- return await editor.addFunction(filePath, functionCode);
3504
- }
3505
- async function astRemoveFunction(filePath, functionName) {
3506
- const editor = CodeEditorFactory.getEditor(filePath);
3507
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3508
- return await editor.removeFunction(filePath, functionName);
3509
- }
3510
- async function astAddImport(filePath, importStatement) {
3511
- const editor = CodeEditorFactory.getEditor(filePath);
3512
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3513
- return await editor.addImport(filePath, importStatement);
3514
- }
3515
- async function astRemoveImport(filePath, modulePath) {
3516
- const editor = CodeEditorFactory.getEditor(filePath);
3517
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3518
- return await editor.removeImport(filePath, modulePath);
3519
- }
3520
- async function astOrganizeImports(filePath) {
3521
- const editor = CodeEditorFactory.getEditor(filePath);
3522
- if (!editor) throw new Error(`AST editing not supported for: ${filePath}`);
3523
- return await editor.organizeImports(filePath);
3524
- }
3525
-
3526
- // src/core/workflow/skill-manager.ts
3527
- import fs7 from "fs/promises";
3528
- import path8 from "path";
3529
- import os2 from "os";
3530
- var SkillManager = class {
3531
- activeSkills = /* @__PURE__ */ new Set();
3532
- skillPrompts = /* @__PURE__ */ new Map();
3533
- async loadSkillFromFile(filePath) {
3534
- const content = await fs7.readFile(filePath, "utf-8");
3535
- const cleanContent = content.replace(/^---[\s\S]*?---\s*/, "");
3536
- return cleanContent;
3537
- }
3538
- async activateSkill(skillName) {
3539
- if (this.activeSkills.has(skillName)) {
3540
- return `Skill ${skillName} is already active.`;
3541
- }
3542
- this.reset();
3543
- const globalPath = path8.join(os2.homedir(), ".shark", "skills", skillName, "SKILL.md");
3544
- const localPath = path8.join(process.cwd(), ".agents", "skills", skillName, "SKILL.md");
3545
- let skillPath = "";
3546
- try {
3547
- await fs7.access(localPath);
3548
- skillPath = localPath;
3549
- } catch {
3550
- try {
3551
- await fs7.access(globalPath);
3552
- skillPath = globalPath;
3553
- } catch {
3554
- throw new Error(`Skill '${skillName}' not found globally or locally.`);
3555
- }
3556
- }
3557
- const prompt = await this.loadSkillFromFile(skillPath);
3558
- this.activeSkills.add(skillName);
3559
- this.skillPrompts.set(skillName, prompt);
3560
- return prompt;
3561
- }
3562
- getSystemInstructionExtension() {
3563
- if (this.activeSkills.size === 0) return "";
3564
- let extension = "\n\n<EXTREMELY_IMPORTANT>\n";
3565
- for (const [name, prompt] of this.skillPrompts.entries()) {
3566
- extension += `
3567
- --- ACTIVE SKILL: ${name} ---
3568
- ${prompt}
3569
- `;
3570
- }
3571
- extension += "\n</EXTREMELY_IMPORTANT>\n";
3572
- return extension;
3573
- }
3574
- async listAvailableSkills() {
3575
- const globalSkillsDir = path8.join(os2.homedir(), ".shark", "skills");
3576
- const localSkillsDir = path8.join(process.cwd(), ".agents", "skills");
3577
- const skillNames = /* @__PURE__ */ new Set();
3578
- const readDir = async (dir) => {
3579
- try {
3580
- const entries = await fs7.readdir(dir, { withFileTypes: true });
3581
- for (const entry of entries) {
3582
- if (entry.isDirectory()) {
3583
- const skillMdPath = path8.join(dir, entry.name, "SKILL.md");
3584
- try {
3585
- await fs7.access(skillMdPath);
3586
- skillNames.add(entry.name);
3587
- } catch {
3588
- }
3589
- }
3590
- }
3591
- } catch {
3592
- }
3593
- };
3594
- await readDir(globalSkillsDir);
3595
- await readDir(localSkillsDir);
3596
- return Array.from(skillNames).sort();
3597
- }
3598
- reset() {
3599
- this.activeSkills.clear();
3600
- this.skillPrompts.clear();
3601
- }
3602
- };
3603
- var skillManager = new SkillManager();
3604
-
3605
- // src/core/workflow/subagent-manager.ts
3606
- import crypto2 from "crypto";
3607
- import fs8 from "fs";
3608
- import path9 from "path";
3609
- import { fork } from "child_process";
3610
- import { fileURLToPath as fileURLToPath2 } from "url";
3611
- var SubagentManager = class {
3612
- subagents = /* @__PURE__ */ new Map();
3613
- customTypes = /* @__PURE__ */ new Map();
3614
- messageSeq = 0;
3615
- registerSubagent(id, type, role, parentId) {
3616
- this.subagents.set(id, { id, type, role, status: "running", parentId });
3617
- }
3618
- terminateSubagent(id, success = true, isCancelled = false) {
3619
- const state = this.subagents.get(id);
3620
- if (state) {
3621
- if (state.status === "cancelled") {
3622
- return;
3623
- }
3624
- if (isCancelled) {
3625
- state.status = "cancelled";
3626
- } else {
3627
- state.status = success ? "completed" : "failed";
3628
- }
3629
- }
3630
- }
3631
- isSubagentActive(id) {
3632
- return this.subagents.get(id)?.status === "running";
3633
- }
3634
- hasSubagent(id) {
3635
- return this.subagents.has(id);
3636
- }
3637
- getSubagentState(id) {
3638
- return this.subagents.get(id);
3639
- }
3640
- updateSubagentSummary(id, summary) {
3641
- const state = this.subagents.get(id);
3642
- if (state) {
3643
- state.summary = summary;
3644
- }
3645
- }
3646
- sendMessage(recipient, message) {
3647
- const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", recipient);
3648
- fs8.mkdirSync(mailboxDir, { recursive: true });
3649
- const seq = (this.messageSeq++).toString().padStart(6, "0");
3650
- const filePath = path9.join(mailboxDir, `${Date.now()}-${seq}-${crypto2.randomUUID()}.json`);
3651
- fs8.writeFileSync(filePath, JSON.stringify({ message }), "utf-8");
3652
- }
3653
- retrieveMessages(id) {
3654
- const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", id);
3655
- if (!fs8.existsSync(mailboxDir)) {
3656
- return [];
3657
- }
3658
- const files = fs8.readdirSync(mailboxDir);
3659
- files.sort();
3660
- const messages = [];
3661
- for (const file of files) {
3662
- const filePath = path9.join(mailboxDir, file);
3663
- try {
3664
- const content = fs8.readFileSync(filePath, "utf-8");
3665
- const data = JSON.parse(content);
3666
- if (data && typeof data.message === "string") {
3667
- messages.push(data.message);
3668
- }
3669
- } catch (e) {
3670
- }
3671
- try {
3672
- fs8.unlinkSync(filePath);
3673
- } catch (e) {
3674
- }
3675
- }
3676
- return messages;
3677
- }
3678
- peekMessages(id) {
3679
- const mailboxDir = path9.resolve(process.cwd(), ".shark", "mailbox", id);
3680
- if (!fs8.existsSync(mailboxDir)) {
3681
- return [];
3682
- }
3683
- const files = fs8.readdirSync(mailboxDir);
3684
- files.sort();
3685
- const messages = [];
3686
- for (const file of files) {
3687
- const filePath = path9.join(mailboxDir, file);
3688
- try {
3689
- const content = fs8.readFileSync(filePath, "utf-8");
3690
- const data = JSON.parse(content);
3691
- if (data && typeof data.message === "string") {
3692
- messages.push(data.message);
2903
+ const files = fs7.readdirSync(mailboxDir);
2904
+ files.sort();
2905
+ const messages = [];
2906
+ for (const file of files) {
2907
+ const filePath = path7.join(mailboxDir, file);
2908
+ try {
2909
+ const content = fs7.readFileSync(filePath, "utf-8");
2910
+ const data = JSON.parse(content);
2911
+ if (data && typeof data.message === "string") {
2912
+ messages.push(data.message);
3693
2913
  }
3694
2914
  } catch (e) {
3695
2915
  }
@@ -3712,12 +2932,12 @@ var SubagentManager = class {
3712
2932
  throw new Error("Invalid subagent ID format");
3713
2933
  }
3714
2934
  const projectRoot = process.cwd();
3715
- const logFile = path9.resolve(projectRoot, "_sharkrc", "history", `subagent-${id}-console.log`);
3716
- if (!fs8.existsSync(logFile)) {
2935
+ const logFile = path7.resolve(projectRoot, "_sharkrc", "history", `subagent-${id}-console.log`);
2936
+ if (!fs7.existsSync(logFile)) {
3717
2937
  return "No console logs found for this subagent.";
3718
2938
  }
3719
2939
  try {
3720
- let content = fs8.readFileSync(logFile, "utf-8");
2940
+ let content = fs7.readFileSync(logFile, "utf-8");
3721
2941
  if (content.endsWith("\n")) {
3722
2942
  content = content.slice(0, -1);
3723
2943
  }
@@ -3760,32 +2980,32 @@ var SubagentManager = class {
3760
2980
  async invokeSubagents(subagents, parentId, parentQueue) {
3761
2981
  const invoked = [];
3762
2982
  for (const sub of subagents) {
3763
- const id = `subagent-${crypto2.randomUUID()}`;
2983
+ const id = `subagent-${crypto3.randomUUID()}`;
3764
2984
  this.registerSubagent(id, sub.TypeName, sub.Role, parentId);
3765
2985
  const promise = (async () => {
3766
2986
  try {
3767
2987
  const projectRoot = process.cwd();
3768
- const __filename2 = fileURLToPath2(import.meta.url);
3769
- const __dirname2 = path9.dirname(__filename2);
3770
- let packageRoot = __dirname2;
2988
+ const __filename3 = fileURLToPath(import.meta.url);
2989
+ const __dirname3 = path7.dirname(__filename3);
2990
+ let packageRoot = __dirname3;
3771
2991
  while (true) {
3772
- const pkgPath = path9.join(packageRoot, "package.json");
3773
- if (fs8.existsSync(pkgPath)) {
2992
+ const pkgPath = path7.join(packageRoot, "package.json");
2993
+ if (fs7.existsSync(pkgPath)) {
3774
2994
  try {
3775
- const pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
2995
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
3776
2996
  if (pkg && pkg.name === "shark-ai") {
3777
2997
  break;
3778
2998
  }
3779
2999
  } catch (e) {
3780
3000
  }
3781
3001
  }
3782
- const parent = path9.dirname(packageRoot);
3002
+ const parent = path7.dirname(packageRoot);
3783
3003
  if (parent === packageRoot) {
3784
3004
  break;
3785
3005
  }
3786
3006
  packageRoot = parent;
3787
3007
  }
3788
- const pathToSharkJs = path9.resolve(packageRoot, "dist", "bin", "shark.js");
3008
+ const pathToSharkJs = path7.resolve(packageRoot, "dist", "bin", "shark.js");
3789
3009
  const customType = this.customTypes.get(sub.TypeName);
3790
3010
  let customContext = `Voc\xEA est\xE1 executando em modo SUBAGENTE.
3791
3011
  `;
@@ -3818,10 +3038,10 @@ var SubagentManager = class {
3818
3038
  if (state2) {
3819
3039
  state2.childProcess = child;
3820
3040
  }
3821
- const historyDir = path9.resolve(projectRoot, "_sharkrc", "history");
3822
- fs8.mkdirSync(historyDir, { recursive: true });
3823
- const consoleLogFile = path9.join(historyDir, `subagent-${id}-console.log`);
3824
- const logStream = fs8.createWriteStream(consoleLogFile, { flags: "a" });
3041
+ const historyDir = path7.resolve(projectRoot, "_sharkrc", "history");
3042
+ fs7.mkdirSync(historyDir, { recursive: true });
3043
+ const consoleLogFile = path7.join(historyDir, `subagent-${id}-console.log`);
3044
+ const logStream = fs7.createWriteStream(consoleLogFile, { flags: "a" });
3825
3045
  if (child.stdout) {
3826
3046
  child.stdout.pipe(logStream);
3827
3047
  }
@@ -3844,8 +3064,8 @@ Subagent ${sub.Role} (${id}) cancelled.`);
3844
3064
  } else if (!success) {
3845
3065
  this.updateSubagentSummary(id, "Failed");
3846
3066
  const fallbackMsg = `[Subagent Notification] Subagent ${sub.Role} (${id}) has finished with status: FAILED. Summary: Subagent process exited with code ${exitCode}`;
3847
- const mailboxDir = path9.resolve(projectRoot, ".shark", "mailbox", parentId);
3848
- const hasMessages = fs8.existsSync(mailboxDir) && fs8.readdirSync(mailboxDir).length > 0;
3067
+ const mailboxDir = path7.resolve(projectRoot, ".shark", "mailbox", parentId);
3068
+ const hasMessages = fs7.existsSync(mailboxDir) && fs7.readdirSync(mailboxDir).length > 0;
3849
3069
  if (!hasMessages) {
3850
3070
  this.sendMessage(parentId, fallbackMsg);
3851
3071
  }
@@ -3947,22 +3167,30 @@ var MessageQueue = class {
3947
3167
  };
3948
3168
 
3949
3169
  // src/core/agents/developer-agent.ts
3170
+ var activeOnCommandHandler = void 0;
3950
3171
  async function promptUser(message, initialValue, placeholder, prefix = "") {
3951
3172
  let userReply = await tui.text({ message: `${prefix}${message}`, initialValue, placeholder });
3952
- while (userReply === "/skills") {
3953
- const availableSkills = await skillManager.listAvailableSkills();
3954
- const options = availableSkills.map((name) => ({ value: name, label: name }));
3955
- if (options.length === 0) {
3956
- tui.log.warning("Nenhuma skill encontrada. Execute `shark super` para instalar as skills.");
3957
- } else {
3958
- const selectedSkill = await tui.select({
3959
- message: "Selecione a Skill do Superpowers para ativar:",
3960
- options
3961
- });
3962
- if (!tui.isCancel(selectedSkill)) {
3963
- await skillManager.activateSkill(selectedSkill);
3964
- tui.log.success(`\u2714 Skill '${selectedSkill}' ativada com sucesso!`);
3173
+ while (userReply && userReply.startsWith("/")) {
3174
+ let handled = false;
3175
+ if (activeOnCommandHandler) {
3176
+ handled = await activeOnCommandHandler(userReply);
3177
+ }
3178
+ if (!handled && userReply === "/skills") {
3179
+ const availableSkills = await skillManager.listAvailableSkills();
3180
+ const options = availableSkills.map((name) => ({ value: name, label: name }));
3181
+ if (options.length === 0) {
3182
+ tui.log.warning("Nenhuma skill encontrada. Execute `shark super` para instalar as skills.");
3183
+ } else {
3184
+ const selectedSkill = await tui.select({
3185
+ message: "Selecione a Skill do Superpowers para ativar:",
3186
+ options
3187
+ });
3188
+ if (!tui.isCancel(selectedSkill)) {
3189
+ await skillManager.activateSkill(selectedSkill);
3190
+ tui.log.success(`\u2714 Skill '${selectedSkill}' ativada com sucesso!`);
3191
+ }
3965
3192
  }
3193
+ handled = true;
3966
3194
  }
3967
3195
  userReply = await tui.text({
3968
3196
  message: `${prefix}${message}`,
@@ -3976,7 +3204,7 @@ async function waitForInputOrNotification(queue, promptMessage = "Your answer:",
3976
3204
  let cancelled = false;
3977
3205
  let resolvePromptPromise = null;
3978
3206
  let timerId = null;
3979
- const promises = [];
3207
+ const promises2 = [];
3980
3208
  if (!isAuto) {
3981
3209
  const promptPromise = new Promise((resolve2) => {
3982
3210
  resolvePromptPromise = resolve2;
@@ -3995,10 +3223,10 @@ async function waitForInputOrNotification(queue, promptMessage = "Your answer:",
3995
3223
  }
3996
3224
  };
3997
3225
  runPrompt();
3998
- promises.push(promptPromise);
3226
+ promises2.push(promptPromise);
3999
3227
  }
4000
3228
  const queuePromise = queue.next();
4001
- promises.push(queuePromise);
3229
+ promises2.push(queuePromise);
4002
3230
  if (timeoutMs !== void 0 && timeoutMs !== null) {
4003
3231
  const timeoutPromise = new Promise((resolve2) => {
4004
3232
  timerId = setTimeout(() => {
@@ -4012,9 +3240,9 @@ async function waitForInputOrNotification(queue, promptMessage = "Your answer:",
4012
3240
  });
4013
3241
  }, timeoutMs);
4014
3242
  });
4015
- promises.push(timeoutPromise);
3243
+ promises2.push(timeoutPromise);
4016
3244
  }
4017
- const winner = await Promise.race(promises);
3245
+ const winner = await Promise.race(promises2);
4018
3246
  if (timerId) {
4019
3247
  clearTimeout(timerId);
4020
3248
  }
@@ -4071,11 +3299,11 @@ async function interactiveDeveloperAgent(options = {}) {
4071
3299
  message: (msg) => tui.log.message(`${subagentPrefix}${msg}`)
4072
3300
  };
4073
3301
  let contextContent = "";
4074
- const defaultContextPath = path10.resolve(projectRoot, "_sharkrc", "project-context.md");
4075
- const specificContextPath = options.context ? path10.resolve(projectRoot, options.context) : defaultContextPath;
4076
- if (fs9.existsSync(specificContextPath)) {
3302
+ const defaultContextPath = path8.resolve(projectRoot, "_sharkrc", "project-context.md");
3303
+ const specificContextPath = options.context ? path8.resolve(projectRoot, options.context) : defaultContextPath;
3304
+ if (fs8.existsSync(specificContextPath)) {
4077
3305
  try {
4078
- contextContent = fs9.readFileSync(specificContextPath, "utf-8");
3306
+ contextContent = fs8.readFileSync(specificContextPath, "utf-8");
4079
3307
  } catch (e) {
4080
3308
  log.warning(`Failed to read context file: ${e}`);
4081
3309
  }
@@ -4121,6 +3349,41 @@ Your goal is to address the user's request:
4121
3349
  let finalSummary = "";
4122
3350
  const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
4123
3351
  const anchorManager = new AnchorStateManager();
3352
+ const onCommandHandler = async (command) => {
3353
+ if (command === "/compact") {
3354
+ tui.log.info("\u{1F988} Compactando mem\xF3ria de forma manual...");
3355
+ const memboxManager = new MemboxManager();
3356
+ const existingConversationId = await conversationManager.getConversationId(conversationKey);
3357
+ if (existingConversationId) {
3358
+ try {
3359
+ const rawHistory = await HistoryManager.getRawHistory(existingConversationId);
3360
+ const provider = ProviderResolver.getProvider("developer_agent");
3361
+ const truncatedHistory = await memboxManager.compactHistory(rawHistory, provider, existingConversationId, true);
3362
+ await HistoryManager.saveRawHistory(existingConversationId, truncatedHistory);
3363
+ tui.log.success("\u2714 Mem\xF3ria compactada e truncada com sucesso!");
3364
+ } catch (error) {
3365
+ tui.log.error(`Erro durante a compacta\xE7\xE3o: ${error.message}`);
3366
+ }
3367
+ } else {
3368
+ tui.log.warning("Nenhuma conversa\xE7\xE3o ativa para compactar.");
3369
+ }
3370
+ return true;
3371
+ }
3372
+ if (command === "/context") {
3373
+ const existingConversationId = await conversationManager.getConversationId(conversationKey);
3374
+ if (existingConversationId) {
3375
+ const rawHistory = await HistoryManager.getRawHistory(existingConversationId);
3376
+ const totalTokensEst = Math.ceil(JSON.stringify(rawHistory).length / 4);
3377
+ tui.log.info(`\u{1F4CA} Hist\xF3rico ativo: ${rawHistory.length} mensagens`);
3378
+ tui.log.info(`\u{1F4CA} Tamanho estimado: ${totalTokensEst} / 8000 tokens (${Math.round(totalTokensEst / 8e3 * 100)}% do limite)`);
3379
+ } else {
3380
+ tui.log.warning("Nenhuma conversa\xE7\xE3o ativa para analisar.");
3381
+ }
3382
+ return true;
3383
+ }
3384
+ return false;
3385
+ };
3386
+ activeOnCommandHandler = onCommandHandler;
4124
3387
  const spinner = tui.spinner();
4125
3388
  const handleCleanupSignal = (exitCode) => {
4126
3389
  const currentId = options.taskId || "parent";
@@ -4171,17 +3434,35 @@ ${mailboxMessages.map((m) => `- ${m}`).join("\n")}
4171
3434
  `;
4172
3435
  currentTurnPrompt += panel;
4173
3436
  }
4174
- const promptToSend = currentTurnPrompt + skillManager.getSystemInstructionExtension();
3437
+ const promptToSend = currentTurnPrompt;
4175
3438
  try {
4176
3439
  const activeSubagents = subagentManager.getActiveSubagents();
4177
3440
  const activeCount = activeSubagents.length;
4178
3441
  const spinnerText = activeCount > 0 ? `\u{1F988} Shark Dev working... (Active subagents: ${activeCount})` : "\u{1F988} Shark Dev working...";
4179
3442
  spinner.start(spinnerText);
4180
3443
  const existingConversationId = await conversationManager.getConversationId(conversationKey);
3444
+ if (existingConversationId) {
3445
+ const rawHistory = await HistoryManager.getRawHistory(existingConversationId);
3446
+ const totalTokensEst = Math.ceil(JSON.stringify(rawHistory).length / 4);
3447
+ const compactionTokenLimit = ConfigManager.getInstance().getConfig().memory?.compactionTokenLimit ?? 8e3;
3448
+ if (totalTokensEst >= compactionTokenLimit) {
3449
+ try {
3450
+ log.info("\u{1F988} Limite de context/tokens atingido. Iniciando compacta\xE7\xE3o autom\xE1tica...");
3451
+ const memboxManager = new MemboxManager();
3452
+ const providerInstance = ProviderResolver.getProvider("developer_agent");
3453
+ const truncatedHistory = await memboxManager.compactHistory(rawHistory, providerInstance, existingConversationId);
3454
+ await HistoryManager.saveRawHistory(existingConversationId, truncatedHistory);
3455
+ log.success("\u2714 Compacta\xE7\xE3o autom\xE1tica conclu\xEDda!");
3456
+ } catch (error) {
3457
+ log.error(`\u26A0\uFE0F Falha na compacta\xE7\xE3o autom\xE1tica: ${error.message}. Prosseguindo sem compacta\xE7\xE3o.`);
3458
+ }
3459
+ }
3460
+ }
4181
3461
  const provider = ProviderResolver.getProvider("developer_agent");
4182
3462
  const response = await provider.streamChat(promptToSend, {
4183
3463
  conversationId: existingConversationId,
4184
3464
  agentType: "developer_agent",
3465
+ searchQuery: nextPrompt,
4185
3466
  onChunk: () => {
4186
3467
  }
4187
3468
  });
@@ -4351,12 +3632,12 @@ ${content}`;
4351
3632
  }
4352
3633
  if (approved) {
4353
3634
  try {
4354
- const resolvedPath = path10.resolve(projectRoot, filePath);
4355
- const dir = path10.dirname(resolvedPath);
4356
- if (!fs9.existsSync(dir)) {
4357
- fs9.mkdirSync(dir, { recursive: true });
3635
+ const resolvedPath = path8.resolve(projectRoot, filePath);
3636
+ const dir = path8.dirname(resolvedPath);
3637
+ if (!fs8.existsSync(dir)) {
3638
+ fs8.mkdirSync(dir, { recursive: true });
4358
3639
  }
4359
- fs9.writeFileSync(resolvedPath, action.content || "", "utf-8");
3640
+ fs8.writeFileSync(resolvedPath, action.content || "", "utf-8");
4360
3641
  resultMsg = `[Action create_file(${filePath}) Success]`;
4361
3642
  } catch (e) {
4362
3643
  resultMsg = `[Action create_file(${filePath}) Failed]: ${e.message}`;
@@ -4373,9 +3654,9 @@ ${content}`;
4373
3654
  }
4374
3655
  if (approved) {
4375
3656
  try {
4376
- const resolvedPath = path10.resolve(projectRoot, filePath);
4377
- if (fs9.existsSync(resolvedPath)) {
4378
- fs9.rmSync(resolvedPath, { force: true });
3657
+ const resolvedPath = path8.resolve(projectRoot, filePath);
3658
+ if (fs8.existsSync(resolvedPath)) {
3659
+ fs8.rmSync(resolvedPath, { force: true });
4379
3660
  }
4380
3661
  resultMsg = `[Action delete_file(${filePath}) Success]`;
4381
3662
  } catch (e) {
@@ -4704,6 +3985,7 @@ ${detailedContent}`
4704
3985
  log.success("\u2705 Task Scope Completed");
4705
3986
  return finalResult;
4706
3987
  } finally {
3988
+ activeOnCommandHandler = void 0;
4707
3989
  process.off("SIGINT", sigIntHandler);
4708
3990
  process.off("SIGTERM", sigTermHandler);
4709
3991
  const currentId = options.taskId || "parent";
@@ -4718,11 +4000,30 @@ ${detailedContent}`
4718
4000
  }
4719
4001
 
4720
4002
  // src/commands/dev.ts
4721
- 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) => {
4003
+ import { exec as exec2 } from "child_process";
4004
+ import * as path9 from "path";
4005
+ import * as fs9 from "fs";
4006
+ import { fileURLToPath as fileURLToPath2 } from "url";
4007
+ var __filename2 = fileURLToPath2(import.meta.url);
4008
+ var __dirname2 = path9.dirname(__filename2);
4009
+ 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").option("--graph", "Enable real-time memory graph visualization", false).action(async (options) => {
4722
4010
  if (options.exportSchema) {
4723
4011
  console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
4724
4012
  return;
4725
4013
  }
4014
+ if (options.graph) {
4015
+ try {
4016
+ const runDir = path9.join(process.cwd(), ".shark", "membox");
4017
+ const configPath = path9.join(runDir, "graph-server.json");
4018
+ if (fs9.existsSync(configPath)) {
4019
+ fs9.unlinkSync(configPath);
4020
+ }
4021
+ } catch {
4022
+ }
4023
+ const cliPath = path9.resolve(__dirname2, "..", "bin", "shark.js");
4024
+ const child = exec2(`node "${cliPath}" graph`);
4025
+ child.unref();
4026
+ }
4726
4027
  try {
4727
4028
  const result = await interactiveDeveloperAgent({
4728
4029
  taskInstruction: options.task,
@@ -4740,996 +4041,534 @@ var devCommand = new Command2("dev").description("Starts the Shark Developer Age
4740
4041
  }
4741
4042
  });
4742
4043
 
4743
- // src/commands/legacy.ts
4044
+ // src/commands/export-schema.ts
4744
4045
  import { Command as Command3 } from "commander";
4046
+ var exportSchemaCommand = new Command3("export-schema").description("Outputs the agent response JSON Schema").action(() => {
4047
+ console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
4048
+ });
4049
+
4050
+ // src/commands/export-prompt.ts
4051
+ import { Command as Command4 } from "commander";
4052
+ var exportPromptCommand = new Command4("export-prompt").description("Outputs the unified agent system prompt").action(() => {
4053
+ console.log(UNIFIED_SYSTEM_PROMPT);
4054
+ });
4745
4055
 
4746
- // src/core/agents/legacy-developer-agent.ts
4747
- import fs10 from "fs";
4748
- import path11 from "path";
4749
- var AGENT_TYPE = "developer_agent";
4750
- async function validateTypeScript(filePath) {
4056
+ // src/commands/super.ts
4057
+ import { Command as Command5 } from "commander";
4058
+ import { fileURLToPath as fileURLToPath3 } from "url";
4059
+ import path10 from "path";
4060
+ import os3 from "os";
4061
+ import fs10 from "fs/promises";
4062
+ var superCommandAction = async (options = {}) => {
4063
+ const __filename3 = fileURLToPath3(import.meta.url);
4064
+ const __dirname3 = path10.dirname(__filename3);
4065
+ const packageRoot = path10.resolve(__dirname3, "../../");
4066
+ const internalSkillsPath = path10.join(packageRoot, "skills");
4067
+ const targetPath = options.local ? path10.join(process.cwd(), ".agents", "skills") : path10.join(os3.homedir(), ".shark", "skills");
4751
4068
  try {
4752
- const result = await handleRunCommand(`npx tsc --noEmit --skipLibCheck ${filePath}`);
4753
- if (result.trim() === "" || !result.includes("error TS")) {
4754
- return { valid: true };
4755
- }
4756
- return { valid: false, error: result };
4757
- } catch (e) {
4758
- return { valid: false, error: e.message || "TypeScript validation failed" };
4759
- }
4760
- }
4761
- function getAgentId(overrideId) {
4762
- if (overrideId) return overrideId;
4763
- const config = ConfigManager.getInstance().getConfig();
4764
- if (config.agents?.dev) return config.agents.dev;
4765
- if (process.env.STACKSPOT_DEV_AGENT_ID) return process.env.STACKSPOT_DEV_AGENT_ID;
4766
- return "01KEQCGJ65YENRA4QBXVN1YFFX";
4767
- }
4768
- async function interactiveDeveloperAgent2(options = {}) {
4769
- FileLogger.init();
4770
- const agentId = getAgentId();
4771
- if (agentId === "PENDING_CONFIGURATION") {
4772
- tui.log.error("\u274C STACKSPOT_DEV_AGENT_ID not configured in .env");
4773
- return { success: false, summary: "Missing configuration." };
4774
- }
4775
- const projectRoot = process.cwd();
4776
- let contextContent = "";
4777
- const defaultContextPath = path11.resolve(projectRoot, "_sharkrc", "project-context.md");
4778
- const specificContextPath = options.context ? path11.resolve(projectRoot, options.context) : defaultContextPath;
4779
- if (fs10.existsSync(specificContextPath)) {
4780
4069
  try {
4781
- contextContent = fs10.readFileSync(specificContextPath, "utf-8");
4782
- } catch (e) {
4783
- tui.log.warning(`Failed to read context file: ${e}`);
4070
+ await fs10.rm(targetPath, { recursive: true, force: true });
4071
+ } catch {
4784
4072
  }
4073
+ await fs10.mkdir(targetPath, { recursive: true });
4074
+ await fs10.cp(internalSkillsPath, targetPath, { recursive: true, force: true });
4075
+ console.log(`\u{1F680} Superpowers skills installed successfully to ${targetPath}`);
4076
+ } catch (error) {
4077
+ console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
4078
+ process.exit(1);
4785
4079
  }
4786
- const currentTask = options.taskInstruction || "Analyze the project and fix pending issues.";
4787
- let basePrompt = ``;
4788
- if (contextContent) {
4789
- basePrompt += `
4080
+ };
4081
+ var superCommand = new Command5("super").description("Install Superpowers skills globally or locally").option("-l, --local", "Install skills locally in the current project under .agents/skills").action(superCommandAction);
4790
4082
 
4791
- --- PROJECT CONTEXT ---
4792
- ${contextContent}
4793
- -----------------------
4794
- `;
4795
- }
4796
- if (options.history) {
4797
- basePrompt += `
4798
-
4799
- --- PREVIOUS EXECUTION SUMMARY ---
4800
- ${options.history}
4801
- ----------------------------------
4802
- `;
4803
- }
4804
- basePrompt += `
4083
+ // src/commands/graph.ts
4084
+ import { Command as Command6, Argument } from "commander";
4085
+ import * as http from "http";
4086
+ import * as fs11 from "fs";
4087
+ import * as path11 from "path";
4088
+ import { exec as exec3 } from "child_process";
4805
4089
 
4806
- \u{1F7E2} EXECUTION MODE
4090
+ // src/commands/graph-html.ts
4091
+ var GRAPH_HTML_TEMPLATE = `
4092
+ <!DOCTYPE html>
4093
+ <html lang="en">
4094
+ <head>
4095
+ <meta charset="UTF-8">
4096
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
4097
+ <title>Shark Dev Memory Graph</title>
4098
+ <script src="https://cdn.tailwindcss.com"></script>
4099
+ <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
4100
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=JetBrains+Mono&display=swap" rel="stylesheet">
4101
+ <style>
4102
+ body { font-family: 'Inter', sans-serif; background-color: #0b0f19; color: #f3f4f6; }
4103
+ .glass { background: rgba(15, 23, 42, 0.75); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.08); }
4104
+ </style>
4105
+ </head>
4106
+ <body class="h-screen w-screen overflow-hidden flex flex-col">
4107
+ <!-- Header -->
4108
+ <header class="h-16 px-6 border-b border-white/5 flex items-center justify-between glass z-10">
4109
+ <div class="flex items-center gap-3">
4110
+ <span class="text-2xl">\u{1F988}</span>
4111
+ <h1 class="text-lg font-semibold tracking-wide">Shark Memory Graph</h1>
4112
+ </div>
4113
+ <div class="flex items-center gap-4">
4114
+ <input type="text" id="searchInput" placeholder="Search topics/events..." class="px-4 py-2 bg-white/5 border border-white/10 rounded-lg text-sm focus:outline-none focus:border-blue-500/50 w-64">
4115
+ <div class="flex bg-white/5 rounded-lg p-0.5 border border-white/5">
4116
+ <button id="btnBoxes" class="px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white transition-all">Boxes Network</button>
4117
+ <button id="btnTimeline" class="px-4 py-1.5 rounded-md text-xs font-medium text-gray-400 hover:text-white transition-all">Event Flow</button>
4118
+ </div>
4119
+ </div>
4120
+ </header>
4807
4121
 
4808
- You are a highly skilled Developer Agent.
4809
- \u{1F449} **CURRENT TASK**: "${currentTask}"
4122
+ <!-- Main Content -->
4123
+ <div class="flex-1 flex relative">
4124
+ <!-- Canvas -->
4125
+ <div id="network" class="flex-1 h-full"></div>
4810
4126
 
4811
- Your goal is to COMPLETE this specific task and then STOP.
4812
- 1. Implement the necessary changes.
4813
- 2. Verify (compile/test).
4814
- 3. **MANDATORY**: When you are confident the task is done, output a final message starting with "TASK_COMPLETED:" followed by a brief technical summary of what you did.
4815
- `;
4816
- let nextPrompt = basePrompt;
4817
- let keepGoing = true;
4818
- const spinner = tui.spinner();
4819
- let finalSummary = "";
4820
- let isTaskCompleted = false;
4821
- const conversationKey = options.taskId ? `dev_agent_${options.taskId}` : `dev_agent_${Date.now()}`;
4822
- let autoApprovals = {
4823
- files: false,
4824
- commands: false
4825
- };
4826
- while (keepGoing) {
4827
- try {
4828
- spinner.start("\u{1F988} Shark Dev working...");
4829
- const lastResponse = await callDevAgentApi(nextPrompt, (chunk) => {
4830
- }, conversationKey);
4831
- spinner.stop("Response received");
4832
- if (lastResponse) {
4833
- const response = lastResponse;
4834
- const actions = response.actions || [];
4835
- if (response.message && response.message.includes("TASK_COMPLETED:")) {
4836
- isTaskCompleted = true;
4837
- finalSummary = response.message.split("TASK_COMPLETED:")[1].trim();
4838
- keepGoing = false;
4839
- }
4840
- if (response.message && response.message.includes("TASK_FAILED:")) {
4841
- const failureReason = response.message.split("TASK_FAILED:")[1].trim();
4842
- tui.log.error(`\u274C Agent reported task failure: ${failureReason}`);
4843
- return { success: false, summary: failureReason };
4844
- }
4845
- if (actions.length === 0 && response.message && !isTaskCompleted) {
4846
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
4847
- console.log(response.message);
4848
- const userReply = await tui.text({ message: "Your answer:" });
4849
- if (tui.isCancel(userReply)) {
4850
- keepGoing = false;
4851
- break;
4852
- }
4853
- nextPrompt = userReply;
4854
- }
4855
- let executionResults = "";
4856
- let waitingForUser = false;
4857
- for (const action of actions) {
4858
- if (action.type === "talk_with_user") {
4859
- tui.log.info(colors.primary("\u{1F916} Shark Dev:"));
4860
- console.log(action.content);
4861
- if (!isTaskCompleted) waitingForUser = true;
4862
- } else if (action.type === "list_files") {
4863
- tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
4864
- const result = handleListFiles(action.path || ".");
4865
- executionResults += `[Action list_files(${action.path}) Result]:
4866
- ${result}
4127
+ <!-- Sidebar -->
4128
+ <aside id="sidebar" class="w-96 glass border-l border-white/5 flex flex-col translate-x-full transition-transform duration-300 absolute right-0 top-0 bottom-0 z-20">
4129
+ <div class="p-6 border-b border-white/5 flex items-center justify-between">
4130
+ <h2 id="sidebarTitle" class="text-lg font-semibold text-blue-400">Node Details</h2>
4131
+ <button id="btnCloseSidebar" class="text-gray-400 hover:text-white text-lg">&times;</button>
4132
+ </div>
4133
+ <div id="sidebarContent" class="flex-1 overflow-y-auto p-6 space-y-4">
4134
+ <!-- Filled dynamically -->
4135
+ </div>
4136
+ </aside>
4867
4137
 
4868
- `;
4869
- } else if (action.type === "read_file") {
4870
- tui.log.info(`\u{1F4D6} Reading: ${colors.dim(action.path || "")}`);
4871
- const result = handleReadFile(action.path || "");
4872
- executionResults += `[Action read_file(${action.path}) Result]:
4873
- ${result}
4138
+ <!-- Empty State overlay -->
4139
+ <div id="emptyState" class="absolute inset-0 bg-[#0b0f19]/90 flex items-center justify-center hidden z-30">
4140
+ <div class="max-w-md p-8 rounded-2xl glass text-center space-y-4">
4141
+ <div class="text-4xl">\u{1F4AD}</div>
4142
+ <h3 class="text-xl font-bold">Awaiting Memories...</h3>
4143
+ <p class="text-sm text-gray-400">Start a chat session using <code class="bg-black/30 px-1.5 py-0.5 rounded text-blue-400">shark dev --graph</code> in another terminal to generate memory boxes.</p>
4144
+ </div>
4145
+ </div>
4146
+ </div>
4874
4147
 
4875
- `;
4876
- } else if (action.type === "search_file") {
4877
- const result = handleSearchFile(action.path || "");
4878
- executionResults += `[Action search_file(${action.path}) Result]:
4879
- ${result}
4880
-
4881
- `;
4882
- } else if (action.type === "run_command") {
4883
- const cmd = action.command || "";
4884
- tui.log.info(`\u{1F4BB} Executing: ${colors.dim(cmd)}`);
4885
- let approved = autoApprovals.commands;
4886
- if (!approved) {
4887
- const choice = await tui.select({
4888
- message: `Execute: ${cmd}?`,
4889
- options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
4890
- });
4891
- if (choice === "always") {
4892
- autoApprovals.commands = true;
4893
- approved = true;
4894
- } else if (choice === "yes") approved = true;
4148
+ <script>
4149
+ let network = null;
4150
+ let currentMode = 'boxes';
4151
+ let graphData = { nodes: [], edges: [] };
4152
+ let lastDataString = '';
4153
+
4154
+ async function fetchGraphData() {
4155
+ try {
4156
+ const res = await fetch(\`/api/graph?mode=\${currentMode}\`);
4157
+ const data = await res.json();
4158
+
4159
+ const container = document.getElementById('emptyState');
4160
+ if (data.nodes.length === 0) {
4161
+ container.classList.remove('hidden');
4162
+ return;
4163
+ }
4164
+ container.classList.add('hidden');
4165
+
4166
+ const currentDataString = JSON.stringify({ nodes: data.nodes, edges: data.edges });
4167
+ if (currentDataString === lastDataString) {
4168
+ highlightActiveNodes(data.activeNodes);
4169
+ return;
4170
+ }
4171
+ lastDataString = currentDataString;
4172
+
4173
+ updateNetwork(data);
4174
+ } catch (err) {
4175
+ console.error("Error fetching graph data:", err);
4895
4176
  }
4896
- if (approved) {
4897
- const result = await handleRunCommand(cmd);
4898
- executionResults += `[Action run_command(${cmd}) Result]:
4899
- ${result}
4177
+ }
4900
4178
 
4901
- `;
4179
+ function getOptionsForMode(mode) {
4180
+ if (mode === 'timeline') {
4181
+ return {
4182
+ layout: {
4183
+ hierarchical: {
4184
+ enabled: true,
4185
+ direction: 'LR',
4186
+ sortMethod: 'directed',
4187
+ nodeSpacing: 200,
4188
+ levelSeparation: 250
4189
+ }
4190
+ },
4191
+ physics: {
4192
+ enabled: false
4193
+ },
4194
+ interaction: { hover: true }
4195
+ };
4902
4196
  } else {
4903
- executionResults += `[Action run_command]: User blocked execution.
4904
-
4905
- `;
4906
- }
4907
- } else if (["create_file", "modify_file"].includes(action.type)) {
4908
- const filePath = action.path || "";
4909
- tui.log.warning(`\u{1F4DD} ${action.type === "create_file" ? "CREATE" : "MODIFY"}: ${colors.bold(filePath)}`);
4910
- let approved = autoApprovals.files;
4911
- if (!approved) {
4912
- const choice = await tui.select({
4913
- message: `Approve changes to ${filePath}?`,
4914
- options: [{ value: "yes", label: "Yes" }, { value: "always", label: "Yes (Auto-Approve Session)" }, { value: "no", label: "No" }]
4915
- });
4916
- if (choice === "always") {
4917
- autoApprovals.files = true;
4918
- approved = true;
4919
- } else if (choice === "yes") approved = true;
4197
+ return {
4198
+ layout: {
4199
+ hierarchical: {
4200
+ enabled: false
4201
+ }
4202
+ },
4203
+ physics: {
4204
+ enabled: true,
4205
+ solver: 'forceAtlas2Based',
4206
+ forceAtlas2Based: { gravitationalConstant: -50, centralGravity: 0.01, springLength: 100 }
4207
+ },
4208
+ interaction: { hover: true }
4209
+ };
4920
4210
  }
4921
- if (approved) {
4922
- if (action.type === "create_file") {
4923
- const dir = path11.dirname(path11.resolve(projectRoot, filePath));
4924
- if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
4925
- fs10.writeFileSync(path11.resolve(projectRoot, filePath), action.content || "", "utf-8");
4926
- executionResults += `[Action create_file]: Success
4927
-
4928
- `;
4929
- } else {
4930
- let success = false;
4931
- if (action.line_range) success = replaceLineRange(filePath, action.line_range[0], action.line_range[1], action.content || "", tui);
4932
- else if (action.target_content) success = startSmartReplace(filePath, action.content || "", action.target_content, tui);
4933
- executionResults += success ? `[Action modify_file]: Success
4211
+ }
4934
4212
 
4935
- ` : `[Action modify_file]: Failed
4213
+ function updateNetwork(data) {
4214
+ graphData = data;
4215
+ const container = document.getElementById('network');
4216
+
4217
+ const visNodes = new vis.DataSet(data.nodes.map(n => {
4218
+ const isActive = data.activeNodes && data.activeNodes.includes(n.id);
4219
+ return {
4220
+ id: n.id,
4221
+ label: n.label,
4222
+ size: n.size || 15,
4223
+ shape: n.type === 'box' ? 'dot' : 'circle',
4224
+ color: {
4225
+ background: isActive ? '#d97706' : (n.type === 'box' ? '#1e40af' : '#065f46'),
4226
+ border: isActive ? '#fbbf24' : (n.type === 'box' ? '#3b82f6' : '#10b981'),
4227
+ highlight: { background: '#2563eb', border: '#60a5fa' }
4228
+ },
4229
+ font: { color: '#f3f4f6', face: 'Inter', size: 12 }
4230
+ };
4231
+ }));
4936
4232
 
4937
- `;
4938
- }
4939
- const val = await validateTypeScript(path11.resolve(projectRoot, filePath));
4940
- if (!val.valid) executionResults += `[Validation Failed]: ${val.error}
4233
+ const visEdges = new vis.DataSet(data.edges.map(e => {
4234
+ // Similarity-based edge thickness (e.similarity from 0.0 to 1.0)
4235
+ const width = e.similarity ? Math.max(1, Math.round(e.similarity * 6)) : 2;
4236
+ const isWeak = e.similarity && e.similarity < 0.6;
4237
+ return {
4238
+ from: e.from,
4239
+ to: e.to,
4240
+ label: e.label || '',
4241
+ arrows: e.arrows || '',
4242
+ width: width,
4243
+ dashes: isWeak ? true : false,
4244
+ color: { color: '#4b5563', highlight: '#9ca3af' },
4245
+ font: { color: '#9ca3af', face: 'Inter', size: 10 }
4246
+ };
4247
+ }));
4941
4248
 
4942
- `;
4249
+ if (network) {
4250
+ network.setData({ nodes: visNodes, edges: visEdges });
4251
+ network.setOptions(getOptionsForMode(currentMode));
4943
4252
  } else {
4944
- executionResults += `[Action ${action.type}]: User Denied.
4945
-
4946
- `;
4253
+ network = new vis.Network(container, { nodes: visNodes, edges: visEdges }, getOptionsForMode(currentMode));
4254
+
4255
+ network.on('click', function(params) {
4256
+ if (params.nodes.length > 0) {
4257
+ showSidebar(params.nodes[0]);
4258
+ } else {
4259
+ hideSidebar();
4260
+ }
4261
+ });
4947
4262
  }
4948
- } else if (action.type.startsWith("ast_")) {
4949
- try {
4950
- let result = "";
4951
- if (action.type === "ast_list_structure") {
4952
- result = await astListStructure(action.path || "");
4953
- } else if (action.type === "ast_get_method") {
4954
- result = await astGetMethod(action.path || "", action.class_name || "", action.method_name || "");
4955
- } else if (action.type === "ast_add_method") {
4956
- const success = await astAddMethod(action.path || "", action.class_name || "", action.method_code || "");
4957
- result = success ? "Method added successfully." : "Failed to add method.";
4958
- } else if (action.type === "ast_modify_method") {
4959
- const success = await astModifyMethod(action.path || "", action.class_name || "", action.method_name || "", action.new_body || "");
4960
- result = success ? "Method modified successfully." : "Failed to modify method.";
4961
- } else if (action.type === "ast_remove_method") {
4962
- const success = await astRemoveMethod(action.path || "", action.class_name || "", action.method_name || "");
4963
- result = success ? "Method removed successfully." : "Failed to remove method.";
4964
- } else if (action.type === "ast_add_class") {
4965
- const success = await astAddClass(action.path || "", action.class_name || "", action.extends_class || void 0, action.implements_interfaces || void 0);
4966
- result = success ? "Class added successfully." : "Failed to add class.";
4967
- } else if (action.type === "ast_get_property") {
4968
- tui.log.info(`\u{1F50D} Reading property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
4969
- const propContent = await astGetProperty(action.path || "", action.class_name || "", action.property_name || "");
4970
- result = `[AST Get Property] Content:
4971
- ${propContent}`;
4972
- } else if (action.type === "ast_add_property") {
4973
- const success = await astAddProperty(action.path || "", action.class_name || "", action.property_code || "");
4974
- result = success ? "Property added successfully." : "Failed to add property.";
4975
- } else if (action.type === "ast_modify_property") {
4976
- tui.log.info(`\u270F\uFE0F Modifying property ${colors.gray(action.property_name || "")} in ${colors.gray(action.class_name || "")}`);
4977
- const success = await astModifyProperty(action.path || "", action.class_name || "", action.property_name || "", action.property_code || "");
4978
- result = success ? "Property modified successfully." : "Failed to modify property.";
4979
- } else if (action.type === "ast_remove_property") {
4980
- const success = await astRemoveProperty(action.path || "", action.class_name || "", action.property_name || "");
4981
- result = success ? "Property removed successfully." : "Failed to remove property.";
4982
- } else if (action.type === "ast_add_decorator") {
4983
- const success = await astAddDecorator(action.path || "", action.class_name || "", action.decorator_code || "");
4984
- result = success ? "Decorator added successfully." : "Failed to add decorator.";
4985
- } else if (action.type === "ast_add_interface") {
4986
- const success = await astAddInterface(action.path || "", action.interface_code || "");
4987
- result = success ? "Interface added successfully." : "Failed to add interface.";
4988
- } else if (action.type === "ast_add_type_alias") {
4989
- const success = await astAddTypeAlias(action.path || "", action.type_code || "");
4990
- result = success ? "Type alias added successfully." : "Failed to add type alias.";
4991
- } else if (action.type === "ast_add_function") {
4992
- const success = await astAddFunction(action.path || "", action.function_code || "");
4993
- result = success ? "Function added successfully." : "Failed to add function.";
4994
- } else if (action.type === "ast_remove_function") {
4995
- const success = await astRemoveFunction(action.path || "", action.function_name || "");
4996
- result = success ? "Function removed successfully." : "Failed to remove function.";
4997
- } else if (action.type === "ast_add_import") {
4998
- const success = await astAddImport(action.path || "", action.import_statement || "");
4999
- result = success ? "Import added successfully." : "Failed to add import.";
5000
- } else if (action.type === "ast_remove_import") {
5001
- const success = await astRemoveImport(action.path || "", action.module_path || "");
5002
- result = success ? "Import removed successfully." : "Failed to remove import.";
5003
- } else if (action.type === "ast_organize_imports") {
5004
- const success = await astOrganizeImports(action.path || "");
5005
- result = success ? "Imports organized successfully." : "Failed to organize imports.";
5006
- } else {
5007
- result = `Unknown AST action: ${action.type}`;
5008
- }
5009
- executionResults += `[Action ${action.type} Result]:
5010
- ${result}
4263
+ }
5011
4264
 
5012
- `;
5013
- tui.log.info(`\u26A1 AST Action ${colors.dim(action.type)}: ${result}`);
5014
- } catch (e) {
5015
- executionResults += `[Action ${action.type} Failed]: ${e.message}
4265
+ function highlightActiveNodes(activeNodeIds) {
4266
+ if (!network || !activeNodeIds) return;
4267
+ const nodesDataset = network.body.data.nodes;
4268
+ graphData.nodes.forEach(n => {
4269
+ const isActive = activeNodeIds.includes(n.id);
4270
+ nodesDataset.update({
4271
+ id: n.id,
4272
+ color: {
4273
+ background: isActive ? '#d97706' : (n.type === 'box' ? '#1e40af' : '#065f46'),
4274
+ border: isActive ? '#fbbf24' : (n.type === 'box' ? '#3b82f6' : '#10b981')
4275
+ }
4276
+ });
4277
+ });
4278
+ }
5016
4279
 
5017
- `;
5018
- tui.log.error(`\u274C AST Action Error: ${e.message}`);
5019
- }
5020
- } else if (action.type === "search_ast") {
5021
- const result = await astGrepSearch(action.pattern || "", action.path || "", action.language || "typescript", tui);
5022
- executionResults += `[Action search_ast Result]:
5023
- ${result}
4280
+ function showSidebar(nodeId) {
4281
+ const node = graphData.nodes.find(n => n.id === nodeId);
4282
+ if (!node || !node.details) return;
5024
4283
 
5025
- `;
5026
- } else if (action.type === "modify_ast") {
5027
- const success = await astGrepRewrite(action.pattern || "", action.fix || "", action.path || "", action.language || "typescript", tui);
5028
- executionResults += success ? `[Action modify_ast]: Success
4284
+ document.getElementById('sidebarTitle').innerText = node.label;
4285
+ const content = document.getElementById('sidebarContent');
4286
+ content.innerHTML = '';
5029
4287
 
5030
- ` : `[Action modify_ast]: Failed
4288
+ if (node.type === 'box') {
4289
+ const detail = node.details;
4290
+ if (detail.keywords && detail.keywords.length > 0) {
4291
+ const tagContainer = document.createElement('div');
4292
+ tagContainer.className = 'flex flex-wrap gap-2';
4293
+ detail.keywords.forEach(kw => {
4294
+ const span = document.createElement('span');
4295
+ span.className = 'px-2 py-0.5 bg-blue-500/10 text-blue-400 rounded text-xs border border-blue-500/20';
4296
+ span.innerText = kw;
4297
+ tagContainer.appendChild(span);
4298
+ });
4299
+ content.appendChild(tagContainer);
4300
+ }
5031
4301
 
5032
- `;
5033
- }
5034
- }
5035
- if (executionResults) {
5036
- if (waitingForUser) {
5037
- const userReply = await tui.text({ message: "Your answer:" });
5038
- if (tui.isCancel(userReply)) {
5039
- keepGoing = false;
5040
- break;
4302
+ if (detail.history) {
4303
+ const historyTitle = document.createElement('h4');
4304
+ historyTitle.className = 'text-sm font-semibold border-b border-white/5 pb-2 mt-4';
4305
+ historyTitle.innerText = 'Dialogue Logs';
4306
+ content.appendChild(historyTitle);
4307
+
4308
+ detail.history.forEach(msg => {
4309
+ const bubble = document.createElement('div');
4310
+ bubble.className = \`p-3 rounded-lg text-sm \${msg.role === 'user' ? 'bg-white/5 border border-white/5' : 'bg-blue-600/10 border border-blue-600/20'}\`;
4311
+ bubble.innerHTML = \`<strong class="block text-xs text-gray-400 mb-1">\${msg.role.toUpperCase()}</strong>\${msg.content}\`;
4312
+ content.appendChild(bubble);
4313
+ });
4314
+ }
4315
+ } else {
4316
+ const eventDesc = document.createElement('p');
4317
+ eventDesc.className = 'text-sm text-gray-300';
4318
+ eventDesc.innerText = node.details.event || '';
4319
+ content.appendChild(eventDesc);
5041
4320
  }
5042
- nextPrompt = `${executionResults}
5043
- User Reply: ${userReply}`;
5044
- } else {
5045
- nextPrompt = `${executionResults}
5046
- [System]: Continue execution. If finished, output "TASK_COMPLETED: summary".`;
5047
- tui.log.info(colors.dim("Processing results..."));
5048
- }
5049
- } else if (!keepGoing) {
5050
- } else if (waitingForUser) {
5051
- } else {
5052
- if (!isTaskCompleted && actions.length > 0) nextPrompt = "Please continue.";
5053
- }
5054
- } else {
5055
- tui.log.warning("No response received from agent.");
5056
- }
5057
- } catch (e) {
5058
- tui.log.error(e.message);
5059
- keepGoing = false;
5060
- return { success: false, summary: `Error: ${e.message}` };
5061
- }
5062
- }
5063
- tui.log.success("\u2705 Task Scope Completed");
5064
- return { success: true, summary: finalSummary || "Task completed without summary." };
5065
- }
5066
- async function callDevAgentApi(prompt, onChunk, conversationKey = AGENT_TYPE) {
5067
- const existingConversationId = await conversationManager.getConversationId(conversationKey);
5068
- const provider = ProviderResolver.getProvider("developer_agent");
5069
- const parsedResponse = await provider.streamChat(prompt, {
5070
- conversationId: existingConversationId,
5071
- agentType: "developer_agent",
5072
- onChunk
5073
- });
5074
- if (parsedResponse.conversation_id) {
5075
- await conversationManager.saveConversationId(conversationKey, parsedResponse.conversation_id);
5076
- }
5077
- return parsedResponse;
5078
- }
5079
4321
 
5080
- // src/core/workflow/task-manager.ts
5081
- import fs11 from "fs";
5082
- import path12 from "path";
5083
- var TaskManager = class {
5084
- projectRoot;
5085
- specPath;
5086
- constructor(projectRoot = process.cwd()) {
5087
- this.projectRoot = projectRoot;
5088
- this.specPath = path12.resolve(this.projectRoot, "_sharkrc", "tech-spec.md");
5089
- }
5090
- /**
5091
- * Reads the tech-spec.md file and analyzes its current state.
5092
- */
5093
- analyzeSpecState() {
5094
- if (!fs11.existsSync(this.specPath)) {
5095
- return { status: "MISSING", allTasks: [] };
5096
- }
5097
- const content = fs11.readFileSync(this.specPath, "utf-8");
5098
- const lines = content.split("\n");
5099
- const tasks = [];
5100
- let taskIndex = 1;
5101
- for (let i = 0; i < lines.length; i++) {
5102
- const line = lines[i];
5103
- const trimmed = line.trim();
5104
- const pendingMatch = trimmed.match(/^- \[ \] (.*)/);
5105
- const completedMatch = trimmed.match(/^- \[x\] (.*)/i);
5106
- const progressMatch = trimmed.match(/^- \[\/\] (.*)/);
5107
- let currentTask = null;
5108
- let status2 = null;
5109
- let description = "";
5110
- if (pendingMatch) {
5111
- description = pendingMatch[1].trim();
5112
- status2 = "PENDING";
5113
- } else if (completedMatch) {
5114
- description = completedMatch[1].trim();
5115
- status2 = "COMPLETED";
5116
- } else if (progressMatch) {
5117
- description = progressMatch[1].trim();
5118
- status2 = "IN_PROGRESS";
5119
- }
5120
- if (status2 && description) {
5121
- let j = i + 1;
5122
- while (j < lines.length) {
5123
- const nextLine = lines[j];
5124
- const nextTrimmed = nextLine.trim();
5125
- if (!nextTrimmed || nextTrimmed.match(/^- \[[ x\/]\]/) || nextTrimmed.startsWith("#")) {
5126
- break;
5127
- }
5128
- description += "\n" + nextTrimmed;
5129
- j++;
4322
+ document.getElementById('sidebar').classList.remove('translate-x-full');
5130
4323
  }
5131
- currentTask = {
5132
- id: `task-${taskIndex++}`,
5133
- description,
5134
- status: status2,
5135
- line_number: i
5136
- };
5137
- tasks.push(currentTask);
5138
- }
5139
- }
5140
- let nextTask = tasks.find((t) => t.status === "IN_PROGRESS");
5141
- if (!nextTask) {
5142
- nextTask = tasks.find((t) => t.status === "PENDING");
5143
- }
5144
- const status = !nextTask && tasks.length > 0 && tasks.every((t) => t.status === "COMPLETED") ? "COMPLETED" : "PENDING";
5145
- return {
5146
- status: tasks.length === 0 ? "MISSING" : status,
5147
- // Empty file is effectively "pending creation" but we treat as missing content logic elsewhere
5148
- nextTask,
5149
- allTasks: tasks
5150
- };
5151
- }
5152
- /**
5153
- * Marks a specific task as COMPLETED in the file.
5154
- * Uses line-based replacement to be safe.
5155
- */
5156
- markTaskAsDone(taskId) {
5157
- const state = this.analyzeSpecState();
5158
- const task = state.allTasks.find((t) => t.id === taskId);
5159
- if (!task) {
5160
- console.error(`Task ${taskId} not found.`);
5161
- return false;
5162
- }
5163
- const content = fs11.readFileSync(this.specPath, "utf-8");
5164
- const lines = content.split("\n");
5165
- const targetLine = lines[task.line_number];
5166
- if (!targetLine.includes(task.description)) {
5167
- console.error(`Concurrency Error: Task line content mistmatch. Expected "${task.description}" at line ${task.line_number}.`);
5168
- return false;
5169
- }
5170
- const newLine = targetLine.replace("- [ ]", "- [x]").replace("- [/]", "- [x]");
5171
- lines[task.line_number] = newLine;
5172
- fs11.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
5173
- return true;
5174
- }
5175
- /**
5176
- * Marks a task as IN_PROGRESS.
5177
- */
5178
- markTaskInProgress(taskId) {
5179
- const state = this.analyzeSpecState();
5180
- const task = state.allTasks.find((t) => t.id === taskId);
5181
- if (!task) return false;
5182
- const content = fs11.readFileSync(this.specPath, "utf-8");
5183
- const lines = content.split("\n");
5184
- let targetLine = lines[task.line_number];
5185
- targetLine = targetLine.replace("- [ ]", "- [/]");
5186
- lines[task.line_number] = targetLine;
5187
- fs11.writeFileSync(this.specPath, lines.join("\n"), "utf-8");
5188
- return true;
5189
- }
5190
- /**
5191
- * Completely updates the spec file content (used by Spec Agent).
5192
- */
5193
- updateSpecContent(newContent) {
5194
- fs11.writeFileSync(this.specPath, newContent, "utf-8");
5195
- }
5196
- getSpecPath() {
5197
- return this.specPath;
5198
- }
5199
- };
5200
-
5201
- // src/core/agents/specification-agent.ts
5202
- import fs12 from "fs";
5203
- import path13 from "path";
5204
- var AGENT_TYPE2 = "specification_agent";
5205
- async function interactiveSpecificationAgent(options = {}) {
5206
- FileLogger.init();
5207
- tui.intro("\u{1F3D7}\uFE0F Specification Agent (Template-Based)");
5208
- const projectRoot = process.cwd();
5209
- const sharkRcDir = path13.resolve(projectRoot, "_sharkrc");
5210
- if (!fs12.existsSync(sharkRcDir)) fs12.mkdirSync(sharkRcDir, { recursive: true });
5211
- const outputFile = path13.resolve(sharkRcDir, "tech-spec.md");
5212
- if (!fs12.existsSync(outputFile)) {
5213
- let initialContent = `# Technical Specification: {{PROJECT_NAME}}
5214
-
5215
- ## 1. Technology Stack
5216
- [TO BE ANALYZED - STACK]
5217
- - Language: [e.g. TypeScript]
5218
- - Framework: [e.g. Node.js / React]
5219
- - Database: [e.g. SQLite / PostgreSQL]
5220
- - Key Libraries: [Top 5 dependencies]
5221
4324
 
5222
- ## 2. Architecture Overview
5223
- [TO BE ANALYZED - ARCHITECTURE]
5224
- [Brief description of architectural pattern]
5225
-
5226
- ## 3. Data Model
5227
- [TO BE ANALYZED - DATA MODEL]
5228
- [Schema/ERD definitions]
4325
+ function hideSidebar() {
4326
+ document.getElementById('sidebar').classList.add('translate-x-full');
4327
+ }
5229
4328
 
5230
- ## 4. API / Interface Contracts
5231
- [TO BE ANALYZED - API]
5232
- [Main endpoints or CLI commands]
4329
+ document.getElementById('btnCloseSidebar').addEventListener('click', hideSidebar);
5233
4330
 
5234
- ## 5. Implementation Steps
5235
- [TO BE FILLED - MUST BE CHECKBOXES]
5236
- `;
5237
- const projectName = path13.basename(projectRoot);
5238
- initialContent = initialContent.replace(/{{PROJECT_NAME}}/g, projectName);
5239
- const BOM = "\uFEFF";
5240
- fs12.writeFileSync(outputFile, BOM + initialContent, { encoding: "utf-8" });
5241
- tui.log.success(`\u2705 Created: ${colors.bold("_sharkrc/tech-spec.md")}`);
5242
- } else {
5243
- tui.log.info(`\u{1F4C4} Using existing ${colors.bold("_sharkrc/tech-spec.md")}`);
5244
- }
5245
- let contextContent = "";
5246
- const contextPath = path13.resolve(projectRoot, "_sharkrc", "project-context.md");
5247
- if (fs12.existsSync(contextPath)) {
5248
- contextContent = fs12.readFileSync(contextPath, "utf-8");
5249
- tui.log.info(`\u{1F4D8} Context loaded.`);
5250
- }
5251
- let briefingContent = "";
5252
- if (options.briefingPath && fs12.existsSync(options.briefingPath)) {
5253
- briefingContent = fs12.readFileSync(options.briefingPath, "utf-8");
5254
- tui.log.info(`\u{1F4C4} Briefing loaded from: ${colors.dim(options.briefingPath)}`);
5255
- } else {
5256
- const standardBriefing = path13.resolve(projectRoot, "_sharkrc", "briefing.md");
5257
- if (fs12.existsSync(standardBriefing)) {
5258
- briefingContent = fs12.readFileSync(standardBriefing, "utf-8");
5259
- tui.log.info(`\u{1F4C4} Briefing loaded.`);
5260
- }
5261
- }
5262
- let initialPrompt = `
5263
- Voc\xEA \xE9 o **Shark Spec**, um Arquiteto de Software S\xEAnior e Tech Lead.
5264
- Seu objetivo final \xE9 produzir uma especifica\xE7\xE3o t\xE9cnica precisa para a tarefa no arquivo \`_sharkrc/tech-spec.md\`.
5265
-
5266
- \u26A0\uFE0F O SEU WORKFLOW \xC9 GUIADO POR FASES.
5267
- N\xC3O TENTE ADIANTAR O TRABALHO (ex: investigar c\xF3digo ou preencher template agora).
4331
+ document.getElementById('btnBoxes').addEventListener('click', () => {
4332
+ currentMode = 'boxes';
4333
+ lastDataString = '';
4334
+ document.getElementById('btnBoxes').className = 'px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white transition-all';
4335
+ document.getElementById('btnTimeline').className = 'px-4 py-1.5 rounded-md text-xs font-medium text-gray-400 hover:text-white transition-all';
4336
+ fetchGraphData();
4337
+ });
5268
4338
 
5269
- **VOC\xCA EST\xC1 NA FASE 1: ENTENDIMENTO DA TAREFA**
5270
- - Use \`talk_with_user\` para perguntar ao usu\xE1rio qual tarefa espec\xEDfica, funcionalidade ou bug ele precisa especificar.
5271
- - Confirme o escopo e os limites com o usu\xE1rio.
5272
- - Se o escopo estiver perfeitamente claro e confirmado (com o usu\xE1rio), emita "PHASE_COMPLETED" no campo "summary" do JSON para avan\xE7ar.
4339
+ document.getElementById('btnTimeline').addEventListener('click', () => {
4340
+ currentMode = 'timeline';
4341
+ lastDataString = '';
4342
+ document.getElementById('btnTimeline').className = 'px-4 py-1.5 rounded-md text-xs font-medium bg-blue-600 text-white transition-all';
4343
+ document.getElementById('btnBoxes').className = 'px-4 py-1.5 rounded-md text-xs font-medium text-gray-400 hover:text-white transition-all';
4344
+ fetchGraphData();
4345
+ });
5273
4346
 
5274
- IMPORTANTE: Toda a sua comunica\xE7\xE3o DEVE ser em Portugu\xEAs.
5275
- `;
5276
- if (briefingContent) {
5277
- initialPrompt += `
5278
- \u2139\uFE0F Um documento de briefing foi encontrado. Ele define parcialmente a tarefa para a Fase 1.
5279
- Confirme seu entendimento com o usu\xE1rio via \`talk_with_user\` antes de prosseguir para a Fase 2.
4347
+ document.getElementById('searchInput').addEventListener('input', (e) => {
4348
+ const val = e.target.value.toLowerCase();
4349
+ if (!val) return;
4350
+ const node = graphData.nodes.find(n => n.label.toLowerCase().includes(val));
4351
+ if (node && network) {
4352
+ network.focus(node.id, { scale: 1.2, animation: true });
4353
+ showSidebar(node.id);
4354
+ }
4355
+ });
5280
4356
 
5281
- --- BRIEFING ---
5282
- ${briefingContent}
5283
- ----------------
5284
- `;
5285
- } else {
5286
- initialPrompt += `
5287
- \u2139\uFE0F Nenhum documento de briefing foi encontrado. Inicie a Fase 1 imediatamente: use \`talk_with_user\` para perguntar ao usu\xE1rio o que precisa ser especificado.
5288
- `;
5289
- }
5290
- if (options.initialContext) {
5291
- initialPrompt += `
5292
- --- CONTEXTO DE EXECU\xC7\xC3O ANTERIOR (HANDOVER/FEEDBACK) ---
5293
- ${options.initialContext}
5294
- -----------------------------------------------------
4357
+ fetchGraphData();
4358
+ setInterval(fetchGraphData, 3000);
4359
+ </script>
4360
+ </body>
4361
+ </html>
5295
4362
  `;
5296
- }
5297
- if (contextContent) {
5298
- initialPrompt += `
5299
- \u2139\uFE0F O contexto do projeto est\xE1 dispon\xEDvel para refer\xEAncia. Use-o na Fase 2 para se alinhar com os padr\xF5es de arquitetura existentes, mas N\xC3O o use para preencher as se\xE7\xF5es de forma gen\xE9rica.
5300
4363
 
5301
- --- PROJECT CONTEXT ---
5302
- ${contextContent}
5303
- -----------------------
5304
- `;
4364
+ // src/commands/graph.ts
4365
+ var graphCommand = new Command6("graph").description("Visualize episodic memory and trace graphs in your browser").addArgument(new Argument("[mode]", "Default view mode (boxes or timeline)").choices(["boxes", "timeline"]).default("boxes")).option("-p, --port <number>", "Port to host the server", "4200").action(async (mode, options) => {
4366
+ let port = parseInt(options.port, 10);
4367
+ if (isNaN(port)) {
4368
+ console.error(colors.error("\u274C Invalid port number."));
4369
+ process.exit(1);
5305
4370
  }
5306
- await runSpecLoop(initialPrompt.trim(), outputFile, options.agentId);
5307
- }
5308
- async function runSpecLoop(initialMessage, targetPath, overrideAgentId) {
5309
- let nextPrompt = initialMessage;
5310
- let keepGoing = true;
5311
- let stepCount = 0;
5312
- let currentPhase = 1;
5313
- const MAX_STEPS = 30;
5314
- while (keepGoing && stepCount < MAX_STEPS) {
5315
- stepCount++;
5316
- const spinner = tui.spinner();
5317
- spinner.start(`\u{1F3D7}\uFE0F Spec Agent working (Step ${stepCount}/${MAX_STEPS})...`);
5318
- let pendingSections = [];
5319
- if (fs12.existsSync(targetPath)) {
5320
- const content = fs12.readFileSync(targetPath, "utf-8");
5321
- if (content.includes("[TO BE ANALYZED]")) pendingSections.push("Analysis Sections (Stack, Arch, Data, API)");
5322
- if (content.includes("[TO BE FILLED")) pendingSections.push("Implementation Steps");
4371
+ let activeNodes = [];
4372
+ let activeNodesTimestamp = 0;
4373
+ const server = http.createServer(async (req, res) => {
4374
+ const url = new URL(req.url || "", `http://localhost:${port}`);
4375
+ if (url.pathname === "/") {
4376
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
4377
+ res.end(GRAPH_HTML_TEMPLATE);
4378
+ return;
5323
4379
  }
5324
- if (pendingSections.length === 0 && stepCount > 1) {
4380
+ if (req.method === "POST" && url.pathname === "/api/active-nodes") {
4381
+ let body = "";
4382
+ req.on("data", (chunk) => {
4383
+ body += chunk;
4384
+ });
4385
+ req.on("end", () => {
4386
+ try {
4387
+ const data = JSON.parse(body);
4388
+ activeNodes = data.nodeIds || [];
4389
+ activeNodesTimestamp = Date.now();
4390
+ res.writeHead(200, { "Content-Type": "application/json" });
4391
+ res.end(JSON.stringify({ success: true }));
4392
+ } catch {
4393
+ res.writeHead(400);
4394
+ res.end("Bad Request");
4395
+ }
4396
+ });
4397
+ return;
5325
4398
  }
5326
- let responseText = "";
5327
- let lastResponse = null;
5328
- try {
5329
- lastResponse = await callSpecAgentApi(nextPrompt, (chunk) => {
5330
- responseText += chunk;
5331
- }, overrideAgentId);
5332
- spinner.stop("Response received");
5333
- if (lastResponse && lastResponse.actions) {
5334
- let executionResults = "";
5335
- let waitingForUser = false;
5336
- let specUpdated = false;
5337
- let hasSystemError = false;
5338
- let systemErrorContent = "";
5339
- for (const action of lastResponse.actions) {
5340
- if (action.type === "talk_with_user") {
5341
- const isSystemError = action.content?.startsWith("[SYSTEM ERROR]");
5342
- if (isSystemError) {
5343
- tui.log.error(`\u26A0\uFE0F Detectado erro na resposta do Agente (truncado ou inv\xE1lido).`);
5344
- tui.log.info(colors.dim(action.content || ""));
5345
- const approved = await tui.confirm({ message: `Enviar notifica\xE7\xE3o de erro para o agente tentar se recuperar automaticamente?` });
5346
- if (approved) {
5347
- hasSystemError = true;
5348
- systemErrorContent = action.content || "";
5349
- } else {
5350
- const userReply = await tui.text({ message: "Seu prompt alternativo para o agente:" });
5351
- if (tui.isCancel(userReply)) {
5352
- keepGoing = false;
5353
- return;
4399
+ if (url.pathname === "/api/graph") {
4400
+ const graphMode = url.searchParams.get("mode") || "boxes";
4401
+ const runDir = path11.join(process.cwd(), ".shark", "membox");
4402
+ const boxesFile = path11.join(runDir, "boxes.jsonl");
4403
+ const tracesFile = path11.join(runDir, "traces.jsonl");
4404
+ const nodes = [];
4405
+ const edges = [];
4406
+ try {
4407
+ if (fs11.existsSync(boxesFile)) {
4408
+ const fileContent = await fs11.promises.readFile(boxesFile, "utf-8");
4409
+ const lines = fileContent.trim().split("\n").filter(Boolean);
4410
+ lines.forEach((line) => {
4411
+ try {
4412
+ const box = JSON.parse(line);
4413
+ if (graphMode === "boxes") {
4414
+ const history = [];
4415
+ if (box.content_text) {
4416
+ const turns = box.content_text.split(/\n(?=user:|assistant:)/i);
4417
+ turns.forEach((turn) => {
4418
+ const match = turn.match(/^(user|assistant):\s*([\s\S]*)$/i);
4419
+ if (match) {
4420
+ const role = match[1].toLowerCase();
4421
+ let content = match[2].trim();
4422
+ if (content.startsWith("{")) {
4423
+ try {
4424
+ const parsed = JSON.parse(content);
4425
+ if (parsed.action && parsed.action.content) {
4426
+ content = parsed.action.content;
4427
+ } else if (parsed.summary) {
4428
+ content = parsed.summary;
4429
+ }
4430
+ } catch {
4431
+ }
4432
+ }
4433
+ history.push({ role, content: content.replace(/\n/g, "<br>") });
4434
+ } else {
4435
+ history.push({ role: "log", content: turn.trim().replace(/\n/g, "<br>") });
4436
+ }
4437
+ });
5354
4438
  }
5355
- hasSystemError = true;
5356
- systemErrorContent = userReply;
5357
- }
5358
- } else {
5359
- tui.log.info(colors.primary("\u{1F916} Architect:"));
5360
- console.log(action.content);
5361
- waitingForUser = true;
5362
- }
5363
- } else if (action.type === "list_files") {
5364
- tui.log.info(`\u{1F4C2} Scanning: ${colors.dim(action.path || ".")}`);
5365
- const result = handleListFiles(action.path || ".");
5366
- executionResults += `[Action list_files(${action.path}) Result]:
5367
- ${result}
5368
-
5369
- `;
5370
- } else if (action.type === "read_file") {
5371
- tui.log.info(`\u{1F4D6} Reading: ${colors.dim(action.path || "")}`);
5372
- const result = handleReadFile(action.path || "");
5373
- executionResults += `[Action read_file(${action.path}) Result]:
5374
- ${result}
5375
-
5376
- `;
5377
- } else if (action.type === "search_file") {
5378
- tui.log.info(`\u{1F50D} Searching: ${colors.dim(action.path || "")}`);
5379
- const result = handleSearchFile(action.path || "");
5380
- executionResults += `[Action search_file(${action.path}) Result]:
5381
- ${result}
5382
-
5383
- `;
5384
- } else if (action.type === "search_code") {
5385
- const glob = action.path || "src/**/*";
5386
- const query = action.query || "";
5387
- const isRegex = action.is_regex === true;
5388
- tui.log.info(`\u{1F50E} Search code: ${colors.dim(`"${query}" in ${glob}`)}`);
5389
- const result = handleSearchCode(glob, query, isRegex);
5390
- executionResults += `[Action search_code("${query}" in "${glob}") Result]:
5391
- ${result}
5392
-
5393
- `;
5394
- } else if (["create_file", "modify_file"].includes(action.type)) {
5395
- let actionPath = path13.resolve(action.path || "");
5396
- const resolvedTargetPath = path13.resolve(targetPath);
5397
- let isTarget = actionPath === resolvedTargetPath;
5398
- if (!isTarget && path13.basename(actionPath) === "tech-spec.md") {
5399
- tui.log.warning(`Redirecting ${action.type} from ${action.path} to ${path13.relative(process.cwd(), targetPath)}`);
5400
- action.path = targetPath;
5401
- actionPath = resolvedTargetPath;
5402
- isTarget = true;
5403
- }
5404
- if (!isTarget && action.type === "create_file") {
5405
- const confirm = await tui.confirm({ message: `Agent wants to create ${action.path}. Allow?` });
5406
- if (!confirm) {
5407
- executionResults += `[Action create_file]: User denied.
5408
- `;
5409
- continue;
4439
+ nodes.push({
4440
+ id: `box_${box.box_id}`,
4441
+ label: box.features?.topic || `Topic ${box.box_id}`,
4442
+ type: "box",
4443
+ size: 15 + Math.min((box.features?.events?.length || 0) * 2, 20),
4444
+ details: {
4445
+ topic: box.features?.topic,
4446
+ keywords: box.features?.keywords,
4447
+ history
4448
+ }
4449
+ });
4450
+ } else {
4451
+ box.features?.events?.forEach((ev, idx) => {
4452
+ nodes.push({
4453
+ id: `ev_${box.box_id}_${idx}`,
4454
+ label: ev.length > 30 ? ev.substring(0, 30) + "..." : ev,
4455
+ type: "event",
4456
+ details: { event: ev }
4457
+ });
4458
+ });
5410
4459
  }
4460
+ } catch (e) {
5411
4461
  }
4462
+ });
4463
+ }
4464
+ if (fs11.existsSync(tracesFile)) {
4465
+ const fileContent = await fs11.promises.readFile(tracesFile, "utf-8");
4466
+ const lines = fileContent.trim().split("\n").filter(Boolean);
4467
+ lines.forEach((line) => {
5412
4468
  try {
5413
- if (action.type === "create_file") {
5414
- const BOM = "\uFEFF";
5415
- fs12.writeFileSync(action.path, BOM + (action.content || ""), "utf-8");
5416
- tui.log.success(`\u2705 Created: ${action.path}`);
5417
- executionResults += `[Action create_file]: Success.
5418
- `;
5419
- } else if (action.type === "modify_file") {
5420
- if (action.target_content) {
5421
- const success = startSmartReplace(action.path, action.content || "", action.target_content, tui);
5422
- if (success) {
5423
- executionResults += `[Action modify_file]: Success.
5424
- `;
5425
- specUpdated = true;
5426
- } else {
5427
- executionResults += `[Action modify_file]: Failed. Target content not found or ambiguous.
5428
- `;
4469
+ const trace = JSON.parse(line);
4470
+ if (graphMode === "boxes") {
4471
+ const boxIds = trace.box_ids || [];
4472
+ const entries = trace.entries || [];
4473
+ for (let i = 0; i < boxIds.length - 1; i++) {
4474
+ const entry = entries.find((e) => e.box_id === boxIds[i + 1]);
4475
+ edges.push({
4476
+ from: `box_${boxIds[i]}`,
4477
+ to: `box_${boxIds[i + 1]}`,
4478
+ label: `Trace ${trace.trace_id}`,
4479
+ arrows: "to",
4480
+ similarity: entry?.similarity ?? 1
4481
+ });
4482
+ }
4483
+ } else {
4484
+ const traceEventIds = [];
4485
+ const entries = trace.entries || [];
4486
+ const eventSimilarities = [];
4487
+ entries.forEach((entry) => {
4488
+ if (entry?.events) {
4489
+ entry.events.forEach((_, idx) => {
4490
+ traceEventIds.push(`ev_${entry.box_id}_${idx}`);
4491
+ eventSimilarities.push(entry.similarity ?? 1);
4492
+ });
5429
4493
  }
5430
- } else {
5431
- executionResults += `[Action modify_file]: Failed. 'target_content' is required.
5432
- `;
4494
+ });
4495
+ for (let i = 0; i < traceEventIds.length - 1; i++) {
4496
+ edges.push({
4497
+ from: traceEventIds[i],
4498
+ to: traceEventIds[i + 1],
4499
+ label: `Trace ${trace.trace_id}`,
4500
+ arrows: "to",
4501
+ similarity: eventSimilarities[i + 1] ?? 1
4502
+ });
5433
4503
  }
5434
4504
  }
5435
4505
  } catch (e) {
5436
- executionResults += `[Action ${action.type}]: Error: ${e.message}
5437
- `;
5438
4506
  }
5439
- }
5440
- }
5441
- if (lastResponse.message && lastResponse.message.includes("PHASE_COMPLETED")) {
5442
- const extraContext = executionResults ? `
5443
-
5444
- Resultados das \xFAltimas a\xE7\xF5es executadas antes da conclus\xE3o:
5445
- ${executionResults}` : "";
5446
- if (currentPhase === 1) {
5447
- currentPhase = 2;
5448
- tui.log.success(`\u2705 Fase 1 Conclu\xEDda. Iniciando Fase 2 (Investiga\xE7\xE3o).`);
5449
- nextPrompt = `[System Message]
5450
- Voc\xEA completou a FASE 1 com sucesso.
5451
-
5452
- **VOC\xCA AGORA EST\xC1 NA FASE 2: INVESTIGA\xC7\xC3O**
5453
- - Use \`search_code\` e \`list_files\` para explorar os arquivos relevantes \xE0 tarefa.
5454
- - Prefira \`search_code\` em vez de \`read_file\` para buscar c\xF3digo sem inflar o contexto.
5455
- - N\xC3O leia o projeto inteiro de forma gen\xE9rica.
5456
- - REGRA DE OURO (READ-FIRST): Voc\xEA N\xC3O PODE referenciar um arquivo na especifica\xE7\xE3o t\xE9cnica que n\xE3o tenha investigado nesta fase.
5457
- - Quando achar que possui toda a clareza t\xE9cnica sobre onde e o que deve ser feito no c\xF3digo, emita "PHASE_COMPLETED" no summary.${extraContext}`;
5458
- continue;
5459
- } else if (currentPhase === 2) {
5460
- currentPhase = 3;
5461
- tui.log.success(`\u2705 Fase 2 Conclu\xEDda. Iniciando Fase 3 (Preenchimento).`);
5462
- nextPrompt = `[System Message]
5463
- Voc\xEA completou a FASE 2 com sucesso.
5464
-
5465
- **VOC\xCA AGORA EST\xC1 NA FASE 3: PREENCHIMENTO DO TEMPLATE**
5466
- - Use \`modify_file\` no arquivo \`${targetPath}\` para substituir os placeholders pelo conte\xFAdo real levantado na fase de investiga\xE7\xE3o.
5467
- - As se\xE7\xF5es 1-4 devem descrever o contexto da TAREFA, e n\xE3o o projeto como um todo.
5468
- - Passos de Implementa\xE7\xE3o (Implementation Steps): APENAS checkboxes markdown: \`- [ ] [Verbo de A\xE7\xE3o] [O Que] em [Caminho Relativo]\`.
5469
- - Quando TODOS os placeholders ([TO BE ANALYZED...] ou [TO BE FILLED]) forem substitu\xEDdos e o trabalho conclu\xEDdo, emita "SPEC_UPDATED: Complete" no summary para finalizar.${extraContext}`;
5470
- continue;
5471
- }
5472
- }
5473
- if (lastResponse.message && lastResponse.message.includes("SPEC_UPDATED:")) {
5474
- if (currentPhase < 3) {
5475
- tui.log.warning(`O agente tentou finalizar prematuramente. For\xE7ando retorno para a fase atual...`);
5476
- nextPrompt = `[System Error]: Voc\xEA tentou finalizar a especifica\xE7\xE3o prematuramente emitindo SPEC_UPDATED, mas ainda est\xE1 na Fase ${currentPhase}. Voc\xEA s\xF3 pode finalizar quando estiver na Fase 3.
5477
-
5478
- Continue seu trabalho na Fase ${currentPhase} ou emita "PHASE_COMPLETED" se terminou esta etapa atual.`;
5479
- continue;
5480
- }
5481
- const content = fs12.existsSync(targetPath) ? fs12.readFileSync(targetPath, "utf-8") : "";
5482
- if (content.includes("[TO BE")) {
5483
- const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
5484
- let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "algumas se\xE7\xF5es";
5485
- tui.log.warning(`O agente tentou concluir prematuramente, mas h\xE1 placeholders pendentes. For\xE7ando retorno...`);
5486
- nextPrompt = `[System Error]: A valida\xE7\xE3o falhou e o bloqueio autom\xE1tico foi acionado.
5487
- Voc\xEA tentou concluir a tarefa, mas o arquivo AINDA possui placeholders '[TO BE ANALYZED...]' ou '[TO BE FILLED]'.
5488
- As seguintes se\xE7\xF5es ainda cont\xEAm estes placeholders: ${missing}.
5489
- Voc\xEA \xE9 OBRIGADO a usar a action \`modify_file\` para preencher o conte\xFAdo de cada uma dessas se\xE7\xF5es. Use o placeholder exato no campo \`target_content\`. N\xC3O repita a conclus\xE3o da tarefa at\xE9 corrigir todas as pend\xEAncias.`;
5490
- continue;
5491
- } else {
5492
- const updateSummary = lastResponse.message.split("SPEC_UPDATED:")[1].trim();
5493
- tui.log.success(`\u2705 Spec Finalized: ${updateSummary}`);
5494
- return;
5495
- }
5496
- }
5497
- if (executionResults) {
5498
- FileLogger.log("TOOL_EXECUTION", "Specification Agent Actions Output", { executionResults });
5499
- }
5500
- if (hasSystemError) {
5501
- nextPrompt = systemErrorContent;
5502
- } else if (waitingForUser) {
5503
- const userReply = await tui.text({ message: "Your answer", placeholder: "Type your answer..." });
5504
- if (tui.isCancel(userReply)) {
5505
- keepGoing = false;
5506
- return;
5507
- }
5508
- FileLogger.log("USER_INPUT", "User response to specification agent", { userReply });
5509
- nextPrompt = `${executionResults}
5510
-
5511
- User Reply: ${userReply}`;
5512
- } else if (executionResults) {
5513
- const content = fs12.existsSync(targetPath) ? fs12.readFileSync(targetPath, "utf-8") : "";
5514
- let systemMsg = "Execu\xE7\xE3o da ferramenta conclu\xEDda.";
5515
- if (specUpdated) {
5516
- if (content.includes("[TO BE")) {
5517
- const pendingMatches = [...content.matchAll(/## ([^\n]+)[\s\S]*?\[TO BE/g)].map((m) => m[1]);
5518
- let missing = pendingMatches.length > 0 ? pendingMatches.join(", ") : "v\xE1rias se\xE7\xF5es";
5519
- systemMsg += `
5520
- [System]: Se\xE7\xE3o atualizada com sucesso. A valida\xE7\xE3o detectou que AINDA H\xC1 placeholders pendentes ('[TO BE...]') nas seguintes se\xE7\xF5es: ${missing}.
5521
- Por favor, envie uma nova action \`modify_file\` focada em uma destas se\xE7\xF5es obrigatoriamente. USE o respectivo placeholder no campo \`target_content\` para que o replace funcione.`;
5522
- } else {
5523
- systemMsg += "\n[System]: O arquivo parece completo! Se estiver satisfeito e possuir TODAS as implementa\xE7\xF5es descritas, retorne 'SPEC_UPDATED: Complete'.";
5524
- }
5525
- } else {
5526
- systemMsg += "\n[System]: A modifica\xE7\xE3o do arquivo falhou. Verifique se o `target_content` que voc\xEA usou existe EXATAMENTE como no arquivo e se ele \xE9 \xDANICO na hora de usar a action `modify_file`.";
5527
- }
5528
- nextPrompt = `${executionResults}
5529
-
5530
- ${systemMsg}`;
5531
- } else {
5532
- if (lastResponse.message) {
5533
- tui.log.info(colors.primary("\u{1F916} Architect (Message only):"));
5534
- console.log(lastResponse.message);
5535
- const userReply = await tui.text({ message: "Your answer:" });
5536
- if (tui.isCancel(userReply)) {
5537
- keepGoing = false;
5538
- break;
5539
- }
5540
- FileLogger.log("USER_INPUT", "User response to specification agent (Message only)", { userReply });
5541
- nextPrompt = userReply;
5542
- } else {
5543
- keepGoing = false;
5544
- }
4507
+ });
5545
4508
  }
5546
- } else {
5547
- tui.log.warning("No actions received.");
5548
- keepGoing = false;
4509
+ } catch (e) {
4510
+ console.error("Error parsing memory graph files:", e);
5549
4511
  }
5550
- } catch (error) {
5551
- spinner.stop("Error");
5552
- tui.log.error(error.message);
5553
- keepGoing = false;
4512
+ const activeNodeList = Date.now() - activeNodesTimestamp < 15e3 ? activeNodes : [];
4513
+ res.writeHead(200, { "Content-Type": "application/json" });
4514
+ res.end(JSON.stringify({ nodes, edges, activeNodes: activeNodeList }));
4515
+ return;
5554
4516
  }
5555
- }
5556
- }
5557
- async function callSpecAgentApi(prompt, onChunk, agentId) {
5558
- const conversationId = await conversationManager.getConversationId(AGENT_TYPE2);
5559
- FileLogger.log("AGENT", "Calling Agent API", { agentId, conversationId });
5560
- const provider = ProviderResolver.getProvider("specification_agent");
5561
- if (agentId && "agentId" in provider) {
5562
- provider.agentId = agentId;
5563
- }
5564
- const parsed = await provider.streamChat(prompt, {
5565
- conversationId,
5566
- agentType: "specification_agent",
5567
- onChunk
4517
+ res.writeHead(404, { "Content-Type": "text/plain" });
4518
+ res.end("Not Found");
5568
4519
  });
5569
- if (parsed.conversation_id) {
5570
- await conversationManager.saveConversationId(AGENT_TYPE2, parsed.conversation_id);
5571
- }
5572
- return parsed;
5573
- }
5574
-
5575
- // src/commands/legacy.ts
5576
- var legacyCommand = new Command3("legacy").description("Starts the Legacy Developer Agent task orchestration loop").option("-t, --task <type>", "Initial task description (Quick Mode)").option("-c, --context <path>", "Path to custom context file").action(async (options) => {
5577
- const taskManager = new TaskManager(process.cwd());
5578
- let state = taskManager.analyzeSpecState();
5579
- if (state.status === "MISSING") {
5580
- tui.log.warning("\u{1F4CB} No tech-spec.md found.");
5581
- const confirm = await tui.confirm({ message: "Create a new Specification (Tech Spec)?" });
5582
- if (confirm) {
5583
- await interactiveSpecificationAgent({
5584
- briefingPath: options.task ? void 0 : void 0
5585
- });
5586
- state = taskManager.analyzeSpecState();
5587
- if (state.status === "MISSING") {
5588
- tui.log.error("\u274C Spec creation aborted or failed.");
5589
- return;
4520
+ const startServer = (p) => {
4521
+ server.listen(p, () => {
4522
+ const url = `http://localhost:${p}/?mode=${mode}`;
4523
+ console.log(colors.success(`\u{1F680} Memory Graph Server active at: ${url}`));
4524
+ const runDir = path11.join(process.cwd(), ".shark", "membox");
4525
+ if (!fs11.existsSync(runDir)) {
4526
+ fs11.mkdirSync(runDir, { recursive: true });
5590
4527
  }
5591
- } else {
5592
- return;
5593
- }
5594
- }
5595
- let keepOrchestrating = true;
5596
- let burnMode = false;
5597
- let contextHistory = "";
5598
- tui.intro("\u{1F988} Shark Legacy Orchestrator");
5599
- while (keepOrchestrating) {
5600
- state = taskManager.analyzeSpecState();
5601
- if (state.status === "COMPLETED") {
5602
- tui.log.success("\u{1F389} All tasks in tech-spec.md are COMPLETED!");
5603
- const choice = await tui.select({
5604
- message: "What next?",
5605
- options: [
5606
- { value: "exit", label: "Exit" },
5607
- { value: "new_spec", label: "New Specification (Reset)" }
5608
- ]
5609
- });
5610
- if (choice === "new_spec") {
5611
- await interactiveSpecificationAgent();
5612
- contextHistory = "";
5613
- continue;
4528
+ fs11.writeFileSync(
4529
+ path11.join(runDir, "graph-server.json"),
4530
+ JSON.stringify({ active: true, port: p, timestamp: Date.now() }),
4531
+ "utf-8"
4532
+ );
4533
+ if (process.platform === "win32") {
4534
+ exec3(`start "" "${url}"`);
4535
+ } else if (process.platform === "darwin") {
4536
+ exec3(`open "${url}"`);
5614
4537
  } else {
5615
- keepOrchestrating = false;
5616
- break;
5617
- }
5618
- }
5619
- const currentTask = state.nextTask;
5620
- if (!currentTask) {
5621
- tui.log.error("Something went wrong. Status is not completed but no next task found.");
5622
- break;
5623
- }
5624
- tui.log.info(`
5625
- \u{1F449} **NEXT TASK**: ${colors.bold(currentTask.description)}`);
5626
- if (!burnMode) {
5627
- const action = await tui.select({
5628
- message: "Orchestration Checkpoint:",
5629
- options: [
5630
- { value: "execute", label: "\u{1F680} Execute Task (Start)" },
5631
- { value: "burn", label: "\u{1F525} Burn Mode (Auto-Execute Remaining)" },
5632
- { value: "pivot", label: "\u{1F527} Pivot/Correct (Edit Spec)" },
5633
- { value: "skip", label: "\u23ED\uFE0F Skip Task (Mark Done without Executing)" },
5634
- { value: "stop", label: "\u{1F6D1} Stop Session" }
5635
- ]
5636
- });
5637
- if (action === "stop") {
5638
- keepOrchestrating = false;
5639
- break;
5640
- } else if (action === "burn") {
5641
- burnMode = true;
5642
- } else if (action === "skip") {
5643
- taskManager.markTaskAsDone(currentTask.id);
5644
- tui.log.info("Task skipped.");
5645
- continue;
5646
- } else if (action === "pivot") {
5647
- tui.log.info("Transferring control to Specification Agent...");
5648
- await interactiveSpecificationAgent({
5649
- initialContext: `User requested pivot after tasks:
5650
- ${contextHistory}`
5651
- });
5652
- continue;
4538
+ exec3(`xdg-open "${url}"`);
5653
4539
  }
5654
- }
5655
- taskManager.markTaskInProgress(currentTask.id);
5656
- tui.log.info(`\u26A1 Starting Micro-Context for Task: "${currentTask.description}"`);
5657
- const result = await interactiveDeveloperAgent2({
5658
- taskId: currentTask.id,
5659
- taskInstruction: currentTask.description,
5660
- history: contextHistory,
5661
- context: options.context
5662
4540
  });
5663
- if (result.success) {
5664
- tui.log.success(`\u2705 Task Completed: ${currentTask.description}`);
5665
- taskManager.markTaskAsDone(currentTask.id);
5666
- contextHistory += `
5667
- [Task "${currentTask.description}" completed]: ${result.summary}`;
4541
+ };
4542
+ server.on("error", (err) => {
4543
+ if (err.code === "EADDRINUSE") {
4544
+ console.log(colors.secondary(`\u26A0\uFE0F Port ${port} is busy. Retrying next port...`));
4545
+ port++;
4546
+ startServer(port);
5668
4547
  } else {
5669
- tui.log.error(`\u274C Task Failed: ${result.summary}`);
5670
- burnMode = false;
5671
- const recovery = await tui.select({
5672
- message: "Task Failed. Recovery Action:",
5673
- options: [
5674
- { value: "retry", label: "Retry (Run Agent Again)" },
5675
- { value: "pivot", label: "Pivot (Adjust Spec/Instructions)" },
5676
- { value: "ignore", label: "Ignore (Mark Done anyway)" },
5677
- { value: "stop", label: "Stop" }
5678
- ]
5679
- });
5680
- if (recovery === "stop") break;
5681
- if (recovery === "ignore") taskManager.markTaskAsDone(currentTask.id);
5682
- if (recovery === "pivot") {
5683
- await interactiveSpecificationAgent({
5684
- initialContext: `Task "${currentTask.description}" FAILED.
5685
- Error: ${result.summary}
5686
- History:
5687
- ${contextHistory}`
5688
- });
5689
- }
4548
+ console.error(colors.error("\u274C Server startup error:"), err);
5690
4549
  }
5691
- }
5692
- tui.outro("\u{1F988} Legacy Orchestration Finished.");
5693
- });
5694
-
5695
- // src/commands/export-schema.ts
5696
- import { Command as Command4 } from "commander";
5697
- var exportSchemaCommand = new Command4("export-schema").description("Outputs the agent response JSON Schema").action(() => {
5698
- console.log(JSON.stringify(AGENT_RESPONSE_JSON_SCHEMA, null, 2));
5699
- });
5700
-
5701
- // src/commands/export-prompt.ts
5702
- import { Command as Command5 } from "commander";
5703
- var exportPromptCommand = new Command5("export-prompt").description("Outputs the unified agent system prompt").action(() => {
5704
- console.log(UNIFIED_SYSTEM_PROMPT);
5705
- });
5706
-
5707
- // src/commands/super.ts
5708
- import { Command as Command6 } from "commander";
5709
- import { fileURLToPath as fileURLToPath3 } from "url";
5710
- import path14 from "path";
5711
- import os3 from "os";
5712
- import fs13 from "fs/promises";
5713
- var superCommandAction = async (options = {}) => {
5714
- const __filename2 = fileURLToPath3(import.meta.url);
5715
- const __dirname2 = path14.dirname(__filename2);
5716
- const packageRoot = path14.resolve(__dirname2, "../../");
5717
- const internalSkillsPath = path14.join(packageRoot, "skills");
5718
- const targetPath = options.local ? path14.join(process.cwd(), ".agents", "skills") : path14.join(os3.homedir(), ".shark", "skills");
5719
- try {
4550
+ });
4551
+ const cleanup = () => {
5720
4552
  try {
5721
- await fs13.rm(targetPath, { recursive: true, force: true });
4553
+ const runDir = path11.join(process.cwd(), ".shark", "membox");
4554
+ const file = path11.join(runDir, "graph-server.json");
4555
+ if (fs11.existsSync(file)) {
4556
+ fs11.unlinkSync(file);
4557
+ }
5722
4558
  } catch {
5723
4559
  }
5724
- await fs13.mkdir(targetPath, { recursive: true });
5725
- await fs13.cp(internalSkillsPath, targetPath, { recursive: true, force: true });
5726
- console.log(`\u{1F680} Superpowers skills installed successfully to ${targetPath}`);
5727
- } catch (error) {
5728
- console.error(`\u274C Failed to install superpowers skills: ${error.message}`);
5729
- process.exit(1);
5730
- }
5731
- };
5732
- 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);
4560
+ };
4561
+ process.on("exit", cleanup);
4562
+ process.on("SIGINT", () => {
4563
+ cleanup();
4564
+ process.exit(0);
4565
+ });
4566
+ process.on("SIGTERM", () => {
4567
+ cleanup();
4568
+ process.exit(0);
4569
+ });
4570
+ startServer(port);
4571
+ });
5733
4572
 
5734
4573
  // src/bin/shark.ts
5735
4574
  crashHandler.init();
@@ -5738,10 +4577,10 @@ program.name("shark").description("Shark CLI: AI-Native Collaborative Developmen
5738
4577
  program.addCommand(loginCommand);
5739
4578
  program.addCommand(initCommand);
5740
4579
  program.addCommand(devCommand);
5741
- program.addCommand(legacyCommand);
5742
4580
  program.addCommand(exportSchemaCommand);
5743
4581
  program.addCommand(exportPromptCommand);
5744
4582
  program.addCommand(superCommand);
4583
+ program.addCommand(graphCommand);
5745
4584
  program.command("config").description("Manage global configuration").action(configCommand.action);
5746
4585
  process.on("unhandledRejection", (err) => {
5747
4586
  console.error(colors.error("\u274C Unhandled Error:"), err);