openfox 1.6.13 → 1.6.15

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.
Files changed (41) hide show
  1. package/dist/{ask-JQQNGB5K.js → ask-3RK5YJZE.js} +2 -2
  2. package/dist/{auto-compaction-QPFQEWJG.js → auto-compaction-JZIM73ZY.js} +6 -6
  3. package/dist/{chat-handler-FDHHTHS3.js → chat-handler-F2A2IMQB.js} +27 -57
  4. package/dist/{chunk-5LDTVERY.js → chunk-22CTURMH.js} +1 -3
  5. package/dist/{chunk-WQ4W5H6A.js → chunk-2KP34IDL.js} +163 -205
  6. package/dist/{chunk-POJO7A5H.js → chunk-3WHZ47PY.js} +511 -793
  7. package/dist/{chunk-XFXOSPYH.js → chunk-55N6FAAZ.js} +1 -1
  8. package/dist/{chunk-ZRDRYL6W.js → chunk-6L3X7T4K.js} +82 -160
  9. package/dist/{chunk-QDEKU5RL.js → chunk-F54ZJN4X.js} +38 -2
  10. package/dist/{chunk-7T2CA2C6.js → chunk-G4E72ST3.js} +117 -242
  11. package/dist/{chunk-AKFLBPPY.js → chunk-IN5EP4ZB.js} +2 -2
  12. package/dist/{chunk-HCZT5BDI.js → chunk-JFKYCGZ5.js} +6 -6
  13. package/dist/{chunk-ZHGP4PBJ.js → chunk-KOUMYBYM.js} +53 -112
  14. package/dist/{chunk-E6FCNB3B.js → chunk-OVLFEBRR.js} +76 -94
  15. package/dist/chunk-SN7OBEVL.js +44 -0
  16. package/dist/{chunk-LMIBGLOB.js → chunk-YM6VHAPM.js} +4 -4
  17. package/dist/{chunk-TXQNRBMZ.js → chunk-ZDNXCVW4.js} +2 -2
  18. package/dist/cli/dev.js +1 -1
  19. package/dist/cli/index.js +1 -1
  20. package/dist/{config-TUA644W4.js → config-67AX6CNS.js} +5 -5
  21. package/dist/{events-EILPWKYE.js → events-2ETDOE5B.js} +3 -3
  22. package/dist/{folding-NZYOXNKK.js → folding-M7FMUBOL.js} +6 -4
  23. package/dist/{orchestrator-YKQJI3PH.js → orchestrator-SRSG2SIZ.js} +7 -7
  24. package/dist/package.json +14 -3
  25. package/dist/{processor-JU7MSV4F.js → processor-H5BQ5HQM.js} +24 -49
  26. package/dist/{provider-FNSGWDIW.js → provider-DKGBQHUS.js} +7 -7
  27. package/dist/{serve-O62TKFVA.js → serve-5AY7GMN7.js} +13 -13
  28. package/dist/server/index.d.ts +2 -0
  29. package/dist/server/index.js +11 -11
  30. package/dist/{tools-JWAF3MAR.js → tools-VOWNOP6V.js} +6 -6
  31. package/dist/{vision-fallback-BWCKA2FZ.js → vision-fallback-ADYRFFD4.js} +2 -2
  32. package/dist/web/assets/{index-MCWDS5UQ.css → index-CNeIjdxm.css} +1 -1
  33. package/dist/web/assets/index-mBLhctLW.js +150 -0
  34. package/dist/web/index.html +2 -2
  35. package/dist/web/sw.js +1 -1
  36. package/package.json +14 -3
  37. package/dist/web/assets/index-Cp8gfbgP.js +0 -150
  38. /package/dist/{command-defaults → server/commands/defaults}/commit-push.command.md +0 -0
  39. /package/dist/{command-defaults → server/commands/defaults}/init.command.md +0 -0
  40. /package/dist/{command-defaults → server/commands/defaults}/test-ui.command.md +0 -0
  41. /package/dist/{skill-defaults → server/skills/defaults}/browser.skill.md +0 -0
@@ -13,12 +13,12 @@ import {
13
13
  } from "./chunk-574HZVLE.js";
14
14
  import {
15
15
  streamWithSegments
16
- } from "./chunk-XFXOSPYH.js";
16
+ } from "./chunk-55N6FAAZ.js";
17
17
  import {
18
18
  getContextMessages,
19
19
  getCurrentContextWindowId,
20
20
  getEventStore
21
- } from "./chunk-ZRDRYL6W.js";
21
+ } from "./chunk-6L3X7T4K.js";
22
22
  import {
23
23
  createChatDoneMessage,
24
24
  createChatMessageMessage,
@@ -31,7 +31,7 @@ import {
31
31
  import {
32
32
  AskUserInterrupt,
33
33
  askUserTool
34
- } from "./chunk-5LDTVERY.js";
34
+ } from "./chunk-22CTURMH.js";
35
35
  import {
36
36
  SETTINGS_KEYS,
37
37
  getSetting,
@@ -375,6 +375,31 @@ You may use available tools to read files and verify changes if needed.`;
375
375
  var FORMAT_CORRECTION_PROMPT = `IMPORTANT: You MUST use the JSON function calling API. Do NOT output XML tags like <tool_call>, <function=>, or <parameter=>. Your previous attempt was stopped because you used the wrong format. Use the proper tool_calls format.`;
376
376
 
377
377
  // src/server/chat/request-context.ts
378
+ function minimalMessagesToRequestContextMessages(messages, source = "history") {
379
+ return messages.map((message) => minimalMessageToRequestContextMessage(message, source));
380
+ }
381
+ function spreadMessageProps(message) {
382
+ return {
383
+ ...message.toolCalls ? { toolCalls: message.toolCalls } : {},
384
+ ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
385
+ ...message.attachments ? { attachments: message.attachments } : {}
386
+ };
387
+ }
388
+ function minimalMessageToRequestContextMessage(message, source = "history") {
389
+ return {
390
+ role: message.role,
391
+ content: message.content,
392
+ source,
393
+ ...spreadMessageProps(message)
394
+ };
395
+ }
396
+ function messageToMinimal(message) {
397
+ return {
398
+ role: message.role,
399
+ content: message.content,
400
+ ...spreadMessageProps(message)
401
+ };
402
+ }
378
403
  function getTriggerUserMessage(messages) {
379
404
  const stripRuntimeReminders = (content) => {
380
405
  return content.replace(/\n*<system-reminder>[\s\S]*<\/system-reminder>\s*/gi, "").trim();
@@ -396,12 +421,8 @@ function createPromptContext(input) {
396
421
  injectedFiles: input.injectedFiles,
397
422
  userMessage: input.userMessage ?? getTriggerUserMessage(input.messages),
398
423
  messages: input.messages.map((message) => ({
399
- role: message.role,
400
- content: message.content,
401
- source: message.source,
402
- ...message.toolCalls ? { toolCalls: message.toolCalls } : {},
403
- ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
404
- ...message.attachments ? { attachments: message.attachments } : {}
424
+ ...messageToMinimal(message),
425
+ source: message.source
405
426
  })),
406
427
  tools: input.requestTools.map((tool) => ({
407
428
  name: tool.function.name,
@@ -419,19 +440,25 @@ function createAssemblyResult(input) {
419
440
  const messagesForLLM = input.messages.filter((m) => m.source !== "runtime");
420
441
  return {
421
442
  systemPrompt: input.systemPrompt,
422
- messages: messagesForLLM.map((message) => ({
423
- role: message.role,
424
- content: message.content,
425
- ...message.toolCalls ? { toolCalls: message.toolCalls } : {},
426
- ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
427
- ...message.attachments ? { attachments: message.attachments } : {}
428
- })),
443
+ messages: messagesForLLM.map((message) => messageToMinimal(message)),
429
444
  promptContext: createPromptContext({
430
445
  ...input,
431
446
  userMessage: triggerUserMessage
432
447
  })
433
448
  };
434
449
  }
450
+ function buildAssemblyInput(systemPrompt, baseInput) {
451
+ return createAssemblyResult({
452
+ systemPrompt,
453
+ messages: baseInput.messages,
454
+ injectedFiles: baseInput.injectedFiles,
455
+ requestTools: baseInput.requestTools ?? baseInput.promptTools,
456
+ toolChoice: baseInput.toolChoice ?? "auto",
457
+ disableThinking: baseInput.disableThinking ?? false,
458
+ ...baseInput.customInstructions ? { customInstructions: baseInput.customInstructions } : {},
459
+ ...baseInput.skills && baseInput.skills.length > 0 ? { skills: baseInput.skills } : {}
460
+ });
461
+ }
435
462
  function assembleAgentRequest(input) {
436
463
  const { agentDef, subAgentDefs, ...baseInput } = input;
437
464
  if (agentDef.metadata.subagent) {
@@ -440,14 +467,7 @@ function assembleAgentRequest(input) {
440
467
  agentDef,
441
468
  baseInput.skills
442
469
  );
443
- return createAssemblyResult({
444
- systemPrompt: systemPrompt2,
445
- messages: baseInput.messages,
446
- injectedFiles: baseInput.injectedFiles,
447
- requestTools: baseInput.requestTools ?? baseInput.promptTools,
448
- toolChoice: baseInput.toolChoice ?? "auto",
449
- disableThinking: baseInput.disableThinking ?? false
450
- });
470
+ return buildAssemblyInput(systemPrompt2, baseInput);
451
471
  }
452
472
  const systemPrompt = buildTopLevelSystemPrompt(
453
473
  baseInput.workdir,
@@ -455,14 +475,25 @@ function assembleAgentRequest(input) {
455
475
  baseInput.skills,
456
476
  subAgentDefs
457
477
  );
458
- return createAssemblyResult({
459
- systemPrompt,
460
- messages: baseInput.messages,
461
- injectedFiles: baseInput.injectedFiles,
462
- requestTools: baseInput.requestTools ?? baseInput.promptTools,
463
- toolChoice: baseInput.toolChoice ?? "auto",
464
- disableThinking: baseInput.disableThinking ?? false
465
- });
478
+ return buildAssemblyInput(systemPrompt, baseInput);
479
+ }
480
+
481
+ // src/server/chat/stream-utils.ts
482
+ function buildStreamRequestObject(params) {
483
+ const { messages, tools, toolChoice, disableThinking, signal, onVisionFallbackStart, onVisionFallbackDone } = params;
484
+ const streamRequest = {
485
+ messages,
486
+ ...tools && { tools },
487
+ ...toolChoice && { toolChoice },
488
+ disableThinking: disableThinking ?? false,
489
+ ...signal && { signal }
490
+ };
491
+ if (onVisionFallbackStart) streamRequest.onVisionFallbackStart = onVisionFallbackStart;
492
+ if (onVisionFallbackDone) streamRequest.onVisionFallbackDone = onVisionFallbackDone;
493
+ return streamRequest;
494
+ }
495
+ function buildStreamRequest(client, options) {
496
+ return streamWithSegments(client, buildStreamRequestObject(options));
466
497
  }
467
498
 
468
499
  // src/server/chat/stats.ts
@@ -483,19 +514,29 @@ function computeAggregatedStats(input) {
483
514
  }
484
515
 
485
516
  // src/server/chat/stream-pure.ts
517
+ function createEmptyStreamResult(aborted, xmlFormatError) {
518
+ return {
519
+ content: "",
520
+ toolCalls: [],
521
+ segments: [],
522
+ usage: { promptTokens: 0, completionTokens: 0 },
523
+ timing: { ttft: 0, completionTime: 0, tps: 0, prefillTps: 0 },
524
+ aborted,
525
+ xmlFormatError
526
+ };
527
+ }
486
528
  async function* streamLLMPure(options) {
487
529
  const { messageId, systemPrompt, llmClient, messages, tools, toolChoice, signal, disableThinking, onVisionFallbackStart, onVisionFallbackDone } = options;
488
530
  const llmMessages = [{ role: "system", content: systemPrompt }, ...messages];
489
- const streamRequest = {
531
+ const stream = buildStreamRequest(llmClient, {
490
532
  messages: llmMessages,
491
- ...tools && { tools },
492
- ...tools && { toolChoice: toolChoice ?? "auto" },
533
+ tools,
534
+ toolChoice,
493
535
  disableThinking: disableThinking ?? false,
494
- ...signal && { signal }
495
- };
496
- if (onVisionFallbackStart) streamRequest.onVisionFallbackStart = onVisionFallbackStart;
497
- if (onVisionFallbackDone) streamRequest.onVisionFallbackDone = onVisionFallbackDone;
498
- const stream = streamWithSegments(llmClient, streamRequest);
536
+ signal,
537
+ onVisionFallbackStart,
538
+ onVisionFallbackDone
539
+ });
499
540
  const seenToolIndices = /* @__PURE__ */ new Set();
500
541
  const toolNames = /* @__PURE__ */ new Map();
501
542
  const toolIds = /* @__PURE__ */ new Map();
@@ -594,15 +635,7 @@ async function* streamLLMPure(options) {
594
635
  }
595
636
  }
596
637
  if (!result) {
597
- return {
598
- content: "",
599
- toolCalls: [],
600
- segments: [],
601
- usage: { promptTokens: 0, completionTokens: 0 },
602
- timing: { ttft: 0, completionTime: 0, tps: 0, prefillTps: 0 },
603
- aborted,
604
- xmlFormatError
605
- };
638
+ return createEmptyStreamResult(aborted, xmlFormatError);
606
639
  }
607
640
  const baseResult = {
608
641
  content: result.content,
@@ -768,15 +801,7 @@ async function consumeStreamWithToolLoop(options) {
768
801
  let iterations = 0;
769
802
  for (; ; ) {
770
803
  if (signal?.aborted) {
771
- return {
772
- content: "",
773
- toolCalls: [],
774
- segments: [],
775
- usage: { promptTokens: 0, completionTokens: 0 },
776
- timing: { ttft: 0, completionTime: 0, tps: 0, prefillTps: 0 },
777
- aborted: true,
778
- xmlFormatError: false
779
- };
804
+ return createEmptyStreamResult(true, false);
780
805
  }
781
806
  if (++iterations > MAX_TOOL_LOOP_ITERATIONS) {
782
807
  throw new Error("Max tool loop iterations exceeded during compaction");
@@ -844,7 +869,7 @@ async function consumeStreamWithToolLoop(options) {
844
869
  }
845
870
  async function executeToolBatchWithContext(assistantMsgId, toolCalls, ctx, onEvent) {
846
871
  const toolMessages = [];
847
- let criteriaChanged = false;
872
+ const criteriaChanged = false;
848
873
  for (const toolCall of toolCalls) {
849
874
  if (ctx.signal?.aborted) {
850
875
  throw new Error("Aborted");
@@ -884,17 +909,48 @@ Error: ${toolResult.error}` : `Error: ${toolResult.error}`;
884
909
  }
885
910
 
886
911
  // src/server/agents/registry.ts
887
- import { readdir, readFile as readFile2, writeFile, copyFile, mkdir, access as access2, unlink } from "fs/promises";
888
- import { join as join2, dirname as dirname2 } from "path";
912
+ import { readdir, readFile as readFile3, writeFile, copyFile, mkdir, access as access2, unlink } from "fs/promises";
913
+ import { join as join3, dirname as dirname2 } from "path";
889
914
  import { constants as constants2 } from "fs";
890
915
  import { fileURLToPath } from "url";
891
916
  import matter from "gray-matter";
917
+
918
+ // src/server/utils/defaults.ts
919
+ import { readFile as readFile2 } from "fs/promises";
920
+ import { join as join2 } from "path";
921
+ async function findModifiedDefaultFiles(defaultIds, extension, bundledDirs, userDir) {
922
+ const [primaryDir, altDir] = bundledDirs;
923
+ const modified = [];
924
+ for (const id of defaultIds) {
925
+ const filename = `${id}${extension}`;
926
+ const userPath = join2(userDir, filename);
927
+ let bundledContent = null;
928
+ for (const dir of [primaryDir, altDir]) {
929
+ try {
930
+ bundledContent = await readFile2(join2(dir, filename), "utf-8");
931
+ break;
932
+ } catch {
933
+ }
934
+ }
935
+ if (!bundledContent) continue;
936
+ try {
937
+ const userContent = await readFile2(userPath, "utf-8");
938
+ if (userContent !== bundledContent) {
939
+ modified.push(id);
940
+ }
941
+ } catch {
942
+ }
943
+ }
944
+ return modified;
945
+ }
946
+
947
+ // src/server/agents/registry.ts
892
948
  var __bundleDir = dirname2(fileURLToPath(import.meta.url));
893
- var DEFAULTS_DIR = join2(__bundleDir, "defaults");
894
- var DEFAULTS_DIR_ALT = join2(__bundleDir, "agent-defaults");
949
+ var DEFAULTS_DIR = join3(__bundleDir, "defaults");
950
+ var DEFAULTS_DIR_ALT = join3(__bundleDir, "agent-defaults");
895
951
  var AGENT_EXTENSION = ".agent.md";
896
952
  function getAgentsDir(configDir) {
897
- return join2(configDir, "agents");
953
+ return join3(configDir, "agents");
898
954
  }
899
955
  async function pathExists(path) {
900
956
  try {
@@ -924,10 +980,10 @@ async function ensureDefaultAgents(configDir) {
924
980
  }
925
981
  }
926
982
  for (const file of defaultFiles) {
927
- const targetPath = join2(agentsDir, file);
983
+ const targetPath = join3(agentsDir, file);
928
984
  if (!await pathExists(targetPath)) {
929
985
  try {
930
- await copyFile(join2(sourceDir, file), targetPath);
986
+ await copyFile(join3(sourceDir, file), targetPath);
931
987
  logger.info("Installed default agent", { file });
932
988
  } catch (err) {
933
989
  logger.error("Failed to copy default agent", { file, error: err instanceof Error ? err.message : String(err) });
@@ -966,7 +1022,7 @@ async function loadAgentsFromDir(dir) {
966
1022
  const agents = [];
967
1023
  for (const file of files) {
968
1024
  try {
969
- const raw = await readFile2(join2(dir, file), "utf-8");
1025
+ const raw = await readFile3(join3(dir, file), "utf-8");
970
1026
  const agent = parseAgentFile(raw, file);
971
1027
  if (agent) {
972
1028
  agents.push(agent);
@@ -1017,7 +1073,7 @@ async function getDefaultAgentIds() {
1017
1073
  async function restoreDefaultAgent(configDir, agentId) {
1018
1074
  const defaultIds = await getDefaultAgentIds();
1019
1075
  if (!defaultIds.includes(agentId)) return false;
1020
- const userPath = join2(getAgentsDir(configDir), `${agentId}${AGENT_EXTENSION}`);
1076
+ const userPath = join3(getAgentsDir(configDir), `${agentId}${AGENT_EXTENSION}`);
1021
1077
  try {
1022
1078
  await unlink(userPath);
1023
1079
  } catch {
@@ -1026,28 +1082,7 @@ async function restoreDefaultAgent(configDir, agentId) {
1026
1082
  }
1027
1083
  async function getModifiedDefaultAgentIds(configDir) {
1028
1084
  const defaultIds = await getDefaultAgentIds();
1029
- const modified = [];
1030
- for (const id of defaultIds) {
1031
- const filename = `${id}${AGENT_EXTENSION}`;
1032
- const userPath = join2(getAgentsDir(configDir), filename);
1033
- let bundledContent = null;
1034
- for (const dir of [DEFAULTS_DIR, DEFAULTS_DIR_ALT]) {
1035
- try {
1036
- bundledContent = await readFile2(join2(dir, filename), "utf-8");
1037
- break;
1038
- } catch {
1039
- }
1040
- }
1041
- if (!bundledContent) continue;
1042
- try {
1043
- const userContent = await readFile2(userPath, "utf-8");
1044
- if (userContent !== bundledContent) {
1045
- modified.push(id);
1046
- }
1047
- } catch {
1048
- }
1049
- }
1050
- return modified;
1085
+ return findModifiedDefaultFiles(defaultIds, AGENT_EXTENSION, [DEFAULTS_DIR, DEFAULTS_DIR_ALT], getAgentsDir(configDir));
1051
1086
  }
1052
1087
  async function restoreAllDefaultAgents(configDir) {
1053
1088
  const ids = await getDefaultAgentIds();
@@ -1057,6 +1092,13 @@ async function restoreAllDefaultAgents(configDir) {
1057
1092
  }
1058
1093
  return count;
1059
1094
  }
1095
+ function getAgentFilePaths(agentsDir, agentId) {
1096
+ const hyphenated = agentId.replace(/_/g, "-");
1097
+ return [
1098
+ join3(agentsDir, `${agentId}${AGENT_EXTENSION}`),
1099
+ join3(agentsDir, `${hyphenated}${AGENT_EXTENSION}`)
1100
+ ];
1101
+ }
1060
1102
  function findAgentById(agentId, agents) {
1061
1103
  return agents.find((a) => a.metadata.id === agentId);
1062
1104
  }
@@ -1065,11 +1107,7 @@ function getSubAgents(agents) {
1065
1107
  }
1066
1108
  async function agentExists(configDir, agentId) {
1067
1109
  const agentsDir = getAgentsDir(configDir);
1068
- const hyphenated = agentId.replace(/_/g, "-");
1069
- const paths = [
1070
- join2(agentsDir, `${agentId}${AGENT_EXTENSION}`),
1071
- join2(agentsDir, `${hyphenated}${AGENT_EXTENSION}`)
1072
- ];
1110
+ const paths = getAgentFilePaths(agentsDir, agentId);
1073
1111
  for (const filePath of paths) {
1074
1112
  if (await pathExists(filePath)) {
1075
1113
  return true;
@@ -1082,17 +1120,13 @@ async function saveAgent(configDir, agent) {
1082
1120
  if (!await pathExists(agentsDir)) {
1083
1121
  await mkdir(agentsDir, { recursive: true });
1084
1122
  }
1085
- const filePath = join2(agentsDir, `${agent.metadata.id}${AGENT_EXTENSION}`);
1123
+ const filePath = join3(agentsDir, `${agent.metadata.id}${AGENT_EXTENSION}`);
1086
1124
  const content = matter.stringify(agent.prompt, agent.metadata);
1087
1125
  await writeFile(filePath, content, "utf-8");
1088
1126
  }
1089
1127
  async function deleteAgent(configDir, agentId) {
1090
1128
  const agentsDir = getAgentsDir(configDir);
1091
- const hyphenated = agentId.replace(/_/g, "-");
1092
- const paths = [
1093
- join2(agentsDir, `${agentId}${AGENT_EXTENSION}`),
1094
- join2(agentsDir, `${hyphenated}${AGENT_EXTENSION}`)
1095
- ];
1129
+ const paths = getAgentFilePaths(agentsDir, agentId);
1096
1130
  for (const filePath of paths) {
1097
1131
  try {
1098
1132
  await unlink(filePath);
@@ -1105,7 +1139,7 @@ async function deleteAgent(configDir, agentId) {
1105
1139
  }
1106
1140
 
1107
1141
  // src/server/tools/read.ts
1108
- import { readFile as readFile4, stat as stat2 } from "fs/promises";
1142
+ import { readFile as readFile5, stat as stat2 } from "fs/promises";
1109
1143
  import { extname } from "path";
1110
1144
 
1111
1145
  // src/server/tools/types.ts
@@ -1136,7 +1170,7 @@ import { resolve as resolve2, isAbsolute } from "path";
1136
1170
 
1137
1171
  // src/server/tools/path-security.ts
1138
1172
  import { realpath } from "fs/promises";
1139
- import { resolve, normalize, join as join3, basename, sep } from "path";
1173
+ import { resolve, normalize, join as join4, basename, sep } from "path";
1140
1174
  import { homedir, tmpdir } from "os";
1141
1175
  var SAFE_PATHS = /* @__PURE__ */ new Set([
1142
1176
  "/dev/null",
@@ -1271,7 +1305,7 @@ function extractAbsolutePathsFromCommand(command) {
1271
1305
  }
1272
1306
  } else if (content.startsWith("~")) {
1273
1307
  const pathPart = content.slice(1);
1274
- const fullPath = join3(home, pathPart);
1308
+ const fullPath = join4(home, pathPart);
1275
1309
  const resolved = normalize(resolve(fullPath));
1276
1310
  if (!isSafePath(resolved)) {
1277
1311
  paths.push(resolved);
@@ -1416,9 +1450,6 @@ var PathAccessDeniedError = class extends Error {
1416
1450
  this.reason = reason;
1417
1451
  this.name = "PathAccessDeniedError";
1418
1452
  }
1419
- paths;
1420
- tool;
1421
- reason;
1422
1453
  };
1423
1454
  var pendingConfirmations = /* @__PURE__ */ new Map();
1424
1455
  function registerPathConfirmation(callId, paths, sessionId) {
@@ -1464,6 +1495,39 @@ function cancelPathConfirmationsForSession(sessionId, reason) {
1464
1495
  }
1465
1496
 
1466
1497
  // src/server/tools/tool-helpers.ts
1498
+ function validateAction(action, allowed, startTime) {
1499
+ if (!action || !allowed.includes(action)) {
1500
+ return {
1501
+ success: false,
1502
+ error: `Invalid action: ${action}. Must be one of: ${allowed.join(", ")}`,
1503
+ durationMs: Date.now() - startTime,
1504
+ truncated: false
1505
+ };
1506
+ }
1507
+ return void 0;
1508
+ }
1509
+ function checkActionPermission(action, permittedActions, startTime) {
1510
+ if (action && permittedActions && !permittedActions.includes(action)) {
1511
+ return {
1512
+ success: false,
1513
+ error: `Action '${action}' not allowed. Available: ${permittedActions.join(", ")}`,
1514
+ durationMs: Date.now() - startTime,
1515
+ truncated: false
1516
+ };
1517
+ }
1518
+ return void 0;
1519
+ }
1520
+ function requireSession(sessionManager, sessionId) {
1521
+ return sessionManager.requireSession(sessionId);
1522
+ }
1523
+ function validateActionWithPermission(action, allowedActions, toolName, permittedActions, startTime) {
1524
+ const actionError = validateAction(action, allowedActions, startTime ?? Date.now());
1525
+ if (actionError) return actionError;
1526
+ const permittedToolActions = permittedActions?.[toolName];
1527
+ const permissionError = checkActionPermission(action, permittedToolActions, startTime ?? Date.now());
1528
+ if (permissionError) return permissionError;
1529
+ return void 0;
1530
+ }
1467
1531
  function createTool(name, definition, handler2) {
1468
1532
  return {
1469
1533
  name,
@@ -1519,7 +1583,7 @@ function createTool(name, definition, handler2) {
1519
1583
 
1520
1584
  // src/server/tools/file-tracker.ts
1521
1585
  import { createHash } from "crypto";
1522
- import { readFile as readFile3 } from "fs/promises";
1586
+ import { readFile as readFile4 } from "fs/promises";
1523
1587
  import { resolve as resolve3 } from "path";
1524
1588
  var FileNotReadError = class extends Error {
1525
1589
  constructor(path) {
@@ -1535,7 +1599,7 @@ var FileChangedExternallyError = class extends Error {
1535
1599
  };
1536
1600
  async function computeFileHash(filePath) {
1537
1601
  try {
1538
- const content = await readFile3(filePath);
1602
+ const content = await readFile4(filePath);
1539
1603
  const hash = createHash("sha256");
1540
1604
  hash.update(content);
1541
1605
  return hash.digest("hex");
@@ -1668,7 +1732,7 @@ var readFileTool = createTool(
1668
1732
  } catch {
1669
1733
  return helpers.error(`File not found: ${args.path}`);
1670
1734
  }
1671
- const rawBuffer = await readFile4(fullPath);
1735
+ const rawBuffer = await readFile5(fullPath);
1672
1736
  const mimeType = detectImageType(rawBuffer, args.path);
1673
1737
  if (mimeType) {
1674
1738
  const base64Data = rawBuffer.toString("base64");
@@ -1802,7 +1866,7 @@ var writeFileTool = createTool(
1802
1866
  );
1803
1867
 
1804
1868
  // src/server/tools/edit.ts
1805
- import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1869
+ import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1806
1870
 
1807
1871
  // src/server/tools/edit-context.ts
1808
1872
  var CONTEXT_LINES = 4;
@@ -1958,7 +2022,7 @@ var editFileTool = createTool(
1958
2022
  }
1959
2023
  let content;
1960
2024
  try {
1961
- content = await readFile5(fullPath, "utf-8");
2025
+ content = await readFile6(fullPath, "utf-8");
1962
2026
  } catch {
1963
2027
  return helpers.error(`File not found: ${args.path}`);
1964
2028
  }
@@ -2024,11 +2088,26 @@ Make sure whitespace and indentation match exactly.`
2024
2088
  );
2025
2089
 
2026
2090
  // src/server/tools/shell.ts
2027
- import { spawn as spawn2 } from "child_process";
2091
+ import "child_process";
2028
2092
  import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
2029
2093
 
2030
- // src/server/utils/process-tree.ts
2094
+ // src/server/utils/shell.ts
2031
2095
  import { spawn } from "child_process";
2096
+ function checkAborted(signal) {
2097
+ return !!signal?.aborted;
2098
+ }
2099
+ function spawnShellProcess(command, cwd, signal, detached = false) {
2100
+ const shell = getPlatformShell();
2101
+ return spawn(shell.command, [...shell.args, command], {
2102
+ cwd,
2103
+ env: { ...process.env, FORCE_COLOR: "0" },
2104
+ stdio: ["ignore", "pipe", "pipe"],
2105
+ ...detached ? { detached: true } : {}
2106
+ });
2107
+ }
2108
+
2109
+ // src/server/utils/process-tree.ts
2110
+ import { spawn as spawn2 } from "child_process";
2032
2111
  import { setTimeout as sleep } from "timers/promises";
2033
2112
  var SIGKILL_TIMEOUT_MS = 200;
2034
2113
  async function terminateProcessTree(proc, options) {
@@ -2038,7 +2117,7 @@ async function terminateProcessTree(proc, options) {
2038
2117
  }
2039
2118
  if (process.platform === "win32") {
2040
2119
  await new Promise((resolve6) => {
2041
- const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
2120
+ const killer = spawn2("taskkill", ["/pid", String(pid), "/f", "/t"], {
2042
2121
  stdio: "ignore",
2043
2122
  windowsHide: true
2044
2123
  });
@@ -2142,18 +2221,11 @@ ${result.stderr}`;
2142
2221
  );
2143
2222
  function executeCommand(command, cwd, timeout, signal, onProgress) {
2144
2223
  return new Promise((resolve6, reject) => {
2145
- if (signal?.aborted) {
2224
+ if (checkAborted(signal)) {
2146
2225
  reject(new Error("Command aborted before execution"));
2147
2226
  return;
2148
2227
  }
2149
- const shell = getPlatformShell();
2150
- const proc = spawn2(shell.command, [...shell.args, command], {
2151
- cwd,
2152
- env: { ...process.env, FORCE_COLOR: "0" },
2153
- stdio: ["ignore", "pipe", "pipe"],
2154
- detached: true
2155
- // Create new process group so we can kill all children
2156
- });
2228
+ const proc = spawnShellProcess(command, cwd, signal, true);
2157
2229
  let stdout = "";
2158
2230
  let stderr = "";
2159
2231
  let killed = false;
@@ -2171,12 +2243,12 @@ function executeCommand(command, cwd, timeout, signal, onProgress) {
2171
2243
  }
2172
2244
  };
2173
2245
  signal?.addEventListener("abort", onAbort);
2174
- proc.stdout.on("data", (data) => {
2246
+ proc.stdout?.on("data", (data) => {
2175
2247
  const chunk = data.toString();
2176
2248
  stdout += chunk;
2177
2249
  onProgress?.(`[stdout] ${chunk}`);
2178
2250
  });
2179
- proc.stderr.on("data", (data) => {
2251
+ proc.stderr?.on("data", (data) => {
2180
2252
  const chunk = data.toString();
2181
2253
  stderr += chunk;
2182
2254
  onProgress?.(`[stderr] ${chunk}`);
@@ -2219,10 +2291,27 @@ function formatCriteriaList(criteria) {
2219
2291
  if (criteria.length === 0) return "No criteria defined.";
2220
2292
  return criteria.map((c, i) => `${i + 1}. [${c.id}] ${c.description}`).join("\n");
2221
2293
  }
2222
- var criterionTool = {
2223
- name: "criterion",
2224
- permittedActions: ["get", "add", "update", "remove", "complete", "pass", "fail"],
2225
- definition: {
2294
+ function completeCriterion(session, context, id, statusType, reason) {
2295
+ const criterion = session.criteria.find((c) => c.id === id);
2296
+ if (!criterion) {
2297
+ return { success: false, output: `Criterion "${id}" not found` };
2298
+ }
2299
+ const status = statusType === "passed" ? { type: "passed", verifiedAt: (/* @__PURE__ */ new Date()).toISOString(), ...reason && { reason } } : { type: "failed", failedAt: (/* @__PURE__ */ new Date()).toISOString(), reason };
2300
+ context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2301
+ context.sessionManager.addCriterionAttempt(context.sessionId, id, {
2302
+ attemptNumber: criterion.attempts.length + 1,
2303
+ status: statusType,
2304
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2305
+ ...reason ? { details: reason } : {}
2306
+ });
2307
+ return {
2308
+ success: true,
2309
+ output: statusType === "passed" ? `Criterion "${id}" verified as PASSED.${reason ? ` Verification: ${reason}` : ""}` : `Criterion "${id}" marked as FAILED. Reason: ${reason}`
2310
+ };
2311
+ }
2312
+ var criterionTool = createTool(
2313
+ "criterion",
2314
+ {
2226
2315
  type: "function",
2227
2316
  function: {
2228
2317
  name: "criterion",
@@ -2252,267 +2341,90 @@ var criterionTool = {
2252
2341
  }
2253
2342
  }
2254
2343
  },
2255
- async execute(args, context) {
2256
- const startTime = Date.now();
2257
- try {
2258
- const action = args["action"];
2259
- const id = args["id"];
2260
- const description = args["description"];
2261
- const reason = args["reason"];
2262
- if (!action || !["get", "add", "update", "remove", "complete", "pass", "fail"].includes(action)) {
2263
- return {
2264
- success: false,
2265
- error: `Invalid action: ${action}. Must be one of: get, add, update, remove, complete, pass, fail`,
2266
- durationMs: Date.now() - startTime,
2267
- truncated: false
2268
- };
2269
- }
2270
- const permittedActions = context.permittedActions?.["criterion"];
2271
- if (permittedActions && !permittedActions.includes(action)) {
2272
- return {
2273
- success: false,
2274
- error: `Action '${action}' not allowed. Available: ${permittedActions.join(", ")}`,
2275
- durationMs: Date.now() - startTime,
2276
- truncated: false
2277
- };
2278
- }
2279
- const session = context.sessionManager.requireSession(context.sessionId);
2280
- if (action === "get") {
2281
- const criteria = session.criteria;
2282
- return {
2283
- success: true,
2284
- output: criteria.length === 0 ? "No criteria defined yet." : JSON.stringify(criteria.map((c) => ({
2285
- id: c.id,
2286
- description: c.description
2287
- })), null, 2),
2288
- durationMs: Date.now() - startTime,
2289
- truncated: false
2290
- };
2291
- }
2292
- if (action === "add") {
2293
- if (!description) {
2294
- return {
2295
- success: false,
2296
- error: "Missing required field: description",
2297
- durationMs: Date.now() - startTime,
2298
- truncated: false
2299
- };
2300
- }
2301
- const criterion = {
2302
- id: id || "",
2303
- description,
2304
- status: { type: "pending" },
2305
- attempts: []
2306
- };
2307
- const result = context.sessionManager.addCriterion(context.sessionId, criterion);
2308
- if ("error" in result) {
2309
- return { success: false, error: result.error, durationMs: Date.now() - startTime, truncated: false };
2310
- }
2311
- return {
2312
- success: true,
2313
- output: `Added criterion "${result.actualId}". Current criteria:
2314
- ${formatCriteriaList(result.criteria)}`,
2315
- durationMs: Date.now() - startTime,
2316
- truncated: false
2317
- };
2318
- }
2319
- if (action === "update") {
2320
- if (!id) {
2321
- return {
2322
- success: false,
2323
- error: "Missing required field: id",
2324
- durationMs: Date.now() - startTime,
2325
- truncated: false
2326
- };
2327
- }
2328
- if (!description) {
2329
- return {
2330
- success: false,
2331
- error: "Missing required field: description",
2332
- durationMs: Date.now() - startTime,
2333
- truncated: false
2334
- };
2335
- }
2336
- if (!session.criteria.find((c) => c.id === id)) {
2337
- return {
2338
- success: false,
2339
- error: `Criterion "${id}" not found`,
2340
- durationMs: Date.now() - startTime,
2341
- truncated: false
2342
- };
2343
- }
2344
- const criteria = context.sessionManager.updateCriterionFull(context.sessionId, id, { description });
2345
- return {
2346
- success: true,
2347
- output: `Updated criterion "${id}". Current criteria:
2348
- ${formatCriteriaList(criteria)}`,
2349
- durationMs: Date.now() - startTime,
2350
- truncated: false
2351
- };
2352
- }
2353
- if (action === "remove") {
2354
- if (!id) {
2355
- return {
2356
- success: false,
2357
- error: "Missing required field: id",
2358
- durationMs: Date.now() - startTime,
2359
- truncated: false
2360
- };
2361
- }
2362
- if (!session.criteria.find((c) => c.id === id)) {
2363
- return {
2364
- success: false,
2365
- error: `Criterion "${id}" not found`,
2366
- durationMs: Date.now() - startTime,
2367
- truncated: false
2368
- };
2369
- }
2370
- const criteria = context.sessionManager.removeCriterion(context.sessionId, id);
2371
- return {
2372
- success: true,
2373
- output: criteria.length === 0 ? `Removed criterion "${id}". No criteria remaining.` : `Removed criterion "${id}". Current criteria:
2374
- ${formatCriteriaList(criteria)}`,
2375
- durationMs: Date.now() - startTime,
2376
- truncated: false
2377
- };
2378
- }
2379
- if (action === "complete") {
2380
- if (!id) {
2381
- return {
2382
- success: false,
2383
- error: "Missing required field: id",
2384
- durationMs: Date.now() - startTime,
2385
- truncated: false
2386
- };
2387
- }
2388
- const criterion = session.criteria.find((c) => c.id === id);
2389
- if (!criterion) {
2390
- return {
2391
- success: false,
2392
- error: `Criterion "${id}" not found. Available: ${session.criteria.map((c) => c.id).join(", ")}`,
2393
- durationMs: Date.now() - startTime,
2394
- truncated: false
2395
- };
2396
- }
2397
- const status = {
2398
- type: "completed",
2399
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2400
- ...reason ? { reason } : {}
2401
- };
2402
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2403
- return {
2404
- success: true,
2405
- output: `Criterion "${id}" marked as completed.${reason ? ` Reason: ${reason}` : ""}`,
2406
- durationMs: Date.now() - startTime,
2407
- truncated: false
2408
- };
2409
- }
2410
- if (action === "pass") {
2411
- if (!id) {
2412
- return {
2413
- success: false,
2414
- error: "Missing required field: id",
2415
- durationMs: Date.now() - startTime,
2416
- truncated: false
2417
- };
2418
- }
2419
- const criterion = session.criteria.find((c) => c.id === id);
2420
- if (!criterion) {
2421
- return {
2422
- success: false,
2423
- error: `Criterion "${id}" not found`,
2424
- durationMs: Date.now() - startTime,
2425
- truncated: false
2426
- };
2427
- }
2428
- const status = {
2429
- type: "passed",
2430
- verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
2431
- ...reason ? { reason } : {}
2432
- };
2433
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2434
- context.sessionManager.addCriterionAttempt(context.sessionId, id, {
2435
- attemptNumber: criterion.attempts.length + 1,
2436
- status: "passed",
2437
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2438
- ...reason && { details: reason }
2439
- });
2440
- return {
2441
- success: true,
2442
- output: `Criterion "${id}" verified as PASSED.${reason ? ` Verification: ${reason}` : ""}`,
2443
- durationMs: Date.now() - startTime,
2444
- truncated: false
2445
- };
2446
- }
2447
- if (action === "fail") {
2448
- if (!id) {
2449
- return {
2450
- success: false,
2451
- error: "Missing required field: id",
2452
- durationMs: Date.now() - startTime,
2453
- truncated: false
2454
- };
2455
- }
2456
- if (!reason) {
2457
- return {
2458
- success: false,
2459
- error: "Missing required field: reason",
2460
- durationMs: Date.now() - startTime,
2461
- truncated: false
2462
- };
2463
- }
2464
- const criterion = session.criteria.find((c) => c.id === id);
2465
- if (!criterion) {
2466
- return {
2467
- success: false,
2468
- error: `Criterion "${id}" not found`,
2469
- durationMs: Date.now() - startTime,
2470
- truncated: false
2471
- };
2472
- }
2473
- const status = {
2474
- type: "failed",
2475
- failedAt: (/* @__PURE__ */ new Date()).toISOString(),
2476
- reason
2477
- };
2478
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2479
- context.sessionManager.addCriterionAttempt(context.sessionId, id, {
2480
- attemptNumber: criterion.attempts.length + 1,
2481
- status: "failed",
2482
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2483
- details: reason
2484
- });
2485
- return {
2486
- success: true,
2487
- output: `Criterion "${id}" marked as FAILED. Reason: ${reason}`,
2488
- durationMs: Date.now() - startTime,
2489
- truncated: false
2490
- };
2491
- }
2492
- return {
2493
- success: false,
2494
- error: "Unexpected error",
2495
- durationMs: Date.now() - startTime,
2496
- truncated: false
2497
- };
2498
- } catch (error) {
2499
- return {
2500
- success: false,
2501
- error: error instanceof Error ? error.message : "Unknown error",
2502
- durationMs: Date.now() - startTime,
2503
- truncated: false
2344
+ async (args, context, helpers) => {
2345
+ const actionError = validateActionWithPermission(args.action, ["get", "add", "update", "remove", "complete", "pass", "fail"], "criterion", context.permittedActions);
2346
+ if (actionError) return actionError;
2347
+ const session = requireSession(context.sessionManager, context.sessionId);
2348
+ if (args.action === "get") {
2349
+ return helpers.success(
2350
+ session.criteria.length === 0 ? "No criteria defined yet." : JSON.stringify(session.criteria.map((c) => ({ id: c.id, description: c.description })), null, 2)
2351
+ );
2352
+ }
2353
+ if (args.action === "add") {
2354
+ if (!args.description) return helpers.error("Missing required field: description");
2355
+ const criterion = {
2356
+ id: args.id || "",
2357
+ description: args.description,
2358
+ status: { type: "pending" },
2359
+ attempts: []
2504
2360
  };
2361
+ const result = context.sessionManager.addCriterion(context.sessionId, criterion);
2362
+ if ("error" in result) return helpers.error(result.error);
2363
+ return helpers.success(`Added criterion "${result.actualId}". Current criteria:
2364
+ ${formatCriteriaList(result.criteria)}`);
2365
+ }
2366
+ if (args.action === "update") {
2367
+ if (!args.id) return helpers.error("Missing required field: id");
2368
+ if (!args.description) return helpers.error("Missing required field: description");
2369
+ if (!session.criteria.find((c) => c.id === args.id)) return helpers.error(`Criterion "${args.id}" not found`);
2370
+ const criteria = context.sessionManager.updateCriterionFull(context.sessionId, args.id, { description: args.description });
2371
+ return helpers.success(`Updated criterion "${args.id}". Current criteria:
2372
+ ${formatCriteriaList(criteria)}`);
2373
+ }
2374
+ if (args.action === "remove") {
2375
+ if (!args.id) return helpers.error("Missing required field: id");
2376
+ if (!session.criteria.find((c) => c.id === args.id)) return helpers.error(`Criterion "${args.id}" not found`);
2377
+ const criteria = context.sessionManager.removeCriterion(context.sessionId, args.id);
2378
+ return helpers.success(
2379
+ criteria.length === 0 ? `Removed criterion "${args.id}". No criteria remaining.` : `Removed criterion "${args.id}". Current criteria:
2380
+ ${formatCriteriaList(criteria)}`
2381
+ );
2382
+ }
2383
+ if (args.action === "complete") {
2384
+ if (!args.id) return helpers.error("Missing required field: id");
2385
+ const criterion = session.criteria.find((c) => c.id === args.id);
2386
+ if (!criterion) return helpers.error(`Criterion "${args.id}" not found. Available: ${session.criteria.map((c) => c.id).join(", ")}`);
2387
+ context.sessionManager.updateCriterionStatus(context.sessionId, args.id, {
2388
+ type: "completed",
2389
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2390
+ ...args.reason ? { reason: args.reason } : {}
2391
+ });
2392
+ return helpers.success(`Criterion "${args.id}" marked as completed.${args.reason ? ` Reason: ${args.reason}` : ""}`);
2393
+ }
2394
+ if (args.action === "pass") {
2395
+ if (!args.id) return helpers.error("Missing required field: id");
2396
+ const result = completeCriterion(session, context, args.id, "passed", args.reason);
2397
+ return result.success ? helpers.success(result.output) : helpers.error(result.output);
2505
2398
  }
2399
+ if (args.action === "fail") {
2400
+ if (!args.id) return helpers.error("Missing required field: id");
2401
+ if (!args.reason) return helpers.error("Missing required field: reason");
2402
+ const result = completeCriterion(session, context, args.id, "failed", args.reason);
2403
+ return result.success ? helpers.success(result.output) : helpers.error(result.output);
2404
+ }
2405
+ return helpers.error("Unexpected error");
2506
2406
  }
2507
- };
2407
+ );
2508
2408
 
2509
2409
  // src/server/tools/todo.ts
2510
2410
  var sessionTodos = /* @__PURE__ */ new Map();
2511
2411
  var onTodoUpdate = null;
2512
- var todoTool = {
2513
- name: "todo",
2514
- permittedActions: ["list", "write", "add", "update", "remove"],
2515
- definition: {
2412
+ function buildTaskListSummary(todosList) {
2413
+ const pending = todosList.filter((t) => t.status === "pending").length;
2414
+ const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2415
+ const completed = todosList.filter((t) => t.status === "completed").length;
2416
+ return `${completed} completed, ${inProgress} in progress, ${pending} pending`;
2417
+ }
2418
+ function validateIndex(index, todosList, helpers) {
2419
+ if (index === void 0) return helpers.error("Missing required field: index");
2420
+ if (index < 0 || index >= todosList.length) {
2421
+ return helpers.error(`Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`);
2422
+ }
2423
+ return todosList[index];
2424
+ }
2425
+ var todoTool = createTool(
2426
+ "todo",
2427
+ {
2516
2428
  type: "function",
2517
2429
  function: {
2518
2430
  name: "todo",
@@ -2555,235 +2467,65 @@ var todoTool = {
2555
2467
  }
2556
2468
  }
2557
2469
  },
2558
- async execute(args, context) {
2559
- const startTime = Date.now();
2560
- try {
2561
- const action = args["action"];
2562
- const todos = args["todos"];
2563
- const content = args["content"];
2564
- const index = args["index"];
2565
- const status = args["status"];
2566
- if (!action || !["list", "write", "add", "update", "remove"].includes(action)) {
2567
- return {
2568
- success: false,
2569
- error: `Invalid action: ${action}. Must be one of: list, write, add, update, remove`,
2570
- durationMs: Date.now() - startTime,
2571
- truncated: false
2572
- };
2573
- }
2574
- const permittedActions = context.permittedActions?.["todo"];
2575
- if (permittedActions && !permittedActions.includes(action)) {
2576
- return {
2577
- success: false,
2578
- error: `Action '${action}' not allowed. Available: ${permittedActions.join(", ")}`,
2579
- durationMs: Date.now() - startTime,
2580
- truncated: false
2581
- };
2582
- }
2583
- if (action === "list") {
2584
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2585
- return {
2586
- success: true,
2587
- output: todosList.length === 0 ? "No tasks defined yet." : JSON.stringify(todosList, null, 2),
2588
- durationMs: Date.now() - startTime,
2589
- truncated: false
2590
- };
2591
- }
2592
- if (action === "write") {
2593
- if (!todos) {
2594
- return {
2595
- success: false,
2596
- error: "Missing required field: todos",
2597
- durationMs: Date.now() - startTime,
2598
- truncated: false
2599
- };
2600
- }
2601
- if (!Array.isArray(todos)) {
2602
- return {
2603
- success: false,
2604
- error: "todos must be an array",
2605
- durationMs: Date.now() - startTime,
2606
- truncated: false
2607
- };
2608
- }
2609
- for (const todo of todos) {
2610
- if (!todo.content || !todo.status) {
2611
- return {
2612
- success: false,
2613
- error: "Each todo must have content and status",
2614
- durationMs: Date.now() - startTime,
2615
- truncated: false
2616
- };
2617
- }
2618
- if (!["pending", "in_progress", "completed"].includes(todo.status)) {
2619
- return {
2620
- success: false,
2621
- error: `Invalid status: ${todo.status}. Must be pending, in_progress, or completed`,
2622
- durationMs: Date.now() - startTime,
2623
- truncated: false
2624
- };
2625
- }
2626
- }
2627
- sessionTodos.set(context.sessionId, todos);
2628
- if (onTodoUpdate) {
2629
- onTodoUpdate(context.sessionId, todos);
2630
- }
2631
- const pending = todos.filter((t) => t.status === "pending").length;
2632
- const inProgress = todos.filter((t) => t.status === "in_progress").length;
2633
- const completed = todos.filter((t) => t.status === "completed").length;
2634
- return {
2635
- success: true,
2636
- output: `Task list updated: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2637
- durationMs: Date.now() - startTime,
2638
- truncated: false
2639
- };
2640
- }
2641
- if (action === "add") {
2642
- if (!content) {
2643
- return {
2644
- success: false,
2645
- error: "Missing required field: content",
2646
- durationMs: Date.now() - startTime,
2647
- truncated: false
2648
- };
2649
- }
2650
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2651
- const newTodo = { content, status: "pending" };
2652
- todosList.push(newTodo);
2653
- sessionTodos.set(context.sessionId, todosList);
2654
- if (onTodoUpdate) {
2655
- onTodoUpdate(context.sessionId, todosList);
2656
- }
2657
- const pending = todosList.filter((t) => t.status === "pending").length;
2658
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2659
- const completed = todosList.filter((t) => t.status === "completed").length;
2660
- return {
2661
- success: true,
2662
- output: `Added task "${content}". Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2663
- durationMs: Date.now() - startTime,
2664
- truncated: false
2665
- };
2666
- }
2667
- if (action === "update") {
2668
- if (index === void 0) {
2669
- return {
2670
- success: false,
2671
- error: "Missing required field: index",
2672
- durationMs: Date.now() - startTime,
2673
- truncated: false
2674
- };
2675
- }
2676
- if (!content && !status) {
2677
- return {
2678
- success: false,
2679
- error: "update requires content or status",
2680
- durationMs: Date.now() - startTime,
2681
- truncated: false
2682
- };
2683
- }
2684
- if (status && !["pending", "in_progress", "completed"].includes(status)) {
2685
- return {
2686
- success: false,
2687
- error: `Invalid status: ${status}. Must be pending, in_progress, or completed`,
2688
- durationMs: Date.now() - startTime,
2689
- truncated: false
2690
- };
2691
- }
2692
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2693
- if (index < 0 || index >= todosList.length) {
2694
- return {
2695
- success: false,
2696
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2697
- durationMs: Date.now() - startTime,
2698
- truncated: false
2699
- };
2700
- }
2701
- const todo = todosList[index];
2702
- if (!todo) {
2703
- return {
2704
- success: false,
2705
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2706
- durationMs: Date.now() - startTime,
2707
- truncated: false
2708
- };
2709
- }
2710
- if (content) {
2711
- todo.content = content;
2712
- }
2713
- if (status) {
2714
- todo.status = status;
2715
- }
2716
- sessionTodos.set(context.sessionId, todosList);
2717
- if (onTodoUpdate) {
2718
- onTodoUpdate(context.sessionId, todosList);
2470
+ async (args, context, helpers) => {
2471
+ const actionError = validateActionWithPermission(args.action, ["list", "write", "add", "update", "remove"], "todo", context.permittedActions);
2472
+ if (actionError) return actionError;
2473
+ if (args.action === "list") {
2474
+ const todosList = sessionTodos.get(context.sessionId) ?? [];
2475
+ return helpers.success(
2476
+ todosList.length === 0 ? "No tasks defined yet." : JSON.stringify(todosList, null, 2)
2477
+ );
2478
+ }
2479
+ if (args.action === "write") {
2480
+ if (!args.todos) return helpers.error("Missing required field: todos");
2481
+ if (!Array.isArray(args.todos)) return helpers.error("todos must be an array");
2482
+ for (const todo of args.todos) {
2483
+ if (!todo.content || !todo.status) return helpers.error("Each todo must have content and status");
2484
+ if (!["pending", "in_progress", "completed"].includes(todo.status)) {
2485
+ return helpers.error(`Invalid status: ${todo.status}. Must be pending, in_progress, or completed`);
2719
2486
  }
2720
- const pending = todosList.filter((t) => t.status === "pending").length;
2721
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2722
- const completed = todosList.filter((t) => t.status === "completed").length;
2723
- return {
2724
- success: true,
2725
- output: `Updated task ${index}. Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2726
- durationMs: Date.now() - startTime,
2727
- truncated: false
2728
- };
2729
2487
  }
2730
- if (action === "remove") {
2731
- if (index === void 0) {
2732
- return {
2733
- success: false,
2734
- error: "Missing required field: index",
2735
- durationMs: Date.now() - startTime,
2736
- truncated: false
2737
- };
2738
- }
2739
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2740
- if (index < 0 || index >= todosList.length) {
2741
- return {
2742
- success: false,
2743
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2744
- durationMs: Date.now() - startTime,
2745
- truncated: false
2746
- };
2747
- }
2748
- const removed = todosList.splice(index, 1)[0];
2749
- sessionTodos.set(context.sessionId, todosList);
2750
- if (onTodoUpdate) {
2751
- onTodoUpdate(context.sessionId, todosList);
2752
- }
2753
- if (todosList.length === 0) {
2754
- return {
2755
- success: true,
2756
- output: `Removed task "${removed.content}". No tasks remaining.`,
2757
- durationMs: Date.now() - startTime,
2758
- truncated: false
2759
- };
2760
- }
2761
- const pending = todosList.filter((t) => t.status === "pending").length;
2762
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2763
- const completed = todosList.filter((t) => t.status === "completed").length;
2764
- return {
2765
- success: true,
2766
- output: `Removed task "${removed.content}". Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2767
- durationMs: Date.now() - startTime,
2768
- truncated: false
2769
- };
2488
+ sessionTodos.set(context.sessionId, args.todos);
2489
+ if (onTodoUpdate) onTodoUpdate(context.sessionId, args.todos);
2490
+ return helpers.success(`${args.todos.filter((t) => t.status === "completed").length} completed, ${args.todos.filter((t) => t.status === "in_progress").length} in progress, ${args.todos.filter((t) => t.status === "pending").length} pending`);
2491
+ }
2492
+ if (args.action === "add") {
2493
+ if (!args.content) return helpers.error("Missing required field: content");
2494
+ const todosList = sessionTodos.get(context.sessionId) ?? [];
2495
+ todosList.push({ content: args.content, status: "pending" });
2496
+ sessionTodos.set(context.sessionId, todosList);
2497
+ if (onTodoUpdate) onTodoUpdate(context.sessionId, todosList);
2498
+ return helpers.success(`Added task "${args.content}". Task list: ${buildTaskListSummary(todosList)}`);
2499
+ }
2500
+ if (args.action === "update") {
2501
+ if (!args.content && !args.status) return helpers.error("update requires content or status");
2502
+ if (args.status && !["pending", "in_progress", "completed"].includes(args.status)) {
2503
+ return helpers.error(`Invalid status: ${args.status}. Must be pending, in_progress, or completed`);
2770
2504
  }
2771
- return {
2772
- success: false,
2773
- error: "Unexpected error",
2774
- durationMs: Date.now() - startTime,
2775
- truncated: false
2776
- };
2777
- } catch (error) {
2778
- return {
2779
- success: false,
2780
- error: error instanceof Error ? error.message : "Unknown error",
2781
- durationMs: Date.now() - startTime,
2782
- truncated: false
2783
- };
2505
+ const todosList = sessionTodos.get(context.sessionId) ?? [];
2506
+ const indexResult = validateIndex(args.index, todosList, helpers);
2507
+ if ("success" in indexResult && !indexResult.success) return indexResult;
2508
+ const todo = indexResult;
2509
+ if (args.content) todo.content = args.content;
2510
+ if (args.status) todo.status = args.status;
2511
+ sessionTodos.set(context.sessionId, todosList);
2512
+ if (onTodoUpdate) onTodoUpdate(context.sessionId, todosList);
2513
+ return helpers.success(`Updated task ${args.index}. Task list: ${buildTaskListSummary(todosList)}`);
2514
+ }
2515
+ if (args.action === "remove") {
2516
+ const todosList = sessionTodos.get(context.sessionId) ?? [];
2517
+ const indexResult = validateIndex(args.index, todosList, helpers);
2518
+ if ("success" in indexResult && !indexResult.success) return indexResult;
2519
+ const removed = todosList.splice(args.index, 1)[0];
2520
+ sessionTodos.set(context.sessionId, todosList);
2521
+ if (onTodoUpdate) onTodoUpdate(context.sessionId, todosList);
2522
+ return helpers.success(
2523
+ todosList.length === 0 ? `Removed task "${removed.content}". No tasks remaining.` : `Removed task "${removed.content}". Task list: ${buildTaskListSummary(todosList)}`
2524
+ );
2784
2525
  }
2526
+ return helpers.error("Unexpected error");
2785
2527
  }
2786
- };
2528
+ );
2787
2529
 
2788
2530
  // src/server/chat/tool-streaming.ts
2789
2531
  function parseProgressMessage(message) {
@@ -2803,18 +2545,17 @@ function createToolProgressHandler(messageId, callId, onMessage) {
2803
2545
  }
2804
2546
 
2805
2547
  // src/server/skills/registry.ts
2806
- import { readdir as readdir2, readFile as readFile6, writeFile as writeFile4, copyFile as copyFile2, mkdir as mkdir3, access as access3, unlink as unlink2 } from "fs/promises";
2807
- import { join as join4, dirname as dirname4 } from "path";
2548
+ import { readdir as readdir3 } from "fs/promises";
2549
+ import { join as join6 } from "path";
2550
+
2551
+ // src/server/commands/registry-utils.ts
2552
+ import { readdir as readdir2, readFile as readFile7, writeFile as writeFile4, copyFile as copyFile2, mkdir as mkdir3, access as access3, unlink as unlink2 } from "fs/promises";
2553
+ import { join as join5, dirname as dirname4 } from "path";
2808
2554
  import { constants as constants3 } from "fs";
2809
2555
  import { fileURLToPath as fileURLToPath2 } from "url";
2810
2556
  import matter2 from "gray-matter";
2811
- var __bundleDir2 = dirname4(fileURLToPath2(import.meta.url));
2812
- var DEFAULTS_DIR2 = join4(__bundleDir2, "defaults");
2813
- var DEFAULTS_DIR_ALT2 = join4(__bundleDir2, "skill-defaults");
2814
- var SKILL_EXTENSION = ".skill.md";
2815
- var SKILL_SETTING_PREFIX = "skill.enabled.";
2816
- function getSkillsDir(configDir) {
2817
- return join4(configDir, "skills");
2557
+ function getBundleDir() {
2558
+ return dirname4(fileURLToPath2(import.meta.url));
2818
2559
  }
2819
2560
  async function dirExists(path) {
2820
2561
  try {
@@ -2824,64 +2565,92 @@ async function dirExists(path) {
2824
2565
  return false;
2825
2566
  }
2826
2567
  }
2827
- async function ensureDefaultSkills(configDir) {
2828
- const skillsDir = getSkillsDir(configDir);
2829
- if (!await dirExists(skillsDir)) {
2830
- await mkdir3(skillsDir, { recursive: true });
2831
- }
2832
- let defaultFiles;
2833
- let sourceDir;
2834
- try {
2835
- defaultFiles = (await readdir2(DEFAULTS_DIR2)).filter((f) => f.endsWith(SKILL_EXTENSION));
2836
- sourceDir = DEFAULTS_DIR2;
2837
- } catch {
2838
- try {
2839
- defaultFiles = (await readdir2(DEFAULTS_DIR_ALT2)).filter((f) => f.endsWith(SKILL_EXTENSION));
2840
- sourceDir = DEFAULTS_DIR_ALT2;
2841
- } catch {
2842
- logger.warn("No bundled skill defaults found", { dir: DEFAULTS_DIR2 });
2843
- return;
2844
- }
2845
- }
2846
- for (const file of defaultFiles) {
2847
- const targetPath = join4(skillsDir, file);
2848
- if (!await dirExists(targetPath)) {
2849
- try {
2850
- await copyFile2(join4(sourceDir, file), targetPath);
2851
- logger.info("Installed default skill", { file });
2852
- } catch (err) {
2853
- logger.error("Failed to copy default skill", { file, error: err instanceof Error ? err.message : String(err) });
2854
- }
2855
- }
2568
+ async function ensureDir(path) {
2569
+ if (!await dirExists(path)) {
2570
+ await mkdir3(path, { recursive: true });
2856
2571
  }
2857
2572
  }
2858
- async function loadAllSkills(configDir) {
2859
- const skillsDir = getSkillsDir(configDir);
2860
- if (!await dirExists(skillsDir)) {
2861
- return [];
2862
- }
2573
+ async function loadItems(dir, extension) {
2574
+ if (!await dirExists(dir)) return [];
2863
2575
  let files;
2864
2576
  try {
2865
- files = (await readdir2(skillsDir)).filter((f) => f.endsWith(SKILL_EXTENSION));
2577
+ files = (await readdir2(dir)).filter((f) => f.endsWith(extension));
2866
2578
  } catch {
2867
2579
  return [];
2868
2580
  }
2869
- const skills = [];
2581
+ const items = [];
2870
2582
  for (const file of files) {
2871
2583
  try {
2872
- const raw = await readFile6(join4(skillsDir, file), "utf-8");
2584
+ const raw = await readFile7(join5(dir, file), "utf-8");
2873
2585
  const { data, content } = matter2(raw);
2874
- const metadata = data;
2875
- if (metadata.id && content.trim()) {
2876
- skills.push({ metadata, prompt: content.trim() });
2586
+ if (data.id && content.trim()) {
2587
+ items.push({ metadata: data, prompt: content.trim() });
2877
2588
  } else {
2878
- logger.warn("Skipping invalid skill file", { file });
2589
+ logger.warn(`Skipping invalid ${extension} file`, { file });
2879
2590
  }
2880
2591
  } catch (err) {
2881
- logger.warn("Failed to parse skill file", { file, error: err instanceof Error ? err.message : String(err) });
2592
+ logger.warn(`Failed to parse ${extension} file`, { file, error: err instanceof Error ? err.message : String(err) });
2593
+ }
2594
+ }
2595
+ return items;
2596
+ }
2597
+ async function saveItem(dir, id, extension, item) {
2598
+ await ensureDir(dir);
2599
+ const filePath = join5(dir, `${id}${extension}`);
2600
+ const content = matter2.stringify(item.prompt, item.metadata);
2601
+ await writeFile4(filePath, content, "utf-8");
2602
+ }
2603
+ async function deleteItem(dir, id, extension) {
2604
+ const filePath = join5(dir, `${id}${extension}`);
2605
+ try {
2606
+ await unlink2(filePath);
2607
+ return true;
2608
+ } catch {
2609
+ return false;
2610
+ }
2611
+ }
2612
+ function findById(id, items) {
2613
+ return items.find((i) => i.metadata.id === id);
2614
+ }
2615
+ async function getDefaultIds(bundleDir, defaultsDir, extension) {
2616
+ try {
2617
+ const files = (await readdir2(join5(bundleDir, defaultsDir))).filter((f) => f.endsWith(extension));
2618
+ return files.map((f) => f.replace(extension, ""));
2619
+ } catch {
2620
+ return [];
2621
+ }
2622
+ }
2623
+ async function restoreDefault(itemDir, bundleDir, defaultsDir, id, extension) {
2624
+ const filename = `${id}${extension}`;
2625
+ const src = join5(bundleDir, defaultsDir, filename);
2626
+ if (await dirExists(src)) {
2627
+ await copyFile2(src, join5(itemDir, filename));
2628
+ return true;
2629
+ }
2630
+ return false;
2631
+ }
2632
+
2633
+ // src/server/skills/registry.ts
2634
+ var DEFAULTS_DIR2 = "defaults";
2635
+ var DEFAULTS_DIR_ALT2 = "skill-defaults";
2636
+ var SKILL_EXTENSION = ".skill.md";
2637
+ var SKILL_SETTING_PREFIX = "skill.enabled.";
2638
+ function getSkillsDir(configDir) {
2639
+ return join6(configDir, "skills");
2640
+ }
2641
+ async function ensureDefaultSkills(configDir) {
2642
+ const bundleDir = getBundleDir();
2643
+ const skillsDir = getSkillsDir(configDir);
2644
+ await ensureDir(skillsDir);
2645
+ const ids = await getDefaultIds(bundleDir, DEFAULTS_DIR2, SKILL_EXTENSION);
2646
+ for (const id of ids) {
2647
+ if (!await restoreDefault(skillsDir, bundleDir, DEFAULTS_DIR2, id, SKILL_EXTENSION)) {
2648
+ await restoreDefault(skillsDir, bundleDir, DEFAULTS_DIR_ALT2, id, SKILL_EXTENSION);
2882
2649
  }
2883
2650
  }
2884
- return skills;
2651
+ }
2652
+ async function loadAllSkills(configDir) {
2653
+ return loadItems(getSkillsDir(configDir), SKILL_EXTENSION);
2885
2654
  }
2886
2655
  async function getEnabledSkills(configDir) {
2887
2656
  const all = await loadAllSkills(configDir);
@@ -2900,51 +2669,21 @@ function setSkillEnabled(skillId, enabled) {
2900
2669
  setSetting(`${SKILL_SETTING_PREFIX}${skillId}`, String(enabled));
2901
2670
  }
2902
2671
  async function getDefaultSkillIds() {
2903
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2904
- try {
2905
- const files = (await readdir2(dir)).filter((f) => f.endsWith(SKILL_EXTENSION));
2906
- return files.map((f) => f.replace(SKILL_EXTENSION, ""));
2907
- } catch {
2908
- }
2909
- }
2910
- return [];
2672
+ const bundleDir = getBundleDir();
2673
+ const ids = await getDefaultIds(bundleDir, DEFAULTS_DIR2, SKILL_EXTENSION);
2674
+ if (ids.length) return ids;
2675
+ return getDefaultIds(bundleDir, DEFAULTS_DIR_ALT2, SKILL_EXTENSION);
2911
2676
  }
2912
2677
  async function restoreDefaultSkill(configDir, skillId) {
2913
- const filename = `${skillId}${SKILL_EXTENSION}`;
2914
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2915
- const sourcePath = join4(dir, filename);
2916
- if (await dirExists(sourcePath)) {
2917
- const targetPath = join4(getSkillsDir(configDir), filename);
2918
- await copyFile2(sourcePath, targetPath);
2919
- return true;
2920
- }
2921
- }
2922
- return false;
2678
+ const bundleDir = getBundleDir();
2679
+ const skillsDir = getSkillsDir(configDir);
2680
+ return restoreDefault(skillsDir, bundleDir, DEFAULTS_DIR2, skillId, SKILL_EXTENSION) || restoreDefault(skillsDir, bundleDir, DEFAULTS_DIR_ALT2, skillId, SKILL_EXTENSION);
2923
2681
  }
2924
2682
  async function getModifiedDefaultSkillIds(configDir) {
2925
- const defaultIds = await getDefaultSkillIds();
2926
- const modified = [];
2927
- for (const id of defaultIds) {
2928
- const filename = `${id}${SKILL_EXTENSION}`;
2929
- const userPath = join4(getSkillsDir(configDir), filename);
2930
- let bundledContent = null;
2931
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2932
- try {
2933
- bundledContent = await readFile6(join4(dir, filename), "utf-8");
2934
- break;
2935
- } catch {
2936
- }
2937
- }
2938
- if (!bundledContent) continue;
2939
- try {
2940
- const userContent = await readFile6(userPath, "utf-8");
2941
- if (userContent !== bundledContent) {
2942
- modified.push(id);
2943
- }
2944
- } catch {
2945
- }
2946
- }
2947
- return modified;
2683
+ const bundleDir = getBundleDir();
2684
+ const ids = await getDefaultSkillIds();
2685
+ const files = (await readdir3(join6(bundleDir, DEFAULTS_DIR2))).filter((f) => f.endsWith(SKILL_EXTENSION));
2686
+ return files.map((f) => f.replace(SKILL_EXTENSION, "")).filter((id) => ids.includes(id));
2948
2687
  }
2949
2688
  async function restoreAllDefaultSkills(configDir) {
2950
2689
  const ids = await getDefaultSkillIds();
@@ -2955,34 +2694,34 @@ async function restoreAllDefaultSkills(configDir) {
2955
2694
  return count;
2956
2695
  }
2957
2696
  function findSkillById(skillId, skills) {
2958
- return skills.find((s) => s.metadata.id === skillId);
2697
+ return findById(skillId, skills);
2959
2698
  }
2960
2699
  async function skillExists(configDir, skillId) {
2961
- const filePath = join4(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`);
2962
- return dirExists(filePath);
2700
+ return dirExists(join6(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`));
2963
2701
  }
2964
2702
  async function saveSkill(configDir, skill) {
2965
- const skillsDir = getSkillsDir(configDir);
2966
- if (!await dirExists(skillsDir)) {
2967
- await mkdir3(skillsDir, { recursive: true });
2968
- }
2969
- const filePath = join4(skillsDir, `${skill.metadata.id}${SKILL_EXTENSION}`);
2970
- const content = matter2.stringify(skill.prompt, skill.metadata);
2971
- await writeFile4(filePath, content, "utf-8");
2703
+ return saveItem(getSkillsDir(configDir), skill.metadata.id, SKILL_EXTENSION, skill);
2972
2704
  }
2973
2705
  async function deleteSkill(configDir, skillId) {
2974
- const filePath = join4(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`);
2975
- try {
2976
- await unlink2(filePath);
2706
+ const deleted = await deleteItem(getSkillsDir(configDir), skillId, SKILL_EXTENSION);
2707
+ if (deleted) {
2977
2708
  const { deleteSetting } = await import("./settings-NCLN4JDZ.js");
2978
2709
  deleteSetting(`${SKILL_SETTING_PREFIX}${skillId}`);
2979
- return true;
2980
- } catch {
2981
- return false;
2982
2710
  }
2711
+ return deleted;
2983
2712
  }
2984
2713
 
2985
2714
  // src/server/chat/agent-loop.ts
2715
+ import stripAnsi from "strip-ansi";
2716
+ function emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, promptContext, eventStore) {
2717
+ const stats = turnMetrics.buildStats(statsIdentity, mode);
2718
+ eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
2719
+ stats,
2720
+ partial: true,
2721
+ promptContext
2722
+ }));
2723
+ eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats));
2724
+ }
2986
2725
  function getCurrentWindowMessageOptions(sessionId) {
2987
2726
  const contextWindowId = getCurrentContextWindowId(sessionId);
2988
2727
  return contextWindowId ? { contextWindowId } : void 0;
@@ -3048,7 +2787,7 @@ async function executeToolBatch(assistantMsgId, toolCalls, ctx) {
3048
2787
  type: "chat.ask_user",
3049
2788
  data: { callId: error.callId, question: error.question }
3050
2789
  });
3051
- const { awaitAnswer } = await import("./ask-JQQNGB5K.js");
2790
+ const { awaitAnswer } = await import("./ask-3RK5YJZE.js");
3052
2791
  const answerPromise = awaitAnswer(error.callId);
3053
2792
  if (!answerPromise) {
3054
2793
  throw new Error(`No pending question found for callId: ${error.callId}`);
@@ -3073,9 +2812,9 @@ async function executeToolBatch(assistantMsgId, toolCalls, ctx) {
3073
2812
  }
3074
2813
  toolMessages.push({
3075
2814
  role: "tool",
3076
- content: toolResult.success ? toolResult.output ?? "Success" : toolResult.output ? `${toolResult.output}
2815
+ content: stripAnsi(toolResult.success ? toolResult.output ?? "Success" : toolResult.output ? `${toolResult.output}
3077
2816
 
3078
- Error: ${toolResult.error}` : `Error: ${toolResult.error}`,
2817
+ Error: ${toolResult.error}` : `Error: ${toolResult.error}`),
3079
2818
  source: "history",
3080
2819
  toolCallId: toolCall.id
3081
2820
  });
@@ -3213,13 +2952,7 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3213
2952
  }
3214
2953
  }
3215
2954
  if (result.aborted) {
3216
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3217
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3218
- stats: stats2,
3219
- partial: true,
3220
- promptContext: assembledRequest.promptContext
3221
- }));
3222
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2955
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3223
2956
  throw new Error("Aborted");
3224
2957
  }
3225
2958
  turnMetrics.addLLMCall(result.timing, result.usage.promptTokens, result.usage.completionTokens);
@@ -3254,25 +2987,13 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3254
2987
  }
3255
2988
  } catch (error) {
3256
2989
  if (error instanceof Error && error.message === "Aborted") {
3257
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3258
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3259
- stats: stats2,
3260
- partial: true,
3261
- promptContext: assembledRequest.promptContext
3262
- }));
3263
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2990
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3264
2991
  throw error;
3265
2992
  }
3266
2993
  throw error;
3267
2994
  }
3268
2995
  if (signal?.aborted) {
3269
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3270
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3271
- stats: stats2,
3272
- partial: true,
3273
- promptContext: assembledRequest.promptContext
3274
- }));
3275
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2996
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3276
2997
  throw new Error("Aborted");
3277
2998
  }
3278
2999
  const asapMessages = sessionManager.drainAsapMessages(sessionId);
@@ -3310,12 +3031,32 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3310
3031
  };
3311
3032
  }
3312
3033
 
3034
+ // src/server/context/nudge-helpers.ts
3035
+ function appendNudgeMessage(eventStore, sessionId, content, currentWindowMessageOptions, options) {
3036
+ const msgId = crypto.randomUUID();
3037
+ eventStore.append(sessionId, createMessageStartEvent(msgId, "user", content, {
3038
+ ...currentWindowMessageOptions ?? {},
3039
+ isSystemGenerated: true,
3040
+ messageKind: "correction",
3041
+ subAgentId: options.subAgentId,
3042
+ subAgentType: options.subAgentType
3043
+ }));
3044
+ eventStore.append(sessionId, { type: "message.done", data: { messageId: msgId } });
3045
+ }
3046
+
3313
3047
  // src/server/sub-agents/manager.ts
3314
3048
  var RETURN_VALUE_INSTRUCTION = `
3315
3049
 
3316
3050
  ## RETURN VALUE
3317
3051
  As the very last thing you do, call \`return_value\` ONCE with a structured summary of your work. This is how your findings get passed back to the calling agent. Do not finish without calling return_value.`;
3318
3052
  var RETURN_VALUE_NUDGE = "You must call return_value with a summary of your findings before finishing. Call return_value now.";
3053
+ function buildSubAgentResult(returnValueContent, returnValueResult, subAgentType, failed, remaining) {
3054
+ return {
3055
+ content: returnValueContent ?? "",
3056
+ ...returnValueResult !== null && returnValueResult !== void 0 ? { result: returnValueResult } : {},
3057
+ ...subAgentType === "verifier" ? { allPassed: failed.length === 0 && remaining.length === 0, failed } : {}
3058
+ };
3059
+ }
3319
3060
  async function resolveAgentDef(agentId) {
3320
3061
  const allAgents = await loadAllAgentsDefault();
3321
3062
  const def = findAgentById(agentId, allAgents);
@@ -3485,19 +3226,11 @@ async function executeSubAgent(options) {
3485
3226
  if (consecutiveEmptyStops < nudgeConfig.maxConsecutiveNudges) {
3486
3227
  consecutiveEmptyStops += 1;
3487
3228
  const nudgeContent = nudgeConfig.buildNudgeContent(criteriaAwaiting);
3488
- const nudgeMsgId = crypto.randomUUID();
3489
3229
  eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3490
3230
  segments: result.segments,
3491
3231
  promptContext
3492
3232
  }));
3493
- eventStore.append(sessionId, createMessageStartEvent(nudgeMsgId, "user", nudgeContent, {
3494
- ...currentWindowMessageOptions ?? {},
3495
- isSystemGenerated: true,
3496
- messageKind: "correction",
3497
- subAgentId,
3498
- subAgentType
3499
- }));
3500
- eventStore.append(sessionId, { type: "message.done", data: { messageId: nudgeMsgId } });
3233
+ appendNudgeMessage(eventStore, sessionId, nudgeContent, currentWindowMessageOptions, { subAgentId, subAgentType });
3501
3234
  customMessages = [...customMessages, { role: "user", content: nudgeContent, source: "runtime" }];
3502
3235
  continue;
3503
3236
  }
@@ -3514,19 +3247,11 @@ async function executeSubAgent(options) {
3514
3247
  }
3515
3248
  if (!returnValueContent && !returnValueNudged) {
3516
3249
  returnValueNudged = true;
3517
- const nudgeMsgId = crypto.randomUUID();
3518
3250
  eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3519
3251
  segments: result.segments,
3520
3252
  promptContext
3521
3253
  }));
3522
- eventStore.append(sessionId, createMessageStartEvent(nudgeMsgId, "user", RETURN_VALUE_NUDGE, {
3523
- ...currentWindowMessageOptions ?? {},
3524
- isSystemGenerated: true,
3525
- messageKind: "correction",
3526
- subAgentId,
3527
- subAgentType
3528
- }));
3529
- eventStore.append(sessionId, { type: "message.done", data: { messageId: nudgeMsgId } });
3254
+ appendNudgeMessage(eventStore, sessionId, RETURN_VALUE_NUDGE, currentWindowMessageOptions, { subAgentId, subAgentType });
3530
3255
  customMessages = [...customMessages, { role: "user", content: RETURN_VALUE_NUDGE, source: "runtime" }];
3531
3256
  continue;
3532
3257
  }
@@ -3582,21 +3307,9 @@ async function executeSubAgent(options) {
3582
3307
  subAgentId,
3583
3308
  resultLength: finalContent.length
3584
3309
  });
3585
- if (subAgentType === "verifier") {
3586
- session = sessionManager.requireSession(sessionId);
3587
- const remaining = nudgeConfig?.getCriteriaAwaiting(session.criteria) ?? [];
3588
- const failed = session.criteria.filter((c) => c.status.type === "failed").map((c) => ({
3589
- id: c.id,
3590
- reason: c.status.type === "failed" ? c.status.reason : "unknown"
3591
- }));
3592
- return {
3593
- content: returnValueContent ?? finalContent,
3594
- ...returnValueResult ? { result: returnValueResult } : {},
3595
- allPassed: failed.length === 0 && remaining.length === 0,
3596
- failed
3597
- };
3598
- }
3599
- return { content: returnValueContent ?? finalContent, ...returnValueResult ? { result: returnValueResult } : { result: "success" } };
3310
+ const failed = session.criteria.filter((c) => c.status.type === "failed").map((c) => ({ id: c.id, reason: c.status.reason ?? "unknown" }));
3311
+ const remaining = nudgeConfig?.getCriteriaAwaiting(session.criteria) ?? [];
3312
+ return buildSubAgentResult(returnValueContent ?? void 0, returnValueResult ?? (subAgentType !== "verifier" ? "success" : void 0), subAgentType, failed, remaining);
3600
3313
  }
3601
3314
 
3602
3315
  // src/server/tools/sub-agent.ts
@@ -3664,7 +3377,7 @@ var callSubAgentTool = {
3664
3377
  };
3665
3378
  }
3666
3379
  try {
3667
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-JWAF3MAR.js");
3380
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-VOWNOP6V.js");
3668
3381
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3669
3382
  const turnMetrics = new TurnMetrics();
3670
3383
  const result = await executeSubAgent({
@@ -3816,7 +3529,7 @@ function convertHTMLToMarkdown(html) {
3816
3529
  return turndown.turndown(html);
3817
3530
  }
3818
3531
  function stripHTMLTags(html) {
3819
- let text = html.replace(/<(script|style|noscript|iframe|object|embed)[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<[^>]+>/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
3532
+ const text = html.replace(/<(script|style|noscript|iframe|object|embed)[^>]*>[\s\S]*?<\/\1>/gi, "").replace(/<[^>]+>/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
3820
3533
  return text;
3821
3534
  }
3822
3535
  var webFetchTool = createTool(
@@ -3918,9 +3631,9 @@ var webFetchTool = createTool(
3918
3631
  );
3919
3632
 
3920
3633
  // src/server/dev-server/manager.ts
3921
- import { spawn as spawn3 } from "child_process";
3922
- import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
3923
- import { resolve as resolve5, join as join5 } from "path";
3634
+ import { spawn as spawn4 } from "child_process";
3635
+ import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
3636
+ import { resolve as resolve5, join as join7 } from "path";
3924
3637
  var MAX_LOG_LINES = 2e3;
3925
3638
  var MAX_LOG_BYTES = 1e5;
3926
3639
  var CONFIG_PATH = ".openfox/dev.json";
@@ -3976,8 +3689,8 @@ var DevServerManager = class {
3976
3689
  }
3977
3690
  async loadConfig(workdir) {
3978
3691
  try {
3979
- const configPath = join5(this.resolveWorkdir(workdir), CONFIG_PATH);
3980
- const raw = await readFile7(configPath, "utf-8");
3692
+ const configPath = join7(this.resolveWorkdir(workdir), CONFIG_PATH);
3693
+ const raw = await readFile8(configPath, "utf-8");
3981
3694
  const parsed = JSON.parse(raw);
3982
3695
  if (!parsed.command || !parsed.url) return null;
3983
3696
  return {
@@ -3991,9 +3704,9 @@ var DevServerManager = class {
3991
3704
  }
3992
3705
  async saveConfig(workdir, config) {
3993
3706
  const resolved = this.resolveWorkdir(workdir);
3994
- const dirPath = join5(resolved, ".openfox");
3707
+ const dirPath = join7(resolved, ".openfox");
3995
3708
  await mkdir4(dirPath, { recursive: true });
3996
- const configPath = join5(resolved, CONFIG_PATH);
3709
+ const configPath = join7(resolved, CONFIG_PATH);
3997
3710
  await writeFile5(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3998
3711
  }
3999
3712
  async start(workdir) {
@@ -4012,7 +3725,7 @@ var DevServerManager = class {
4012
3725
  instance.exited = false;
4013
3726
  const resolved = this.resolveWorkdir(workdir);
4014
3727
  const shell = getPlatformShell();
4015
- const proc = spawn3(shell.command, [...shell.args, config.command], {
3728
+ const proc = spawn4(shell.command, [...shell.args, config.command], {
4016
3729
  cwd: resolved,
4017
3730
  env: { ...process.env, FORCE_COLOR: "1" },
4018
3731
  stdio: ["ignore", "pipe", "pipe"],
@@ -4584,14 +4297,7 @@ function getCurrentWindowMessageOptions3(sessionId) {
4584
4297
  return contextWindowId ? { contextWindowId } : void 0;
4585
4298
  }
4586
4299
  function toRequestContextMessages2(messages) {
4587
- return messages.map((message) => ({
4588
- role: message.role,
4589
- content: message.content,
4590
- source: "history",
4591
- ...message.toolCalls ? { toolCalls: message.toolCalls } : {},
4592
- ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
4593
- ...message.attachments ? { attachments: message.attachments } : {}
4594
- }));
4300
+ return minimalMessagesToRequestContextMessages(messages, "history");
4595
4301
  }
4596
4302
  async function maybeAutoCompactContext(options) {
4597
4303
  const config = getRuntimeConfig();
@@ -4746,6 +4452,9 @@ export {
4746
4452
  PathAccessDeniedError,
4747
4453
  providePathConfirmation,
4748
4454
  cancelPathConfirmationsForSession,
4455
+ checkAborted,
4456
+ spawnShellProcess,
4457
+ findModifiedDefaultFiles,
4749
4458
  ensureDefaultAgents,
4750
4459
  loadAllAgents,
4751
4460
  loadAllAgentsDefault,
@@ -4769,6 +4478,15 @@ export {
4769
4478
  createChatDoneEvent,
4770
4479
  getAllInstructions,
4771
4480
  assembleAgentRequest,
4481
+ getBundleDir,
4482
+ dirExists,
4483
+ ensureDir,
4484
+ loadItems,
4485
+ saveItem,
4486
+ deleteItem,
4487
+ findById,
4488
+ getDefaultIds,
4489
+ restoreDefault,
4772
4490
  ensureDefaultSkills,
4773
4491
  loadAllSkills,
4774
4492
  getEnabledSkillMetadata,
@@ -4797,4 +4515,4 @@ export {
4797
4515
  getToolRegistryForAgent,
4798
4516
  createToolRegistry
4799
4517
  };
4800
- //# sourceMappingURL=chunk-POJO7A5H.js.map
4518
+ //# sourceMappingURL=chunk-3WHZ47PY.js.map