openfox 0.1.0 → 0.2.1

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 (62) hide show
  1. package/dist/agent-defaults/builder.agent.md +38 -0
  2. package/dist/agent-defaults/code-reviewer.agent.md +21 -0
  3. package/dist/agent-defaults/debugger.agent.md +21 -0
  4. package/dist/agent-defaults/planner.agent.md +35 -0
  5. package/dist/agent-defaults/test-generator.agent.md +22 -0
  6. package/dist/agent-defaults/verifier.agent.md +41 -0
  7. package/dist/{chunk-FF6Z2MC3.js → chunk-5ZUU23XF.js} +18 -1
  8. package/dist/{chunk-IVHCPQO2.js → chunk-ARFK3RZW.js} +1 -1
  9. package/dist/{chunk-IKAWIOXM.js → chunk-FUUYYDVG.js} +387 -73
  10. package/dist/{chunk-YQ36AK5A.js → chunk-JFQEATRC.js} +623 -327
  11. package/dist/{chunk-43SWNGYW.js → chunk-L475ND2Z.js} +9 -9
  12. package/dist/{chunk-6MOGZMGX.js → chunk-L4XDA3V7.js} +2 -2
  13. package/dist/{chunk-EI46G5WO.js → chunk-MKAHOQBI.js} +10 -10
  14. package/dist/cli/dev.js +1 -1
  15. package/dist/cli/index.js +1 -1
  16. package/dist/{config-6GJ4SVFH.js → config-BMXSDOZB.js} +3 -3
  17. package/dist/{init-UVZDOHMX.js → init-PLRTRSDC.js} +6 -6
  18. package/dist/{protocol-BJsm88OW.d.ts → protocol-BFFz3WM0.d.ts} +14 -3
  19. package/dist/{provider-LCE6WZA6.js → provider-ETVY64A2.js} +5 -5
  20. package/dist/{serve-27DZK34K.js → serve-EOKIXTWM.js} +8 -8
  21. package/dist/server/index.d.ts +1 -1
  22. package/dist/server/index.js +5 -5
  23. package/dist/shared/index.d.ts +2 -2
  24. package/dist/shared/index.js +1 -1
  25. package/dist/{tools-R4YQAYUF.js → tools-QMIIDJTV.js} +7 -5
  26. package/dist/web/assets/index-CAD7Iv_d.js +122 -0
  27. package/dist/web/assets/{index-BSlSWZrM.css → index-DHmZmhnR.css} +1 -1
  28. package/dist/web/index.html +2 -2
  29. package/dist/workflow-defaults/default.workflow.json +45 -0
  30. package/package.json +34 -16
  31. package/dist/branch.api-WDHKZJS6.js.map +0 -1
  32. package/dist/chunk-26NRL3HX.js.map +0 -1
  33. package/dist/chunk-43SWNGYW.js.map +0 -1
  34. package/dist/chunk-4Z2OZ6NX.js.map +0 -1
  35. package/dist/chunk-6MOGZMGX.js.map +0 -1
  36. package/dist/chunk-AF7XS4OI.js.map +0 -1
  37. package/dist/chunk-EI46G5WO.js.map +0 -1
  38. package/dist/chunk-FF6Z2MC3.js.map +0 -1
  39. package/dist/chunk-IKAWIOXM.js.map +0 -1
  40. package/dist/chunk-IVHCPQO2.js.map +0 -1
  41. package/dist/chunk-MTX5Y3V3.js.map +0 -1
  42. package/dist/chunk-N2XYX4ZA.js.map +0 -1
  43. package/dist/chunk-QY7BMXWT.js.map +0 -1
  44. package/dist/chunk-U62ZECSQ.js.map +0 -1
  45. package/dist/chunk-YQ36AK5A.js.map +0 -1
  46. package/dist/cli/dev.js.map +0 -1
  47. package/dist/cli/index.js.map +0 -1
  48. package/dist/config-6GJ4SVFH.js.map +0 -1
  49. package/dist/init-UVZDOHMX.js.map +0 -1
  50. package/dist/paths-4BPXWB2W.js.map +0 -1
  51. package/dist/project-creator-CWPKRIKI.js.map +0 -1
  52. package/dist/provider-LCE6WZA6.js.map +0 -1
  53. package/dist/serve-27DZK34K.js.map +0 -1
  54. package/dist/server/index.js.map +0 -1
  55. package/dist/settings-D5O3TMLD.js.map +0 -1
  56. package/dist/shared/index.js.map +0 -1
  57. package/dist/tools-R4YQAYUF.js.map +0 -1
  58. package/dist/web/assets/index-BMNDCsbJ.js +0 -120
  59. /package/dist/command-defaults/{defaults/commit-push.command.md → commit-push.command.md} +0 -0
  60. /package/dist/command-defaults/{defaults/init.command.md → init.command.md} +0 -0
  61. /package/dist/command-defaults/{defaults/test-ui.command.md → test-ui.command.md} +0 -0
  62. /package/dist/skill-defaults/{defaults/playwright-cli.skill.md → playwright-cli.skill.md} +0 -0
@@ -9,7 +9,7 @@ import {
9
9
  import {
10
10
  createServerMessage,
11
11
  isClientMessage
12
- } from "./chunk-IVHCPQO2.js";
12
+ } from "./chunk-ARFK3RZW.js";
13
13
  import {
14
14
  streamWithSegments
15
15
  } from "./chunk-26NRL3HX.js";
@@ -237,7 +237,8 @@ function storedEventToServerMessage(event) {
237
237
  ...data.subAgentType ? { subAgentType: data.subAgentType } : {},
238
238
  ...data.isSystemGenerated ? { isSystemGenerated: data.isSystemGenerated } : {},
239
239
  ...data.messageKind ? { messageKind: data.messageKind } : {},
240
- ...data.isCompactionSummary ? { isCompactionSummary: data.isCompactionSummary } : {}
240
+ ...data.isCompactionSummary ? { isCompactionSummary: data.isCompactionSummary } : {},
241
+ ...data.attachments ? { attachments: data.attachments } : {}
241
242
  };
242
243
  return createChatMessageMessage(message);
243
244
  }
@@ -584,8 +585,8 @@ var PathAccessDeniedError = class extends Error {
584
585
  };
585
586
  var pendingConfirmations = /* @__PURE__ */ new Map();
586
587
  function registerPathConfirmation(callId, paths, sessionId) {
587
- return new Promise((resolve7, reject) => {
588
- pendingConfirmations.set(callId, { resolve: resolve7, reject, paths, sessionId });
588
+ return new Promise((resolve8, reject) => {
589
+ pendingConfirmations.set(callId, { resolve: resolve8, reject, paths, sessionId });
589
590
  });
590
591
  }
591
592
  function providePathConfirmation(callId, approved) {
@@ -829,21 +830,24 @@ var readFileTool = createTool(
829
830
  const mimeType = detectImageType(rawBuffer, args.path);
830
831
  if (mimeType) {
831
832
  const base64Data = rawBuffer.toString("base64");
833
+ const dataUrl = `data:${mimeType};base64,${base64Data}`;
832
834
  const contentHash2 = await computeFileHash(fullPath);
833
835
  if (contentHash2) {
834
836
  context.sessionManager.recordFileRead(context.sessionId, fullPath, contentHash2);
835
837
  }
836
- return {
837
- success: true,
838
- durationMs: Date.now() - startTime,
839
- truncated: false,
840
- metadata: {
841
- mimeType,
842
- size: rawBuffer.length,
843
- base64Data,
844
- path: fullPath
838
+ return helpers.success(
839
+ `[Image: ${args.path} (${mimeType}, ${rawBuffer.length} bytes)]`,
840
+ false,
841
+ {
842
+ metadata: {
843
+ mimeType,
844
+ size: rawBuffer.length,
845
+ base64Data,
846
+ dataUrl,
847
+ path: fullPath
848
+ }
845
849
  }
846
- };
850
+ );
847
851
  }
848
852
  const content = rawBuffer.toString("utf-8");
849
853
  const lines = content.split("\n");
@@ -1178,13 +1182,13 @@ async function terminateProcessTree(proc, options) {
1178
1182
  return;
1179
1183
  }
1180
1184
  if (process.platform === "win32") {
1181
- await new Promise((resolve7) => {
1185
+ await new Promise((resolve8) => {
1182
1186
  const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
1183
1187
  stdio: "ignore",
1184
1188
  windowsHide: true
1185
1189
  });
1186
- killer.once("exit", () => resolve7());
1187
- killer.once("error", () => resolve7());
1190
+ killer.once("exit", () => resolve8());
1191
+ killer.once("error", () => resolve8());
1188
1192
  });
1189
1193
  return;
1190
1194
  }
@@ -1301,7 +1305,7 @@ ${result.stderr}`;
1301
1305
  }
1302
1306
  );
1303
1307
  function executeCommand(command, cwd, timeout, signal, onProgress) {
1304
- return new Promise((resolve7, reject) => {
1308
+ return new Promise((resolve8, reject) => {
1305
1309
  if (signal?.aborted) {
1306
1310
  reject(new Error("Command aborted before execution"));
1307
1311
  return;
@@ -1351,7 +1355,7 @@ function executeCommand(command, cwd, timeout, signal, onProgress) {
1351
1355
  let output = stdout.trim();
1352
1356
  if (output) output += "\n\n";
1353
1357
  output += "[interrupted by user]";
1354
- resolve7({
1358
+ resolve8({
1355
1359
  stdout: output,
1356
1360
  stderr: stderr.trim(),
1357
1361
  exitCode: 130
@@ -1359,7 +1363,7 @@ function executeCommand(command, cwd, timeout, signal, onProgress) {
1359
1363
  });
1360
1364
  return;
1361
1365
  }
1362
- resolve7({
1366
+ resolve8({
1363
1367
  stdout: stdout.trim(),
1364
1368
  stderr: stderr.trim(),
1365
1369
  exitCode: code ?? 1
@@ -1661,7 +1665,7 @@ ${result.stderr}`;
1661
1665
  }
1662
1666
  };
1663
1667
  function executeGitCommand(command, cwd, signal) {
1664
- return new Promise((resolve7, reject) => {
1668
+ return new Promise((resolve8, reject) => {
1665
1669
  if (signal?.aborted) {
1666
1670
  reject(new Error("Git command aborted before execution"));
1667
1671
  return;
@@ -1693,14 +1697,14 @@ function executeGitCommand(command, cwd, signal) {
1693
1697
  exited = true;
1694
1698
  signal?.removeEventListener("abort", onAbort);
1695
1699
  if (aborted) {
1696
- resolve7({
1700
+ resolve8({
1697
1701
  stdout: stdout.trim() + "\n[interrupted by user]",
1698
1702
  stderr: stderr.trim(),
1699
1703
  exitCode: 130
1700
1704
  });
1701
1705
  return;
1702
1706
  }
1703
- resolve7({
1707
+ resolve8({
1704
1708
  stdout: stdout.trim(),
1705
1709
  stderr: stderr.trim(),
1706
1710
  exitCode: code ?? 1
@@ -1715,13 +1719,13 @@ function executeGitCommand(command, cwd, signal) {
1715
1719
 
1716
1720
  // src/server/utils/async.ts
1717
1721
  function createDeferred() {
1718
- let resolve7;
1722
+ let resolve8;
1719
1723
  let reject;
1720
1724
  const promise = new Promise((res, rej) => {
1721
- resolve7 = res;
1725
+ resolve8 = res;
1722
1726
  reject = rej;
1723
1727
  });
1724
- return { promise, resolve: resolve7, reject };
1728
+ return { promise, resolve: resolve8, reject };
1725
1729
  }
1726
1730
  var EventEmitter = class {
1727
1731
  listeners = /* @__PURE__ */ new Map();
@@ -2416,7 +2420,8 @@ function parseAgentFile(raw, filename) {
2416
2420
  description: String(meta["description"] ?? ""),
2417
2421
  subagent: meta["subagent"] === true,
2418
2422
  tools: Array.isArray(meta["tools"]) ? meta["tools"].map(String) : [],
2419
- ...typeof meta["color"] === "string" ? { color: meta["color"] } : {}
2423
+ ...typeof meta["color"] === "string" ? { color: meta["color"] } : {},
2424
+ ...Array.isArray(meta["results"]) ? { results: meta["results"].map(String) } : {}
2420
2425
  };
2421
2426
  return { metadata, prompt: content.trim() };
2422
2427
  }
@@ -2733,6 +2738,8 @@ var COMPACTION_PROMPT = `Summarize the conversation history concisely, preservin
2733
2738
  2. All errors encountered and how they were resolved
2734
2739
  3. Current progress on each task
2735
2740
  4. Any important decisions or learnings
2741
+ 5. Next steps or pending actions that should be continued after compaction
2742
+ 6. The user's current question, prompt, or active request
2736
2743
 
2737
2744
  Be thorough but concise. Output as a structured summary.
2738
2745
 
@@ -3239,8 +3246,8 @@ var EventStore = class {
3239
3246
  if (queued) {
3240
3247
  return { value: queued, done: false };
3241
3248
  }
3242
- return new Promise((resolve7) => {
3243
- resolveNext = resolve7;
3249
+ return new Promise((resolve8) => {
3250
+ resolveNext = resolve8;
3244
3251
  });
3245
3252
  },
3246
3253
  async return() {
@@ -3741,14 +3748,25 @@ function buildContextMessagesFromStoredEvents(events, windowId, options) {
3741
3748
  case "tool.result": {
3742
3749
  const data = event.data;
3743
3750
  if (messageMap.has(data.messageId)) {
3744
- messages.push({
3751
+ const imageMeta = data.result.metadata;
3752
+ const msg = {
3745
3753
  id: `tool-${data.toolCallId}`,
3746
3754
  role: "tool",
3747
3755
  content: data.result.success ? data.result.output ?? "Success" : data.result.output ? `${data.result.output}
3748
3756
 
3749
3757
  Error: ${data.result.error}` : `Error: ${data.result.error}`,
3750
3758
  toolCallId: data.toolCallId
3751
- });
3759
+ };
3760
+ if (imageMeta?.dataUrl && imageMeta?.mimeType?.startsWith("image/")) {
3761
+ msg.attachments = [{
3762
+ id: crypto.randomUUID(),
3763
+ filename: imageMeta.path ?? "image",
3764
+ mimeType: imageMeta.mimeType,
3765
+ size: imageMeta.size ?? 0,
3766
+ data: imageMeta.dataUrl
3767
+ }];
3768
+ }
3769
+ messages.push(msg);
3752
3770
  }
3753
3771
  break;
3754
3772
  }
@@ -4430,26 +4448,6 @@ function assembleAgentRequest(input) {
4430
4448
  }
4431
4449
 
4432
4450
  // src/server/context/auto-compaction.ts
4433
- var MIN_SUMMARY_LENGTH = 50;
4434
- var compactionCooldowns = /* @__PURE__ */ new Map();
4435
- var COOLDOWN_BASE_MS = 3e4;
4436
- var MAX_COOLDOWN_MS = 3e5;
4437
- function isInCooldown(sessionId) {
4438
- const cooldown = compactionCooldowns.get(sessionId);
4439
- if (!cooldown) return false;
4440
- const backoff = Math.min(COOLDOWN_BASE_MS * Math.pow(2, cooldown.attempts - 1), MAX_COOLDOWN_MS);
4441
- return Date.now() - cooldown.failedAt < backoff;
4442
- }
4443
- function recordCooldown(sessionId) {
4444
- const prev = compactionCooldowns.get(sessionId);
4445
- compactionCooldowns.set(sessionId, {
4446
- failedAt: Date.now(),
4447
- attempts: (prev?.attempts ?? 0) + 1
4448
- });
4449
- }
4450
- function clearCooldown(sessionId) {
4451
- compactionCooldowns.delete(sessionId);
4452
- }
4453
4451
  function getCurrentWindowMessageOptions(sessionId) {
4454
4452
  const contextWindowId = getCurrentContextWindowId(sessionId);
4455
4453
  return contextWindowId ? { contextWindowId } : void 0;
@@ -4470,20 +4468,12 @@ async function maybeAutoCompactContext(options) {
4470
4468
  if (!shouldCompact(contextState.currentTokens, contextState.maxTokens, config.context.compactionThreshold)) {
4471
4469
  return false;
4472
4470
  }
4473
- if (isInCooldown(options.sessionId)) {
4474
- logger.warn("Skipping auto-compaction (in cooldown after previous failure)", {
4475
- sessionId: options.sessionId,
4476
- currentTokens: contextState.currentTokens
4477
- });
4478
- return false;
4479
- }
4480
4471
  try {
4481
4472
  await performContextCompaction({
4482
4473
  ...options,
4483
4474
  tokenCountAtClose: contextState.currentTokens,
4484
4475
  trigger: "auto"
4485
4476
  });
4486
- clearCooldown(options.sessionId);
4487
4477
  return true;
4488
4478
  } catch (error) {
4489
4479
  if (error instanceof Error && error.message === "Aborted") {
@@ -4495,7 +4485,6 @@ async function maybeAutoCompactContext(options) {
4495
4485
  currentTokens: contextState.currentTokens,
4496
4486
  maxTokens: contextState.maxTokens
4497
4487
  });
4498
- recordCooldown(options.sessionId);
4499
4488
  const eventStore = getEventStore();
4500
4489
  eventStore.append(options.sessionId, {
4501
4490
  type: "chat.error",
@@ -4548,7 +4537,6 @@ async function performContextCompaction(options) {
4548
4537
  subAgentDefs,
4549
4538
  workdir: session.workdir,
4550
4539
  messages: requestMessages,
4551
- includeRuntimeReminder: false,
4552
4540
  injectedFiles,
4553
4541
  promptTools: [],
4554
4542
  requestTools: [],
@@ -4556,6 +4544,13 @@ async function performContextCompaction(options) {
4556
4544
  disableThinking: true,
4557
4545
  ...instructions ? { customInstructions: instructions } : {}
4558
4546
  });
4547
+ const compactionReminder = `<system-reminder>
4548
+ ${COMPACTION_PROMPT}
4549
+ </system-reminder>`;
4550
+ const llmMessages = [
4551
+ ...assembledRequest.messages,
4552
+ { role: "user", content: compactionReminder }
4553
+ ];
4559
4554
  const compactPromptMsgId = crypto.randomUUID();
4560
4555
  eventStore.append(sessionId, createMessageStartEvent(compactPromptMsgId, "user", COMPACTION_PROMPT, {
4561
4556
  ...getCurrentWindowMessageOptions(sessionId) ?? {},
@@ -4573,7 +4568,7 @@ async function performContextCompaction(options) {
4573
4568
  messageId: assistantMsgId,
4574
4569
  systemPrompt: assembledRequest.systemPrompt,
4575
4570
  llmClient,
4576
- messages: [...assembledRequest.messages, ...correctionMessages],
4571
+ messages: [...llmMessages, ...correctionMessages],
4577
4572
  tools: [],
4578
4573
  toolChoice: "none",
4579
4574
  disableThinking: true,
@@ -4611,10 +4606,8 @@ async function performContextCompaction(options) {
4611
4606
  logger.info("Using thinking content as compaction summary (text content was empty)", { sessionId });
4612
4607
  summary = result.thinkingContent.trim();
4613
4608
  }
4614
- if (!summary || summary.length < MIN_SUMMARY_LENGTH) {
4615
- throw new Error(
4616
- `Compaction produced insufficient summary (length=${summary.length}, completionTokens=${result.usage.completionTokens}). The model may not support the required context size.`
4617
- );
4609
+ if (!summary) {
4610
+ throw new Error("Compaction produced empty summary");
4618
4611
  }
4619
4612
  sessionManager.compactContext(sessionId, summary, tokenCountAtClose);
4620
4613
  logger.info(`${trigger === "auto" ? "Auto" : "Manual"} compaction complete`, {
@@ -4816,6 +4809,7 @@ async function executeToolBatch(assistantMsgId, toolCalls, ctx) {
4816
4809
  const toolMessages = [];
4817
4810
  let criteriaChanged = false;
4818
4811
  let returnValueContent;
4812
+ let returnValueResult;
4819
4813
  let session = ctx.sessionManager.requireSession(ctx.sessionId);
4820
4814
  for (const toolCall of toolCalls) {
4821
4815
  if (ctx.signal?.aborted) {
@@ -4870,6 +4864,7 @@ async function executeToolBatch(assistantMsgId, toolCalls, ctx) {
4870
4864
  ctx.onToolExecuted?.(toolCall, toolResult);
4871
4865
  if (toolCall.name === "return_value" && !toolCall.parseError) {
4872
4866
  returnValueContent = toolCall.arguments["content"];
4867
+ returnValueResult = toolCall.arguments["result"];
4873
4868
  }
4874
4869
  toolMessages.push({
4875
4870
  role: "tool",
@@ -4886,7 +4881,7 @@ Error: ${toolResult.error}` : `Error: ${toolResult.error}`,
4886
4881
  criteriaChanged = true;
4887
4882
  }
4888
4883
  }
4889
- return { toolMessages, criteriaChanged, returnValueContent };
4884
+ return { toolMessages, criteriaChanged, returnValueContent, returnValueResult };
4890
4885
  }
4891
4886
  var MAX_FORMAT_RETRIES2 = 10;
4892
4887
  var FORMAT_CORRECTION_PROMPT2 = `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.`;
@@ -4904,6 +4899,8 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
4904
4899
  const { mode, sessionManager, sessionId, llmClient, signal, onMessage, statsIdentity } = config;
4905
4900
  const eventStore = getEventStore();
4906
4901
  let formatRetryCount = 0;
4902
+ let returnValueContent;
4903
+ let returnValueResult;
4907
4904
  for (; ; ) {
4908
4905
  await maybeAutoCompactContext({
4909
4906
  sessionManager,
@@ -4912,11 +4909,13 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
4912
4909
  statsIdentity,
4913
4910
  ...signal ? { signal } : {}
4914
4911
  });
4912
+ if (signal?.aborted) throw new Error("Aborted");
4915
4913
  const session = sessionManager.requireSession(sessionId);
4916
4914
  if (formatRetryCount === 0) {
4917
4915
  config.injectKickoff?.();
4918
4916
  }
4919
4917
  const { content: instructionContent, files } = await getAllInstructions(session.workdir, session.projectId);
4918
+ if (signal?.aborted) throw new Error("Aborted");
4920
4919
  const injectedFiles = files.map((f) => ({
4921
4920
  path: f.path,
4922
4921
  content: f.content ?? "",
@@ -4937,6 +4936,7 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
4937
4936
  }
4938
4937
  const configDir = getGlobalConfigDir(getRuntimeConfig().mode ?? "production");
4939
4938
  const skills = await getEnabledSkillMetadata(configDir);
4939
+ if (signal?.aborted) throw new Error("Aborted");
4940
4940
  const assembledRequest = config.assembleRequest({
4941
4941
  workdir: session.workdir,
4942
4942
  messages: requestMessages,
@@ -4991,7 +4991,7 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
4991
4991
  promptContext: assembledRequest.promptContext
4992
4992
  }));
4993
4993
  try {
4994
- await executeToolBatch(assistantMsgId, result.toolCalls, {
4994
+ const batchResult = await executeToolBatch(assistantMsgId, result.toolCalls, {
4995
4995
  toolRegistry,
4996
4996
  sessionManager,
4997
4997
  sessionId,
@@ -5003,6 +5003,12 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
5003
5003
  statsIdentity,
5004
5004
  onToolExecuted: config.onToolExecuted
5005
5005
  });
5006
+ if (batchResult.returnValueContent) {
5007
+ returnValueContent = batchResult.returnValueContent;
5008
+ }
5009
+ if (batchResult.returnValueResult) {
5010
+ returnValueResult = batchResult.returnValueResult;
5011
+ }
5006
5012
  } catch (error) {
5007
5013
  if (error instanceof Error && error.message === "Aborted") {
5008
5014
  const stats2 = turnMetrics.buildStats(statsIdentity, mode);
@@ -5050,13 +5056,17 @@ async function runTopLevelAgentLoop(config, turnMetrics) {
5050
5056
  eventStore.append(sessionId, createChatDoneEvent(assistantMsgId, "complete", stats));
5051
5057
  break;
5052
5058
  }
5059
+ return {
5060
+ ...returnValueContent ? { returnValueContent } : {},
5061
+ ...returnValueResult ? { returnValueResult } : {}
5062
+ };
5053
5063
  }
5054
5064
 
5055
5065
  // src/server/sub-agents/manager.ts
5056
5066
  var RETURN_VALUE_INSTRUCTION = `
5057
5067
 
5058
5068
  ## RETURN VALUE
5059
- As the very last thing you do, call \`return_value\` 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.`;
5069
+ 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.`;
5060
5070
  var RETURN_VALUE_NUDGE = "You must call return_value with a summary of your findings before finishing. Call return_value now.";
5061
5071
  async function resolveAgentDef(agentId) {
5062
5072
  const allAgents = await loadAllAgentsDefault();
@@ -5133,6 +5143,7 @@ async function executeSubAgent(options) {
5133
5143
  let consecutiveEmptyStops = 0;
5134
5144
  let finalContent = "";
5135
5145
  let returnValueContent = null;
5146
+ let returnValueResult = void 0;
5136
5147
  let returnValueNudged = false;
5137
5148
  for (; ; ) {
5138
5149
  if (signal?.aborted) {
@@ -5266,6 +5277,9 @@ async function executeSubAgent(options) {
5266
5277
  if (batchResult.returnValueContent) {
5267
5278
  returnValueContent = batchResult.returnValueContent;
5268
5279
  }
5280
+ if (batchResult.returnValueResult) {
5281
+ returnValueResult = batchResult.returnValueResult;
5282
+ }
5269
5283
  customMessages = [...customMessages, ...batchResult.toolMessages];
5270
5284
  session = sessionManager.requireSession(sessionId);
5271
5285
  if (nudgeConfig) {
@@ -5286,11 +5300,12 @@ async function executeSubAgent(options) {
5286
5300
  }));
5287
5301
  return {
5288
5302
  content: returnValueContent ?? finalContent,
5303
+ ...returnValueResult ? { result: returnValueResult } : {},
5289
5304
  allPassed: failed.length === 0 && remaining.length === 0,
5290
5305
  failed
5291
5306
  };
5292
5307
  }
5293
- return { content: returnValueContent ?? finalContent };
5308
+ return { content: returnValueContent ?? finalContent, ...returnValueResult ? { result: returnValueResult } : { result: "success" } };
5294
5309
  }
5295
5310
 
5296
5311
  // src/server/tools/sub-agent.ts
@@ -5358,7 +5373,7 @@ var callSubAgentTool = {
5358
5373
  };
5359
5374
  }
5360
5375
  try {
5361
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-R4YQAYUF.js");
5376
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-QMIIDJTV.js");
5362
5377
  const toolRegistry = getToolRegistryForAgent2(agentDef);
5363
5378
  const turnMetrics = new TurnMetrics();
5364
5379
  const result = await executeSubAgent({
@@ -5450,6 +5465,10 @@ var returnValueTool = {
5450
5465
  content: {
5451
5466
  type: "string",
5452
5467
  description: "The return value content \u2014 a structured summary of findings, results, or recommendations"
5468
+ },
5469
+ result: {
5470
+ type: "string",
5471
+ description: 'Result classification (e.g. "passed", "failed", "error"). Used for workflow routing.'
5453
5472
  }
5454
5473
  },
5455
5474
  required: ["content"]
@@ -5604,6 +5623,297 @@ var webFetchTool = createTool(
5604
5623
  }
5605
5624
  );
5606
5625
 
5626
+ // src/server/dev-server/manager.ts
5627
+ import { spawn as spawn4 } from "child_process";
5628
+ import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
5629
+ import { resolve as resolve7, join as join5 } from "path";
5630
+ var MAX_LOG_LINES = 2e3;
5631
+ var MAX_LOG_BYTES = 1e5;
5632
+ var CONFIG_PATH = ".openfox/dev.json";
5633
+ var ERROR_PATTERNS = [
5634
+ /EADDRINUSE/,
5635
+ /EACCES/,
5636
+ /ENOENT/,
5637
+ /Error: listen/,
5638
+ /Unhandled 'error' event/,
5639
+ /throw er;.*Unhandled/s,
5640
+ /Cannot find module/,
5641
+ /SyntaxError:/,
5642
+ /MODULE_NOT_FOUND/
5643
+ ];
5644
+ function createInstance() {
5645
+ return {
5646
+ process: null,
5647
+ state: "off",
5648
+ config: null,
5649
+ logs: [],
5650
+ totalLogBytes: 0,
5651
+ errorMessage: void 0,
5652
+ exited: true
5653
+ };
5654
+ }
5655
+ var DevServerManager = class {
5656
+ instances = /* @__PURE__ */ new Map();
5657
+ outputListeners = /* @__PURE__ */ new Set();
5658
+ stateListeners = /* @__PURE__ */ new Set();
5659
+ resolveWorkdir(workdir) {
5660
+ return resolve7(workdir);
5661
+ }
5662
+ getInstance(workdir) {
5663
+ const key = this.resolveWorkdir(workdir);
5664
+ let instance = this.instances.get(key);
5665
+ if (!instance) {
5666
+ instance = createInstance();
5667
+ this.instances.set(key, instance);
5668
+ }
5669
+ return instance;
5670
+ }
5671
+ emitOutput(workdir, chunk) {
5672
+ const resolved = this.resolveWorkdir(workdir);
5673
+ for (const listener of this.outputListeners) {
5674
+ listener(resolved, chunk);
5675
+ }
5676
+ }
5677
+ emitStateChange(workdir, state, errorMessage) {
5678
+ const resolved = this.resolveWorkdir(workdir);
5679
+ for (const listener of this.stateListeners) {
5680
+ listener(resolved, state, errorMessage);
5681
+ }
5682
+ }
5683
+ async loadConfig(workdir) {
5684
+ try {
5685
+ const configPath = join5(this.resolveWorkdir(workdir), CONFIG_PATH);
5686
+ const raw = await readFile8(configPath, "utf-8");
5687
+ const parsed = JSON.parse(raw);
5688
+ if (!parsed.command || !parsed.url) return null;
5689
+ return {
5690
+ command: parsed.command,
5691
+ url: parsed.url,
5692
+ hotReload: parsed.hotReload ?? false
5693
+ };
5694
+ } catch {
5695
+ return null;
5696
+ }
5697
+ }
5698
+ async saveConfig(workdir, config) {
5699
+ const resolved = this.resolveWorkdir(workdir);
5700
+ const dirPath = join5(resolved, ".openfox");
5701
+ await mkdir4(dirPath, { recursive: true });
5702
+ const configPath = join5(resolved, CONFIG_PATH);
5703
+ await writeFile5(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
5704
+ }
5705
+ async start(workdir) {
5706
+ const instance = this.getInstance(workdir);
5707
+ if (instance.state === "running" && instance.process && !instance.exited) {
5708
+ await this.stop(workdir);
5709
+ }
5710
+ const config = await this.loadConfig(workdir);
5711
+ if (!config) {
5712
+ return this.getStatus(workdir);
5713
+ }
5714
+ instance.config = config;
5715
+ instance.logs = [];
5716
+ instance.totalLogBytes = 0;
5717
+ instance.errorMessage = void 0;
5718
+ instance.exited = false;
5719
+ const resolved = this.resolveWorkdir(workdir);
5720
+ const proc = spawn4("sh", ["-c", config.command], {
5721
+ cwd: resolved,
5722
+ env: { ...process.env, FORCE_COLOR: "1" },
5723
+ stdio: ["ignore", "pipe", "pipe"],
5724
+ detached: true
5725
+ });
5726
+ instance.process = proc;
5727
+ const appendLog = (line) => {
5728
+ instance.logs.push(line);
5729
+ instance.totalLogBytes += line.length;
5730
+ while (instance.logs.length > MAX_LOG_LINES || instance.totalLogBytes > MAX_LOG_BYTES) {
5731
+ const removed = instance.logs.shift();
5732
+ if (removed) instance.totalLogBytes -= removed.length;
5733
+ }
5734
+ };
5735
+ proc.stdout?.on("data", (data) => {
5736
+ const content = data.toString();
5737
+ appendLog(content);
5738
+ this.emitOutput(workdir, { stream: "stdout", content });
5739
+ });
5740
+ proc.stderr?.on("data", (data) => {
5741
+ const content = data.toString();
5742
+ appendLog(content);
5743
+ this.emitOutput(workdir, { stream: "stderr", content });
5744
+ if (instance.state === "running" && ERROR_PATTERNS.some((p) => p.test(content))) {
5745
+ instance.state = "warning";
5746
+ instance.errorMessage = content.trim().slice(0, 500);
5747
+ this.emitStateChange(workdir, "warning", instance.errorMessage);
5748
+ }
5749
+ });
5750
+ proc.on("close", (code) => {
5751
+ instance.exited = true;
5752
+ instance.process = null;
5753
+ if (code !== 0 && code !== null) {
5754
+ const recentLogs = instance.logs.slice(-10).join("");
5755
+ const errorMessage = `Process exited with code ${code}
5756
+ ${recentLogs}`.trim();
5757
+ instance.state = "error";
5758
+ instance.errorMessage = errorMessage;
5759
+ this.emitStateChange(workdir, "error", errorMessage);
5760
+ } else {
5761
+ instance.state = "off";
5762
+ instance.errorMessage = void 0;
5763
+ this.emitStateChange(workdir, "off", void 0);
5764
+ }
5765
+ });
5766
+ proc.on("error", (err) => {
5767
+ instance.exited = true;
5768
+ instance.process = null;
5769
+ instance.state = "error";
5770
+ instance.errorMessage = err.message;
5771
+ this.emitStateChange(workdir, "error", err.message);
5772
+ });
5773
+ instance.state = "running";
5774
+ instance.errorMessage = void 0;
5775
+ this.emitStateChange(workdir, "running", void 0);
5776
+ logger.info("Dev server started", { workdir, command: config.command });
5777
+ return this.getStatus(workdir);
5778
+ }
5779
+ async stop(workdir) {
5780
+ const instance = this.getInstance(workdir);
5781
+ if (instance.process && !instance.exited) {
5782
+ await terminateProcessTree(instance.process, { exited: () => instance.exited });
5783
+ instance.process = null;
5784
+ instance.exited = true;
5785
+ instance.state = "off";
5786
+ instance.errorMessage = void 0;
5787
+ this.emitStateChange(workdir, "off", void 0);
5788
+ logger.info("Dev server stopped", { workdir });
5789
+ }
5790
+ return this.getStatus(workdir);
5791
+ }
5792
+ async restart(workdir) {
5793
+ await this.stop(workdir);
5794
+ return this.start(workdir);
5795
+ }
5796
+ getStatus(workdir) {
5797
+ const instance = this.getInstance(workdir);
5798
+ return {
5799
+ state: instance.state,
5800
+ url: instance.config?.url ?? null,
5801
+ hotReload: instance.config?.hotReload ?? false,
5802
+ config: instance.config,
5803
+ errorMessage: instance.errorMessage
5804
+ };
5805
+ }
5806
+ getLogs(workdir) {
5807
+ const instance = this.getInstance(workdir);
5808
+ return [...instance.logs];
5809
+ }
5810
+ /** Register a global listener for output from any dev server */
5811
+ onOutput(callback) {
5812
+ this.outputListeners.add(callback);
5813
+ return () => {
5814
+ this.outputListeners.delete(callback);
5815
+ };
5816
+ }
5817
+ /** Register a global listener for state changes from any dev server */
5818
+ onStateChange(callback) {
5819
+ this.stateListeners.add(callback);
5820
+ return () => {
5821
+ this.stateListeners.delete(callback);
5822
+ };
5823
+ }
5824
+ async stopAll() {
5825
+ const stops = Array.from(this.instances.keys()).map((workdir) => this.stop(workdir));
5826
+ await Promise.allSettled(stops);
5827
+ this.instances.clear();
5828
+ }
5829
+ };
5830
+ var devServerManager = new DevServerManager();
5831
+
5832
+ // src/server/tools/dev-server.ts
5833
+ var devServerTool = createTool(
5834
+ "dev_server",
5835
+ {
5836
+ type: "function",
5837
+ function: {
5838
+ name: "dev_server",
5839
+ description: "Control the project dev server. Start, stop, restart, or check status. The dev server command and URL are configured in .openfox/dev.json.",
5840
+ parameters: {
5841
+ type: "object",
5842
+ properties: {
5843
+ action: {
5844
+ type: "string",
5845
+ enum: ["start", "stop", "restart", "status"],
5846
+ description: "The action to perform on the dev server"
5847
+ }
5848
+ },
5849
+ required: ["action"]
5850
+ }
5851
+ }
5852
+ },
5853
+ async (args, context, helpers) => {
5854
+ const workdir = context.workdir;
5855
+ let status;
5856
+ switch (args.action) {
5857
+ case "start":
5858
+ status = await devServerManager.start(workdir);
5859
+ break;
5860
+ case "stop":
5861
+ status = await devServerManager.stop(workdir);
5862
+ break;
5863
+ case "restart":
5864
+ status = await devServerManager.restart(workdir);
5865
+ break;
5866
+ case "status":
5867
+ status = devServerManager.getStatus(workdir);
5868
+ if (!status.config) {
5869
+ const config = await devServerManager.loadConfig(workdir);
5870
+ if (config) {
5871
+ status.config = config;
5872
+ status.url = config.url;
5873
+ status.hotReload = config.hotReload;
5874
+ }
5875
+ }
5876
+ break;
5877
+ }
5878
+ if (!status.config) {
5879
+ return helpers.error(
5880
+ 'No .openfox/dev.json config found. Create one with:\n\n{\n "command": "npm run dev",\n "url": "http://localhost:3000",\n "hotReload": true\n}'
5881
+ );
5882
+ }
5883
+ return helpers.success(JSON.stringify({
5884
+ state: status.state,
5885
+ url: status.url,
5886
+ hotReload: status.hotReload,
5887
+ ...status.errorMessage ? { error: status.errorMessage } : {}
5888
+ }, null, 2));
5889
+ }
5890
+ );
5891
+
5892
+ // src/server/tools/step-done.ts
5893
+ var stepDoneTool = {
5894
+ name: "step_done",
5895
+ definition: {
5896
+ type: "function",
5897
+ function: {
5898
+ name: "step_done",
5899
+ description: "Signal that you have completed the current workflow step. Call this exactly once when you have finished all work for this step. After calling this, no further tool calls should be made.",
5900
+ parameters: {
5901
+ type: "object",
5902
+ properties: {},
5903
+ required: []
5904
+ }
5905
+ }
5906
+ },
5907
+ async execute(_args, _context) {
5908
+ return {
5909
+ success: true,
5910
+ output: "Step completion signal recorded.",
5911
+ durationMs: 0,
5912
+ truncated: false
5913
+ };
5914
+ }
5915
+ };
5916
+
5607
5917
  // src/server/tools/index.ts
5608
5918
  function createRegistryFromTools(tools) {
5609
5919
  const toolMap = /* @__PURE__ */ new Map();
@@ -5671,7 +5981,9 @@ var allToolsByName = new Map([
5671
5981
  callSubAgentTool,
5672
5982
  loadSkillTool,
5673
5983
  returnValueTool,
5674
- webFetchTool
5984
+ webFetchTool,
5985
+ devServerTool,
5986
+ stepDoneTool
5675
5987
  ].map((t) => [t.name, t])
5676
5988
  ]);
5677
5989
  function getToolRegistryForSubAgent(toolNames) {
@@ -5810,8 +6122,10 @@ export {
5810
6122
  deleteSkill,
5811
6123
  runTopLevelAgentLoop,
5812
6124
  executeSubAgent,
6125
+ devServerManager,
6126
+ stepDoneTool,
5813
6127
  getToolRegistryForSubAgent,
5814
6128
  getToolRegistryForAgent,
5815
6129
  createToolRegistry
5816
6130
  };
5817
- //# sourceMappingURL=chunk-IKAWIOXM.js.map
6131
+ //# sourceMappingURL=chunk-FUUYYDVG.js.map