openfox 1.6.14 → 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 (39) hide show
  1. package/dist/{auto-compaction-C4PMBB24.js → auto-compaction-JZIM73ZY.js} +5 -5
  2. package/dist/{chat-handler-US3QOOE4.js → chat-handler-F2A2IMQB.js} +26 -56
  3. package/dist/{chunk-7HLRI6JM.js → chunk-2KP34IDL.js} +161 -204
  4. package/dist/{chunk-N25QUEPO.js → chunk-3WHZ47PY.js} +506 -786
  5. package/dist/{chunk-XFXOSPYH.js → chunk-55N6FAAZ.js} +1 -1
  6. package/dist/{chunk-5GE3QJSZ.js → chunk-6L3X7T4K.js} +82 -160
  7. package/dist/{chunk-QDEKU5RL.js → chunk-F54ZJN4X.js} +38 -2
  8. package/dist/{chunk-3JU6H6A4.js → chunk-G4E72ST3.js} +116 -241
  9. package/dist/{chunk-UL4JYQKK.js → chunk-IN5EP4ZB.js} +2 -2
  10. package/dist/{chunk-PCOG5FQD.js → chunk-JFKYCGZ5.js} +6 -6
  11. package/dist/{chunk-OOIRCXAY.js → chunk-KOUMYBYM.js} +53 -112
  12. package/dist/{chunk-3XZ23PXM.js → chunk-OVLFEBRR.js} +76 -92
  13. package/dist/chunk-SN7OBEVL.js +44 -0
  14. package/dist/{chunk-H22VHHQ4.js → chunk-YM6VHAPM.js} +4 -4
  15. package/dist/{chunk-J2DHVXRX.js → chunk-ZDNXCVW4.js} +2 -2
  16. package/dist/cli/dev.js +1 -1
  17. package/dist/cli/index.js +1 -1
  18. package/dist/{config-DEM6ASQM.js → config-67AX6CNS.js} +5 -5
  19. package/dist/{events-VDAC7E7K.js → events-2ETDOE5B.js} +3 -3
  20. package/dist/{folding-NDFQCISZ.js → folding-M7FMUBOL.js} +6 -4
  21. package/dist/{orchestrator-KZI7B5IS.js → orchestrator-SRSG2SIZ.js} +6 -6
  22. package/dist/package.json +13 -3
  23. package/dist/{processor-KBSF2HFD.js → processor-H5BQ5HQM.js} +24 -49
  24. package/dist/{provider-K3PHZO27.js → provider-DKGBQHUS.js} +7 -7
  25. package/dist/{serve-DERN4AB6.js → serve-5AY7GMN7.js} +12 -12
  26. package/dist/server/index.d.ts +2 -0
  27. package/dist/server/index.js +10 -10
  28. package/dist/{tools-PLVK22CF.js → tools-VOWNOP6V.js} +5 -5
  29. package/dist/{vision-fallback-NOC3YYIB.js → vision-fallback-ADYRFFD4.js} +2 -2
  30. package/dist/web/assets/{index-MCWDS5UQ.css → index-CNeIjdxm.css} +1 -1
  31. package/dist/web/assets/index-mBLhctLW.js +150 -0
  32. package/dist/web/index.html +2 -2
  33. package/dist/web/sw.js +1 -1
  34. package/package.json +13 -3
  35. package/dist/web/assets/index-Ccy2Csbl.js +0 -150
  36. /package/dist/{command-defaults → server/commands/defaults}/commit-push.command.md +0 -0
  37. /package/dist/{command-defaults → server/commands/defaults}/init.command.md +0 -0
  38. /package/dist/{command-defaults → server/commands/defaults}/test-ui.command.md +0 -0
  39. /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-5GE3QJSZ.js";
21
+ } from "./chunk-6L3X7T4K.js";
22
22
  import {
23
23
  createChatDoneMessage,
24
24
  createChatMessageMessage,
@@ -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);
@@ -1461,6 +1495,39 @@ function cancelPathConfirmationsForSession(sessionId, reason) {
1461
1495
  }
1462
1496
 
1463
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
+ }
1464
1531
  function createTool(name, definition, handler2) {
1465
1532
  return {
1466
1533
  name,
@@ -1516,7 +1583,7 @@ function createTool(name, definition, handler2) {
1516
1583
 
1517
1584
  // src/server/tools/file-tracker.ts
1518
1585
  import { createHash } from "crypto";
1519
- import { readFile as readFile3 } from "fs/promises";
1586
+ import { readFile as readFile4 } from "fs/promises";
1520
1587
  import { resolve as resolve3 } from "path";
1521
1588
  var FileNotReadError = class extends Error {
1522
1589
  constructor(path) {
@@ -1532,7 +1599,7 @@ var FileChangedExternallyError = class extends Error {
1532
1599
  };
1533
1600
  async function computeFileHash(filePath) {
1534
1601
  try {
1535
- const content = await readFile3(filePath);
1602
+ const content = await readFile4(filePath);
1536
1603
  const hash = createHash("sha256");
1537
1604
  hash.update(content);
1538
1605
  return hash.digest("hex");
@@ -1665,7 +1732,7 @@ var readFileTool = createTool(
1665
1732
  } catch {
1666
1733
  return helpers.error(`File not found: ${args.path}`);
1667
1734
  }
1668
- const rawBuffer = await readFile4(fullPath);
1735
+ const rawBuffer = await readFile5(fullPath);
1669
1736
  const mimeType = detectImageType(rawBuffer, args.path);
1670
1737
  if (mimeType) {
1671
1738
  const base64Data = rawBuffer.toString("base64");
@@ -1799,7 +1866,7 @@ var writeFileTool = createTool(
1799
1866
  );
1800
1867
 
1801
1868
  // src/server/tools/edit.ts
1802
- import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1869
+ import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1803
1870
 
1804
1871
  // src/server/tools/edit-context.ts
1805
1872
  var CONTEXT_LINES = 4;
@@ -1955,7 +2022,7 @@ var editFileTool = createTool(
1955
2022
  }
1956
2023
  let content;
1957
2024
  try {
1958
- content = await readFile5(fullPath, "utf-8");
2025
+ content = await readFile6(fullPath, "utf-8");
1959
2026
  } catch {
1960
2027
  return helpers.error(`File not found: ${args.path}`);
1961
2028
  }
@@ -2021,11 +2088,26 @@ Make sure whitespace and indentation match exactly.`
2021
2088
  );
2022
2089
 
2023
2090
  // src/server/tools/shell.ts
2024
- import { spawn as spawn2 } from "child_process";
2091
+ import "child_process";
2025
2092
  import { resolve as resolve4, isAbsolute as isAbsolute2 } from "path";
2026
2093
 
2027
- // src/server/utils/process-tree.ts
2094
+ // src/server/utils/shell.ts
2028
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";
2029
2111
  import { setTimeout as sleep } from "timers/promises";
2030
2112
  var SIGKILL_TIMEOUT_MS = 200;
2031
2113
  async function terminateProcessTree(proc, options) {
@@ -2035,7 +2117,7 @@ async function terminateProcessTree(proc, options) {
2035
2117
  }
2036
2118
  if (process.platform === "win32") {
2037
2119
  await new Promise((resolve6) => {
2038
- const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
2120
+ const killer = spawn2("taskkill", ["/pid", String(pid), "/f", "/t"], {
2039
2121
  stdio: "ignore",
2040
2122
  windowsHide: true
2041
2123
  });
@@ -2139,18 +2221,11 @@ ${result.stderr}`;
2139
2221
  );
2140
2222
  function executeCommand(command, cwd, timeout, signal, onProgress) {
2141
2223
  return new Promise((resolve6, reject) => {
2142
- if (signal?.aborted) {
2224
+ if (checkAborted(signal)) {
2143
2225
  reject(new Error("Command aborted before execution"));
2144
2226
  return;
2145
2227
  }
2146
- const shell = getPlatformShell();
2147
- const proc = spawn2(shell.command, [...shell.args, command], {
2148
- cwd,
2149
- env: { ...process.env, FORCE_COLOR: "0" },
2150
- stdio: ["ignore", "pipe", "pipe"],
2151
- detached: true
2152
- // Create new process group so we can kill all children
2153
- });
2228
+ const proc = spawnShellProcess(command, cwd, signal, true);
2154
2229
  let stdout = "";
2155
2230
  let stderr = "";
2156
2231
  let killed = false;
@@ -2168,12 +2243,12 @@ function executeCommand(command, cwd, timeout, signal, onProgress) {
2168
2243
  }
2169
2244
  };
2170
2245
  signal?.addEventListener("abort", onAbort);
2171
- proc.stdout.on("data", (data) => {
2246
+ proc.stdout?.on("data", (data) => {
2172
2247
  const chunk = data.toString();
2173
2248
  stdout += chunk;
2174
2249
  onProgress?.(`[stdout] ${chunk}`);
2175
2250
  });
2176
- proc.stderr.on("data", (data) => {
2251
+ proc.stderr?.on("data", (data) => {
2177
2252
  const chunk = data.toString();
2178
2253
  stderr += chunk;
2179
2254
  onProgress?.(`[stderr] ${chunk}`);
@@ -2216,10 +2291,27 @@ function formatCriteriaList(criteria) {
2216
2291
  if (criteria.length === 0) return "No criteria defined.";
2217
2292
  return criteria.map((c, i) => `${i + 1}. [${c.id}] ${c.description}`).join("\n");
2218
2293
  }
2219
- var criterionTool = {
2220
- name: "criterion",
2221
- permittedActions: ["get", "add", "update", "remove", "complete", "pass", "fail"],
2222
- 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
+ {
2223
2315
  type: "function",
2224
2316
  function: {
2225
2317
  name: "criterion",
@@ -2249,267 +2341,90 @@ var criterionTool = {
2249
2341
  }
2250
2342
  }
2251
2343
  },
2252
- async execute(args, context) {
2253
- const startTime = Date.now();
2254
- try {
2255
- const action = args["action"];
2256
- const id = args["id"];
2257
- const description = args["description"];
2258
- const reason = args["reason"];
2259
- if (!action || !["get", "add", "update", "remove", "complete", "pass", "fail"].includes(action)) {
2260
- return {
2261
- success: false,
2262
- error: `Invalid action: ${action}. Must be one of: get, add, update, remove, complete, pass, fail`,
2263
- durationMs: Date.now() - startTime,
2264
- truncated: false
2265
- };
2266
- }
2267
- const permittedActions = context.permittedActions?.["criterion"];
2268
- if (permittedActions && !permittedActions.includes(action)) {
2269
- return {
2270
- success: false,
2271
- error: `Action '${action}' not allowed. Available: ${permittedActions.join(", ")}`,
2272
- durationMs: Date.now() - startTime,
2273
- truncated: false
2274
- };
2275
- }
2276
- const session = context.sessionManager.requireSession(context.sessionId);
2277
- if (action === "get") {
2278
- const criteria = session.criteria;
2279
- return {
2280
- success: true,
2281
- output: criteria.length === 0 ? "No criteria defined yet." : JSON.stringify(criteria.map((c) => ({
2282
- id: c.id,
2283
- description: c.description
2284
- })), null, 2),
2285
- durationMs: Date.now() - startTime,
2286
- truncated: false
2287
- };
2288
- }
2289
- if (action === "add") {
2290
- if (!description) {
2291
- return {
2292
- success: false,
2293
- error: "Missing required field: description",
2294
- durationMs: Date.now() - startTime,
2295
- truncated: false
2296
- };
2297
- }
2298
- const criterion = {
2299
- id: id || "",
2300
- description,
2301
- status: { type: "pending" },
2302
- attempts: []
2303
- };
2304
- const result = context.sessionManager.addCriterion(context.sessionId, criterion);
2305
- if ("error" in result) {
2306
- return { success: false, error: result.error, durationMs: Date.now() - startTime, truncated: false };
2307
- }
2308
- return {
2309
- success: true,
2310
- output: `Added criterion "${result.actualId}". Current criteria:
2311
- ${formatCriteriaList(result.criteria)}`,
2312
- durationMs: Date.now() - startTime,
2313
- truncated: false
2314
- };
2315
- }
2316
- if (action === "update") {
2317
- if (!id) {
2318
- return {
2319
- success: false,
2320
- error: "Missing required field: id",
2321
- durationMs: Date.now() - startTime,
2322
- truncated: false
2323
- };
2324
- }
2325
- if (!description) {
2326
- return {
2327
- success: false,
2328
- error: "Missing required field: description",
2329
- durationMs: Date.now() - startTime,
2330
- truncated: false
2331
- };
2332
- }
2333
- if (!session.criteria.find((c) => c.id === id)) {
2334
- return {
2335
- success: false,
2336
- error: `Criterion "${id}" not found`,
2337
- durationMs: Date.now() - startTime,
2338
- truncated: false
2339
- };
2340
- }
2341
- const criteria = context.sessionManager.updateCriterionFull(context.sessionId, id, { description });
2342
- return {
2343
- success: true,
2344
- output: `Updated criterion "${id}". Current criteria:
2345
- ${formatCriteriaList(criteria)}`,
2346
- durationMs: Date.now() - startTime,
2347
- truncated: false
2348
- };
2349
- }
2350
- if (action === "remove") {
2351
- if (!id) {
2352
- return {
2353
- success: false,
2354
- error: "Missing required field: id",
2355
- durationMs: Date.now() - startTime,
2356
- truncated: false
2357
- };
2358
- }
2359
- if (!session.criteria.find((c) => c.id === id)) {
2360
- return {
2361
- success: false,
2362
- error: `Criterion "${id}" not found`,
2363
- durationMs: Date.now() - startTime,
2364
- truncated: false
2365
- };
2366
- }
2367
- const criteria = context.sessionManager.removeCriterion(context.sessionId, id);
2368
- return {
2369
- success: true,
2370
- output: criteria.length === 0 ? `Removed criterion "${id}". No criteria remaining.` : `Removed criterion "${id}". Current criteria:
2371
- ${formatCriteriaList(criteria)}`,
2372
- durationMs: Date.now() - startTime,
2373
- truncated: false
2374
- };
2375
- }
2376
- if (action === "complete") {
2377
- if (!id) {
2378
- return {
2379
- success: false,
2380
- error: "Missing required field: id",
2381
- durationMs: Date.now() - startTime,
2382
- truncated: false
2383
- };
2384
- }
2385
- const criterion = session.criteria.find((c) => c.id === id);
2386
- if (!criterion) {
2387
- return {
2388
- success: false,
2389
- error: `Criterion "${id}" not found. Available: ${session.criteria.map((c) => c.id).join(", ")}`,
2390
- durationMs: Date.now() - startTime,
2391
- truncated: false
2392
- };
2393
- }
2394
- const status = {
2395
- type: "completed",
2396
- completedAt: (/* @__PURE__ */ new Date()).toISOString(),
2397
- ...reason ? { reason } : {}
2398
- };
2399
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2400
- return {
2401
- success: true,
2402
- output: `Criterion "${id}" marked as completed.${reason ? ` Reason: ${reason}` : ""}`,
2403
- durationMs: Date.now() - startTime,
2404
- truncated: false
2405
- };
2406
- }
2407
- if (action === "pass") {
2408
- if (!id) {
2409
- return {
2410
- success: false,
2411
- error: "Missing required field: id",
2412
- durationMs: Date.now() - startTime,
2413
- truncated: false
2414
- };
2415
- }
2416
- const criterion = session.criteria.find((c) => c.id === id);
2417
- if (!criterion) {
2418
- return {
2419
- success: false,
2420
- error: `Criterion "${id}" not found`,
2421
- durationMs: Date.now() - startTime,
2422
- truncated: false
2423
- };
2424
- }
2425
- const status = {
2426
- type: "passed",
2427
- verifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
2428
- ...reason ? { reason } : {}
2429
- };
2430
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2431
- context.sessionManager.addCriterionAttempt(context.sessionId, id, {
2432
- attemptNumber: criterion.attempts.length + 1,
2433
- status: "passed",
2434
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2435
- ...reason && { details: reason }
2436
- });
2437
- return {
2438
- success: true,
2439
- output: `Criterion "${id}" verified as PASSED.${reason ? ` Verification: ${reason}` : ""}`,
2440
- durationMs: Date.now() - startTime,
2441
- truncated: false
2442
- };
2443
- }
2444
- if (action === "fail") {
2445
- if (!id) {
2446
- return {
2447
- success: false,
2448
- error: "Missing required field: id",
2449
- durationMs: Date.now() - startTime,
2450
- truncated: false
2451
- };
2452
- }
2453
- if (!reason) {
2454
- return {
2455
- success: false,
2456
- error: "Missing required field: reason",
2457
- durationMs: Date.now() - startTime,
2458
- truncated: false
2459
- };
2460
- }
2461
- const criterion = session.criteria.find((c) => c.id === id);
2462
- if (!criterion) {
2463
- return {
2464
- success: false,
2465
- error: `Criterion "${id}" not found`,
2466
- durationMs: Date.now() - startTime,
2467
- truncated: false
2468
- };
2469
- }
2470
- const status = {
2471
- type: "failed",
2472
- failedAt: (/* @__PURE__ */ new Date()).toISOString(),
2473
- reason
2474
- };
2475
- context.sessionManager.updateCriterionStatus(context.sessionId, id, status);
2476
- context.sessionManager.addCriterionAttempt(context.sessionId, id, {
2477
- attemptNumber: criterion.attempts.length + 1,
2478
- status: "failed",
2479
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2480
- details: reason
2481
- });
2482
- return {
2483
- success: true,
2484
- output: `Criterion "${id}" marked as FAILED. Reason: ${reason}`,
2485
- durationMs: Date.now() - startTime,
2486
- truncated: false
2487
- };
2488
- }
2489
- return {
2490
- success: false,
2491
- error: "Unexpected error",
2492
- durationMs: Date.now() - startTime,
2493
- truncated: false
2494
- };
2495
- } catch (error) {
2496
- return {
2497
- success: false,
2498
- error: error instanceof Error ? error.message : "Unknown error",
2499
- durationMs: Date.now() - startTime,
2500
- 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: []
2501
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);
2502
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");
2503
2406
  }
2504
- };
2407
+ );
2505
2408
 
2506
2409
  // src/server/tools/todo.ts
2507
2410
  var sessionTodos = /* @__PURE__ */ new Map();
2508
2411
  var onTodoUpdate = null;
2509
- var todoTool = {
2510
- name: "todo",
2511
- permittedActions: ["list", "write", "add", "update", "remove"],
2512
- 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
+ {
2513
2428
  type: "function",
2514
2429
  function: {
2515
2430
  name: "todo",
@@ -2552,235 +2467,65 @@ var todoTool = {
2552
2467
  }
2553
2468
  }
2554
2469
  },
2555
- async execute(args, context) {
2556
- const startTime = Date.now();
2557
- try {
2558
- const action = args["action"];
2559
- const todos = args["todos"];
2560
- const content = args["content"];
2561
- const index = args["index"];
2562
- const status = args["status"];
2563
- if (!action || !["list", "write", "add", "update", "remove"].includes(action)) {
2564
- return {
2565
- success: false,
2566
- error: `Invalid action: ${action}. Must be one of: list, write, add, update, remove`,
2567
- durationMs: Date.now() - startTime,
2568
- truncated: false
2569
- };
2570
- }
2571
- const permittedActions = context.permittedActions?.["todo"];
2572
- if (permittedActions && !permittedActions.includes(action)) {
2573
- return {
2574
- success: false,
2575
- error: `Action '${action}' not allowed. Available: ${permittedActions.join(", ")}`,
2576
- durationMs: Date.now() - startTime,
2577
- truncated: false
2578
- };
2579
- }
2580
- if (action === "list") {
2581
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2582
- return {
2583
- success: true,
2584
- output: todosList.length === 0 ? "No tasks defined yet." : JSON.stringify(todosList, null, 2),
2585
- durationMs: Date.now() - startTime,
2586
- truncated: false
2587
- };
2588
- }
2589
- if (action === "write") {
2590
- if (!todos) {
2591
- return {
2592
- success: false,
2593
- error: "Missing required field: todos",
2594
- durationMs: Date.now() - startTime,
2595
- truncated: false
2596
- };
2597
- }
2598
- if (!Array.isArray(todos)) {
2599
- return {
2600
- success: false,
2601
- error: "todos must be an array",
2602
- durationMs: Date.now() - startTime,
2603
- truncated: false
2604
- };
2605
- }
2606
- for (const todo of todos) {
2607
- if (!todo.content || !todo.status) {
2608
- return {
2609
- success: false,
2610
- error: "Each todo must have content and status",
2611
- durationMs: Date.now() - startTime,
2612
- truncated: false
2613
- };
2614
- }
2615
- if (!["pending", "in_progress", "completed"].includes(todo.status)) {
2616
- return {
2617
- success: false,
2618
- error: `Invalid status: ${todo.status}. Must be pending, in_progress, or completed`,
2619
- durationMs: Date.now() - startTime,
2620
- truncated: false
2621
- };
2622
- }
2623
- }
2624
- sessionTodos.set(context.sessionId, todos);
2625
- if (onTodoUpdate) {
2626
- onTodoUpdate(context.sessionId, todos);
2627
- }
2628
- const pending = todos.filter((t) => t.status === "pending").length;
2629
- const inProgress = todos.filter((t) => t.status === "in_progress").length;
2630
- const completed = todos.filter((t) => t.status === "completed").length;
2631
- return {
2632
- success: true,
2633
- output: `Task list updated: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2634
- durationMs: Date.now() - startTime,
2635
- truncated: false
2636
- };
2637
- }
2638
- if (action === "add") {
2639
- if (!content) {
2640
- return {
2641
- success: false,
2642
- error: "Missing required field: content",
2643
- durationMs: Date.now() - startTime,
2644
- truncated: false
2645
- };
2646
- }
2647
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2648
- const newTodo = { content, status: "pending" };
2649
- todosList.push(newTodo);
2650
- sessionTodos.set(context.sessionId, todosList);
2651
- if (onTodoUpdate) {
2652
- onTodoUpdate(context.sessionId, todosList);
2653
- }
2654
- const pending = todosList.filter((t) => t.status === "pending").length;
2655
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2656
- const completed = todosList.filter((t) => t.status === "completed").length;
2657
- return {
2658
- success: true,
2659
- output: `Added task "${content}". Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2660
- durationMs: Date.now() - startTime,
2661
- truncated: false
2662
- };
2663
- }
2664
- if (action === "update") {
2665
- if (index === void 0) {
2666
- return {
2667
- success: false,
2668
- error: "Missing required field: index",
2669
- durationMs: Date.now() - startTime,
2670
- truncated: false
2671
- };
2672
- }
2673
- if (!content && !status) {
2674
- return {
2675
- success: false,
2676
- error: "update requires content or status",
2677
- durationMs: Date.now() - startTime,
2678
- truncated: false
2679
- };
2680
- }
2681
- if (status && !["pending", "in_progress", "completed"].includes(status)) {
2682
- return {
2683
- success: false,
2684
- error: `Invalid status: ${status}. Must be pending, in_progress, or completed`,
2685
- durationMs: Date.now() - startTime,
2686
- truncated: false
2687
- };
2688
- }
2689
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2690
- if (index < 0 || index >= todosList.length) {
2691
- return {
2692
- success: false,
2693
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2694
- durationMs: Date.now() - startTime,
2695
- truncated: false
2696
- };
2697
- }
2698
- const todo = todosList[index];
2699
- if (!todo) {
2700
- return {
2701
- success: false,
2702
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2703
- durationMs: Date.now() - startTime,
2704
- truncated: false
2705
- };
2706
- }
2707
- if (content) {
2708
- todo.content = content;
2709
- }
2710
- if (status) {
2711
- todo.status = status;
2712
- }
2713
- sessionTodos.set(context.sessionId, todosList);
2714
- if (onTodoUpdate) {
2715
- 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`);
2716
2486
  }
2717
- const pending = todosList.filter((t) => t.status === "pending").length;
2718
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2719
- const completed = todosList.filter((t) => t.status === "completed").length;
2720
- return {
2721
- success: true,
2722
- output: `Updated task ${index}. Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2723
- durationMs: Date.now() - startTime,
2724
- truncated: false
2725
- };
2726
2487
  }
2727
- if (action === "remove") {
2728
- if (index === void 0) {
2729
- return {
2730
- success: false,
2731
- error: "Missing required field: index",
2732
- durationMs: Date.now() - startTime,
2733
- truncated: false
2734
- };
2735
- }
2736
- const todosList = sessionTodos.get(context.sessionId) ?? [];
2737
- if (index < 0 || index >= todosList.length) {
2738
- return {
2739
- success: false,
2740
- error: `Index out of range: ${index}. Valid range: 0-${todosList.length - 1}`,
2741
- durationMs: Date.now() - startTime,
2742
- truncated: false
2743
- };
2744
- }
2745
- const removed = todosList.splice(index, 1)[0];
2746
- sessionTodos.set(context.sessionId, todosList);
2747
- if (onTodoUpdate) {
2748
- onTodoUpdate(context.sessionId, todosList);
2749
- }
2750
- if (todosList.length === 0) {
2751
- return {
2752
- success: true,
2753
- output: `Removed task "${removed.content}". No tasks remaining.`,
2754
- durationMs: Date.now() - startTime,
2755
- truncated: false
2756
- };
2757
- }
2758
- const pending = todosList.filter((t) => t.status === "pending").length;
2759
- const inProgress = todosList.filter((t) => t.status === "in_progress").length;
2760
- const completed = todosList.filter((t) => t.status === "completed").length;
2761
- return {
2762
- success: true,
2763
- output: `Removed task "${removed.content}". Task list: ${completed} completed, ${inProgress} in progress, ${pending} pending`,
2764
- durationMs: Date.now() - startTime,
2765
- truncated: false
2766
- };
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`);
2767
2504
  }
2768
- return {
2769
- success: false,
2770
- error: "Unexpected error",
2771
- durationMs: Date.now() - startTime,
2772
- truncated: false
2773
- };
2774
- } catch (error) {
2775
- return {
2776
- success: false,
2777
- error: error instanceof Error ? error.message : "Unknown error",
2778
- durationMs: Date.now() - startTime,
2779
- truncated: false
2780
- };
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
+ );
2781
2525
  }
2526
+ return helpers.error("Unexpected error");
2782
2527
  }
2783
- };
2528
+ );
2784
2529
 
2785
2530
  // src/server/chat/tool-streaming.ts
2786
2531
  function parseProgressMessage(message) {
@@ -2800,18 +2545,17 @@ function createToolProgressHandler(messageId, callId, onMessage) {
2800
2545
  }
2801
2546
 
2802
2547
  // src/server/skills/registry.ts
2803
- 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";
2804
- 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";
2805
2554
  import { constants as constants3 } from "fs";
2806
2555
  import { fileURLToPath as fileURLToPath2 } from "url";
2807
2556
  import matter2 from "gray-matter";
2808
- var __bundleDir2 = dirname4(fileURLToPath2(import.meta.url));
2809
- var DEFAULTS_DIR2 = join4(__bundleDir2, "defaults");
2810
- var DEFAULTS_DIR_ALT2 = join4(__bundleDir2, "skill-defaults");
2811
- var SKILL_EXTENSION = ".skill.md";
2812
- var SKILL_SETTING_PREFIX = "skill.enabled.";
2813
- function getSkillsDir(configDir) {
2814
- return join4(configDir, "skills");
2557
+ function getBundleDir() {
2558
+ return dirname4(fileURLToPath2(import.meta.url));
2815
2559
  }
2816
2560
  async function dirExists(path) {
2817
2561
  try {
@@ -2821,64 +2565,92 @@ async function dirExists(path) {
2821
2565
  return false;
2822
2566
  }
2823
2567
  }
2824
- async function ensureDefaultSkills(configDir) {
2825
- const skillsDir = getSkillsDir(configDir);
2826
- if (!await dirExists(skillsDir)) {
2827
- await mkdir3(skillsDir, { recursive: true });
2828
- }
2829
- let defaultFiles;
2830
- let sourceDir;
2831
- try {
2832
- defaultFiles = (await readdir2(DEFAULTS_DIR2)).filter((f) => f.endsWith(SKILL_EXTENSION));
2833
- sourceDir = DEFAULTS_DIR2;
2834
- } catch {
2835
- try {
2836
- defaultFiles = (await readdir2(DEFAULTS_DIR_ALT2)).filter((f) => f.endsWith(SKILL_EXTENSION));
2837
- sourceDir = DEFAULTS_DIR_ALT2;
2838
- } catch {
2839
- logger.warn("No bundled skill defaults found", { dir: DEFAULTS_DIR2 });
2840
- return;
2841
- }
2842
- }
2843
- for (const file of defaultFiles) {
2844
- const targetPath = join4(skillsDir, file);
2845
- if (!await dirExists(targetPath)) {
2846
- try {
2847
- await copyFile2(join4(sourceDir, file), targetPath);
2848
- logger.info("Installed default skill", { file });
2849
- } catch (err) {
2850
- logger.error("Failed to copy default skill", { file, error: err instanceof Error ? err.message : String(err) });
2851
- }
2852
- }
2568
+ async function ensureDir(path) {
2569
+ if (!await dirExists(path)) {
2570
+ await mkdir3(path, { recursive: true });
2853
2571
  }
2854
2572
  }
2855
- async function loadAllSkills(configDir) {
2856
- const skillsDir = getSkillsDir(configDir);
2857
- if (!await dirExists(skillsDir)) {
2858
- return [];
2859
- }
2573
+ async function loadItems(dir, extension) {
2574
+ if (!await dirExists(dir)) return [];
2860
2575
  let files;
2861
2576
  try {
2862
- files = (await readdir2(skillsDir)).filter((f) => f.endsWith(SKILL_EXTENSION));
2577
+ files = (await readdir2(dir)).filter((f) => f.endsWith(extension));
2863
2578
  } catch {
2864
2579
  return [];
2865
2580
  }
2866
- const skills = [];
2581
+ const items = [];
2867
2582
  for (const file of files) {
2868
2583
  try {
2869
- const raw = await readFile6(join4(skillsDir, file), "utf-8");
2584
+ const raw = await readFile7(join5(dir, file), "utf-8");
2870
2585
  const { data, content } = matter2(raw);
2871
- const metadata = data;
2872
- if (metadata.id && content.trim()) {
2873
- skills.push({ metadata, prompt: content.trim() });
2586
+ if (data.id && content.trim()) {
2587
+ items.push({ metadata: data, prompt: content.trim() });
2874
2588
  } else {
2875
- logger.warn("Skipping invalid skill file", { file });
2589
+ logger.warn(`Skipping invalid ${extension} file`, { file });
2876
2590
  }
2877
2591
  } catch (err) {
2878
- 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);
2879
2649
  }
2880
2650
  }
2881
- return skills;
2651
+ }
2652
+ async function loadAllSkills(configDir) {
2653
+ return loadItems(getSkillsDir(configDir), SKILL_EXTENSION);
2882
2654
  }
2883
2655
  async function getEnabledSkills(configDir) {
2884
2656
  const all = await loadAllSkills(configDir);
@@ -2897,51 +2669,21 @@ function setSkillEnabled(skillId, enabled) {
2897
2669
  setSetting(`${SKILL_SETTING_PREFIX}${skillId}`, String(enabled));
2898
2670
  }
2899
2671
  async function getDefaultSkillIds() {
2900
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2901
- try {
2902
- const files = (await readdir2(dir)).filter((f) => f.endsWith(SKILL_EXTENSION));
2903
- return files.map((f) => f.replace(SKILL_EXTENSION, ""));
2904
- } catch {
2905
- }
2906
- }
2907
- 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);
2908
2676
  }
2909
2677
  async function restoreDefaultSkill(configDir, skillId) {
2910
- const filename = `${skillId}${SKILL_EXTENSION}`;
2911
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2912
- const sourcePath = join4(dir, filename);
2913
- if (await dirExists(sourcePath)) {
2914
- const targetPath = join4(getSkillsDir(configDir), filename);
2915
- await copyFile2(sourcePath, targetPath);
2916
- return true;
2917
- }
2918
- }
2919
- 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);
2920
2681
  }
2921
2682
  async function getModifiedDefaultSkillIds(configDir) {
2922
- const defaultIds = await getDefaultSkillIds();
2923
- const modified = [];
2924
- for (const id of defaultIds) {
2925
- const filename = `${id}${SKILL_EXTENSION}`;
2926
- const userPath = join4(getSkillsDir(configDir), filename);
2927
- let bundledContent = null;
2928
- for (const dir of [DEFAULTS_DIR2, DEFAULTS_DIR_ALT2]) {
2929
- try {
2930
- bundledContent = await readFile6(join4(dir, filename), "utf-8");
2931
- break;
2932
- } catch {
2933
- }
2934
- }
2935
- if (!bundledContent) continue;
2936
- try {
2937
- const userContent = await readFile6(userPath, "utf-8");
2938
- if (userContent !== bundledContent) {
2939
- modified.push(id);
2940
- }
2941
- } catch {
2942
- }
2943
- }
2944
- 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));
2945
2687
  }
2946
2688
  async function restoreAllDefaultSkills(configDir) {
2947
2689
  const ids = await getDefaultSkillIds();
@@ -2952,35 +2694,34 @@ async function restoreAllDefaultSkills(configDir) {
2952
2694
  return count;
2953
2695
  }
2954
2696
  function findSkillById(skillId, skills) {
2955
- return skills.find((s) => s.metadata.id === skillId);
2697
+ return findById(skillId, skills);
2956
2698
  }
2957
2699
  async function skillExists(configDir, skillId) {
2958
- const filePath = join4(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`);
2959
- return dirExists(filePath);
2700
+ return dirExists(join6(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`));
2960
2701
  }
2961
2702
  async function saveSkill(configDir, skill) {
2962
- const skillsDir = getSkillsDir(configDir);
2963
- if (!await dirExists(skillsDir)) {
2964
- await mkdir3(skillsDir, { recursive: true });
2965
- }
2966
- const filePath = join4(skillsDir, `${skill.metadata.id}${SKILL_EXTENSION}`);
2967
- const content = matter2.stringify(skill.prompt, skill.metadata);
2968
- await writeFile4(filePath, content, "utf-8");
2703
+ return saveItem(getSkillsDir(configDir), skill.metadata.id, SKILL_EXTENSION, skill);
2969
2704
  }
2970
2705
  async function deleteSkill(configDir, skillId) {
2971
- const filePath = join4(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`);
2972
- try {
2973
- await unlink2(filePath);
2706
+ const deleted = await deleteItem(getSkillsDir(configDir), skillId, SKILL_EXTENSION);
2707
+ if (deleted) {
2974
2708
  const { deleteSetting } = await import("./settings-NCLN4JDZ.js");
2975
2709
  deleteSetting(`${SKILL_SETTING_PREFIX}${skillId}`);
2976
- return true;
2977
- } catch {
2978
- return false;
2979
2710
  }
2711
+ return deleted;
2980
2712
  }
2981
2713
 
2982
2714
  // src/server/chat/agent-loop.ts
2983
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
+ }
2984
2725
  function getCurrentWindowMessageOptions(sessionId) {
2985
2726
  const contextWindowId = getCurrentContextWindowId(sessionId);
2986
2727
  return contextWindowId ? { contextWindowId } : void 0;
@@ -3211,13 +2952,7 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3211
2952
  }
3212
2953
  }
3213
2954
  if (result.aborted) {
3214
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3215
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3216
- stats: stats2,
3217
- partial: true,
3218
- promptContext: assembledRequest.promptContext
3219
- }));
3220
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2955
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3221
2956
  throw new Error("Aborted");
3222
2957
  }
3223
2958
  turnMetrics.addLLMCall(result.timing, result.usage.promptTokens, result.usage.completionTokens);
@@ -3252,25 +2987,13 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3252
2987
  }
3253
2988
  } catch (error) {
3254
2989
  if (error instanceof Error && error.message === "Aborted") {
3255
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3256
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3257
- stats: stats2,
3258
- partial: true,
3259
- promptContext: assembledRequest.promptContext
3260
- }));
3261
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2990
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3262
2991
  throw error;
3263
2992
  }
3264
2993
  throw error;
3265
2994
  }
3266
2995
  if (signal?.aborted) {
3267
- const stats2 = turnMetrics.buildStats(statsIdentity, mode);
3268
- eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3269
- stats: stats2,
3270
- partial: true,
3271
- promptContext: assembledRequest.promptContext
3272
- }));
3273
- eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "stopped", stats2));
2996
+ emitPartialDoneEvents(sessionId, assistantMsgId, statsIdentity, mode, turnMetrics, assembledRequest.promptContext, eventStore);
3274
2997
  throw new Error("Aborted");
3275
2998
  }
3276
2999
  const asapMessages = sessionManager.drainAsapMessages(sessionId);
@@ -3308,12 +3031,32 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
3308
3031
  };
3309
3032
  }
3310
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
+
3311
3047
  // src/server/sub-agents/manager.ts
3312
3048
  var RETURN_VALUE_INSTRUCTION = `
3313
3049
 
3314
3050
  ## RETURN VALUE
3315
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.`;
3316
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
+ }
3317
3060
  async function resolveAgentDef(agentId) {
3318
3061
  const allAgents = await loadAllAgentsDefault();
3319
3062
  const def = findAgentById(agentId, allAgents);
@@ -3483,19 +3226,11 @@ async function executeSubAgent(options) {
3483
3226
  if (consecutiveEmptyStops < nudgeConfig.maxConsecutiveNudges) {
3484
3227
  consecutiveEmptyStops += 1;
3485
3228
  const nudgeContent = nudgeConfig.buildNudgeContent(criteriaAwaiting);
3486
- const nudgeMsgId = crypto.randomUUID();
3487
3229
  eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3488
3230
  segments: result.segments,
3489
3231
  promptContext
3490
3232
  }));
3491
- eventStore.append(sessionId, createMessageStartEvent(nudgeMsgId, "user", nudgeContent, {
3492
- ...currentWindowMessageOptions ?? {},
3493
- isSystemGenerated: true,
3494
- messageKind: "correction",
3495
- subAgentId,
3496
- subAgentType
3497
- }));
3498
- eventStore.append(sessionId, { type: "message.done", data: { messageId: nudgeMsgId } });
3233
+ appendNudgeMessage(eventStore, sessionId, nudgeContent, currentWindowMessageOptions, { subAgentId, subAgentType });
3499
3234
  customMessages = [...customMessages, { role: "user", content: nudgeContent, source: "runtime" }];
3500
3235
  continue;
3501
3236
  }
@@ -3512,19 +3247,11 @@ async function executeSubAgent(options) {
3512
3247
  }
3513
3248
  if (!returnValueContent && !returnValueNudged) {
3514
3249
  returnValueNudged = true;
3515
- const nudgeMsgId = crypto.randomUUID();
3516
3250
  eventStore.append(sessionId, createMessageDoneEvent(assistantMsgId, {
3517
3251
  segments: result.segments,
3518
3252
  promptContext
3519
3253
  }));
3520
- eventStore.append(sessionId, createMessageStartEvent(nudgeMsgId, "user", RETURN_VALUE_NUDGE, {
3521
- ...currentWindowMessageOptions ?? {},
3522
- isSystemGenerated: true,
3523
- messageKind: "correction",
3524
- subAgentId,
3525
- subAgentType
3526
- }));
3527
- eventStore.append(sessionId, { type: "message.done", data: { messageId: nudgeMsgId } });
3254
+ appendNudgeMessage(eventStore, sessionId, RETURN_VALUE_NUDGE, currentWindowMessageOptions, { subAgentId, subAgentType });
3528
3255
  customMessages = [...customMessages, { role: "user", content: RETURN_VALUE_NUDGE, source: "runtime" }];
3529
3256
  continue;
3530
3257
  }
@@ -3580,21 +3307,9 @@ async function executeSubAgent(options) {
3580
3307
  subAgentId,
3581
3308
  resultLength: finalContent.length
3582
3309
  });
3583
- if (subAgentType === "verifier") {
3584
- session = sessionManager.requireSession(sessionId);
3585
- const remaining = nudgeConfig?.getCriteriaAwaiting(session.criteria) ?? [];
3586
- const failed = session.criteria.filter((c) => c.status.type === "failed").map((c) => ({
3587
- id: c.id,
3588
- reason: c.status.type === "failed" ? c.status.reason : "unknown"
3589
- }));
3590
- return {
3591
- content: returnValueContent ?? finalContent,
3592
- ...returnValueResult ? { result: returnValueResult } : {},
3593
- allPassed: failed.length === 0 && remaining.length === 0,
3594
- failed
3595
- };
3596
- }
3597
- 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);
3598
3313
  }
3599
3314
 
3600
3315
  // src/server/tools/sub-agent.ts
@@ -3662,7 +3377,7 @@ var callSubAgentTool = {
3662
3377
  };
3663
3378
  }
3664
3379
  try {
3665
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-PLVK22CF.js");
3380
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-VOWNOP6V.js");
3666
3381
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3667
3382
  const turnMetrics = new TurnMetrics();
3668
3383
  const result = await executeSubAgent({
@@ -3814,7 +3529,7 @@ function convertHTMLToMarkdown(html) {
3814
3529
  return turndown.turndown(html);
3815
3530
  }
3816
3531
  function stripHTMLTags(html) {
3817
- 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();
3818
3533
  return text;
3819
3534
  }
3820
3535
  var webFetchTool = createTool(
@@ -3916,9 +3631,9 @@ var webFetchTool = createTool(
3916
3631
  );
3917
3632
 
3918
3633
  // src/server/dev-server/manager.ts
3919
- import { spawn as spawn3 } from "child_process";
3920
- import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
3921
- 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";
3922
3637
  var MAX_LOG_LINES = 2e3;
3923
3638
  var MAX_LOG_BYTES = 1e5;
3924
3639
  var CONFIG_PATH = ".openfox/dev.json";
@@ -3974,8 +3689,8 @@ var DevServerManager = class {
3974
3689
  }
3975
3690
  async loadConfig(workdir) {
3976
3691
  try {
3977
- const configPath = join5(this.resolveWorkdir(workdir), CONFIG_PATH);
3978
- const raw = await readFile7(configPath, "utf-8");
3692
+ const configPath = join7(this.resolveWorkdir(workdir), CONFIG_PATH);
3693
+ const raw = await readFile8(configPath, "utf-8");
3979
3694
  const parsed = JSON.parse(raw);
3980
3695
  if (!parsed.command || !parsed.url) return null;
3981
3696
  return {
@@ -3989,9 +3704,9 @@ var DevServerManager = class {
3989
3704
  }
3990
3705
  async saveConfig(workdir, config) {
3991
3706
  const resolved = this.resolveWorkdir(workdir);
3992
- const dirPath = join5(resolved, ".openfox");
3707
+ const dirPath = join7(resolved, ".openfox");
3993
3708
  await mkdir4(dirPath, { recursive: true });
3994
- const configPath = join5(resolved, CONFIG_PATH);
3709
+ const configPath = join7(resolved, CONFIG_PATH);
3995
3710
  await writeFile5(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3996
3711
  }
3997
3712
  async start(workdir) {
@@ -4010,7 +3725,7 @@ var DevServerManager = class {
4010
3725
  instance.exited = false;
4011
3726
  const resolved = this.resolveWorkdir(workdir);
4012
3727
  const shell = getPlatformShell();
4013
- const proc = spawn3(shell.command, [...shell.args, config.command], {
3728
+ const proc = spawn4(shell.command, [...shell.args, config.command], {
4014
3729
  cwd: resolved,
4015
3730
  env: { ...process.env, FORCE_COLOR: "1" },
4016
3731
  stdio: ["ignore", "pipe", "pipe"],
@@ -4582,14 +4297,7 @@ function getCurrentWindowMessageOptions3(sessionId) {
4582
4297
  return contextWindowId ? { contextWindowId } : void 0;
4583
4298
  }
4584
4299
  function toRequestContextMessages2(messages) {
4585
- return messages.map((message) => ({
4586
- role: message.role,
4587
- content: message.content,
4588
- source: "history",
4589
- ...message.toolCalls ? { toolCalls: message.toolCalls } : {},
4590
- ...message.toolCallId ? { toolCallId: message.toolCallId } : {},
4591
- ...message.attachments ? { attachments: message.attachments } : {}
4592
- }));
4300
+ return minimalMessagesToRequestContextMessages(messages, "history");
4593
4301
  }
4594
4302
  async function maybeAutoCompactContext(options) {
4595
4303
  const config = getRuntimeConfig();
@@ -4744,6 +4452,9 @@ export {
4744
4452
  PathAccessDeniedError,
4745
4453
  providePathConfirmation,
4746
4454
  cancelPathConfirmationsForSession,
4455
+ checkAborted,
4456
+ spawnShellProcess,
4457
+ findModifiedDefaultFiles,
4747
4458
  ensureDefaultAgents,
4748
4459
  loadAllAgents,
4749
4460
  loadAllAgentsDefault,
@@ -4767,6 +4478,15 @@ export {
4767
4478
  createChatDoneEvent,
4768
4479
  getAllInstructions,
4769
4480
  assembleAgentRequest,
4481
+ getBundleDir,
4482
+ dirExists,
4483
+ ensureDir,
4484
+ loadItems,
4485
+ saveItem,
4486
+ deleteItem,
4487
+ findById,
4488
+ getDefaultIds,
4489
+ restoreDefault,
4770
4490
  ensureDefaultSkills,
4771
4491
  loadAllSkills,
4772
4492
  getEnabledSkillMetadata,
@@ -4795,4 +4515,4 @@ export {
4795
4515
  getToolRegistryForAgent,
4796
4516
  createToolRegistry
4797
4517
  };
4798
- //# sourceMappingURL=chunk-N25QUEPO.js.map
4518
+ //# sourceMappingURL=chunk-3WHZ47PY.js.map