openfox 1.5.2 → 1.6.0

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 (34) hide show
  1. package/dist/agent-defaults/builder.agent.md +1 -0
  2. package/dist/agent-defaults/planner.agent.md +1 -0
  3. package/dist/{auto-compaction-MNK2FZ6E.js → auto-compaction-BRT6SDEP.js} +7 -5
  4. package/dist/{chat-handler-Y7KR2RLL.js → chat-handler-55LA4LI2.js} +11 -9
  5. package/dist/chunk-574HZVLE.js +149 -0
  6. package/dist/{chunk-NW7PIZH3.js → chunk-CIXMZ73F.js} +82 -3
  7. package/dist/{chunk-S4QKTRGJ.js → chunk-EBOKO2PW.js} +2 -2
  8. package/dist/{chunk-GXVA5W7Q.js → chunk-GBBTJ6ND.js} +183 -15
  9. package/dist/chunk-NBU6KIOD.js +184 -0
  10. package/dist/{chunk-F7PUG5WA.js → chunk-S73ATBSR.js} +4 -4
  11. package/dist/{chunk-6PV24MJB.js → chunk-TKWJ665A.js} +2 -2
  12. package/dist/{chunk-UBWZDTZ4.js → chunk-WZJLCOV3.js} +89 -48
  13. package/dist/{chunk-TPT6HP4H.js → chunk-XQQ2BRJA.js} +1 -1
  14. package/dist/cli/dev.js +1 -1
  15. package/dist/cli/index.js +1 -1
  16. package/dist/{events-B7S4U4DB.js → events-OZKDX6XE.js} +2 -2
  17. package/dist/manager-HM2J5VMW.js +20 -0
  18. package/dist/{orchestrator-QGFC5QB6.js → orchestrator-IEYZSNVH.js} +8 -6
  19. package/dist/package.json +1 -1
  20. package/dist/{processor-BFTXIEDV.js → processor-I4BGYK3T.js} +5 -5
  21. package/dist/{protocol-WQENDK72.js → protocol-ODKD7QJO.js} +3 -3
  22. package/dist/{protocol-CLWtTnMd.d.ts → protocol-vX_YbJXE.d.ts} +40 -2
  23. package/dist/{serve-JJLKBQXS.js → serve-SDBFDRNJ.js} +10 -8
  24. package/dist/server/index.d.ts +1 -1
  25. package/dist/server/index.js +8 -6
  26. package/dist/shared/index.d.ts +2 -2
  27. package/dist/shared/index.js +1 -1
  28. package/dist/store-2IF3ZSLD.js +33 -0
  29. package/dist/{tools-MY4TPRP4.js → tools-GQ3PJL6N.js} +7 -5
  30. package/dist/web/assets/{index-BnJ4NNAw.css → index-Dc2TJUUt.css} +1 -1
  31. package/dist/web/assets/{index-xs6xUYKs.js → index-JG-uVLoP.js} +70 -62
  32. package/dist/web/index.html +2 -2
  33. package/dist/web/sw.js +1 -1
  34. package/package.json +1 -1
@@ -16,6 +16,7 @@ allowedTools:
16
16
  - call_sub_agent
17
17
  - load_skill
18
18
  - dev_server
19
+ - background_process
19
20
  - step_done
20
21
  ---
21
22
 
@@ -11,6 +11,7 @@ allowedTools:
11
11
  - criterion:add,get,update,remove
12
12
  - call_sub_agent
13
13
  - load_skill
14
+ - background_process
14
15
  ---
15
16
 
16
17
  # Plan Mode
@@ -2,16 +2,18 @@ import {
2
2
  maybeAutoCompactContext,
3
3
  performManualContextCompaction,
4
4
  resolveCompactionStatsIdentity
5
- } from "./chunk-GXVA5W7Q.js";
5
+ } from "./chunk-GBBTJ6ND.js";
6
+ import "./chunk-NBU6KIOD.js";
7
+ import "./chunk-574HZVLE.js";
6
8
  import "./chunk-XFXOSPYH.js";
7
- import "./chunk-NW7PIZH3.js";
9
+ import "./chunk-CIXMZ73F.js";
8
10
  import "./chunk-WQ4W5H6A.js";
9
- import "./chunk-S4QKTRGJ.js";
11
+ import "./chunk-EBOKO2PW.js";
10
12
  import "./chunk-22CTURMH.js";
11
13
  import "./chunk-7IOZFJBW.js";
12
14
  import "./chunk-XKFPU2FA.js";
13
15
  import "./chunk-3EHGGBWE.js";
14
- import "./chunk-TPT6HP4H.js";
16
+ import "./chunk-XQQ2BRJA.js";
15
17
  import "./chunk-R4HADRYO.js";
16
18
  import "./chunk-TVQOONDR.js";
17
19
  import "./chunk-PNBH3RAX.js";
@@ -20,4 +22,4 @@ export {
20
22
  performManualContextCompaction,
21
23
  resolveCompactionStatsIdentity
22
24
  };
23
- //# sourceMappingURL=auto-compaction-MNK2FZ6E.js.map
25
+ //# sourceMappingURL=auto-compaction-BRT6SDEP.js.map
@@ -1,16 +1,18 @@
1
- import {
2
- runChatTurn
3
- } from "./chunk-F7PUG5WA.js";
4
- import "./chunk-GXVA5W7Q.js";
5
1
  import {
6
2
  generateSessionName,
7
3
  needsNameGeneration
8
4
  } from "./chunk-QDEKU5RL.js";
5
+ import {
6
+ runChatTurn
7
+ } from "./chunk-S73ATBSR.js";
8
+ import "./chunk-GBBTJ6ND.js";
9
+ import "./chunk-NBU6KIOD.js";
10
+ import "./chunk-574HZVLE.js";
9
11
  import "./chunk-XFXOSPYH.js";
10
12
  import {
11
13
  getEventStore,
12
14
  updateSessionMetadata
13
- } from "./chunk-NW7PIZH3.js";
15
+ } from "./chunk-CIXMZ73F.js";
14
16
  import {
15
17
  buildMessagesFromStoredEvents,
16
18
  foldPendingConfirmations
@@ -21,12 +23,12 @@ import {
21
23
  createPhaseChangedMessage,
22
24
  createSessionRunningMessage,
23
25
  createSessionStateMessage
24
- } from "./chunk-S4QKTRGJ.js";
26
+ } from "./chunk-EBOKO2PW.js";
25
27
  import "./chunk-22CTURMH.js";
26
28
  import "./chunk-7IOZFJBW.js";
27
29
  import "./chunk-XKFPU2FA.js";
28
30
  import "./chunk-3EHGGBWE.js";
29
- import "./chunk-TPT6HP4H.js";
31
+ import "./chunk-XQQ2BRJA.js";
30
32
  import "./chunk-R4HADRYO.js";
31
33
  import "./chunk-TVQOONDR.js";
32
34
  import {
@@ -59,7 +61,7 @@ async function startChatSession(sessionId, content, deps, options) {
59
61
  sessionManager.setRunning(sessionId, true);
60
62
  broadcastForSession(sessionId, createSessionRunningMessage(true));
61
63
  try {
62
- const { maybeAutoCompactContext } = await import("./auto-compaction-MNK2FZ6E.js");
64
+ const { maybeAutoCompactContext } = await import("./auto-compaction-BRT6SDEP.js");
63
65
  await maybeAutoCompactContext({
64
66
  sessionManager,
65
67
  sessionId,
@@ -198,4 +200,4 @@ export {
198
200
  startChatSession,
199
201
  stopSessionExecution
200
202
  };
201
- //# sourceMappingURL=chat-handler-Y7KR2RLL.js.map
203
+ //# sourceMappingURL=chat-handler-55LA4LI2.js.map
@@ -0,0 +1,149 @@
1
+ // src/server/tools/background-process/store.ts
2
+ var MAX_PER_SESSION = 5;
3
+ var processesBySession = /* @__PURE__ */ new Map();
4
+ var logsByProcess = /* @__PURE__ */ new Map();
5
+ function getMaxPerSession() {
6
+ return MAX_PER_SESSION;
7
+ }
8
+ function getProcess(processId, sessionId) {
9
+ return processesBySession.get(sessionId)?.get(processId);
10
+ }
11
+ function getSessionProcesses(sessionId) {
12
+ const sessionProcesses = processesBySession.get(sessionId);
13
+ if (!sessionProcesses) return [];
14
+ return Array.from(sessionProcesses.values());
15
+ }
16
+ function getSessionProcessCount(sessionId) {
17
+ return getSessionProcesses(sessionId).filter((p) => p.status !== "exited").length;
18
+ }
19
+ function createProcess(sessionId, name, command, cwd) {
20
+ const count = getSessionProcessCount(sessionId);
21
+ if (count >= MAX_PER_SESSION) {
22
+ return null;
23
+ }
24
+ const process2 = {
25
+ id: crypto.randomUUID(),
26
+ sessionId,
27
+ name,
28
+ command,
29
+ cwd,
30
+ pid: null,
31
+ status: "pending",
32
+ exitCode: null,
33
+ createdAt: Date.now(),
34
+ startedAt: null,
35
+ endedAt: null
36
+ };
37
+ if (!processesBySession.has(sessionId)) {
38
+ processesBySession.set(sessionId, /* @__PURE__ */ new Map());
39
+ }
40
+ processesBySession.get(sessionId).set(process2.id, process2);
41
+ logsByProcess.set(process2.id, []);
42
+ return process2;
43
+ }
44
+ function startProcess(processId, sessionId, pid) {
45
+ const process2 = getProcess(processId, sessionId);
46
+ if (!process2) return void 0;
47
+ process2.pid = pid;
48
+ process2.status = "running";
49
+ process2.startedAt = Date.now();
50
+ return process2;
51
+ }
52
+ function updateStatus(processId, sessionId, status, exitCode) {
53
+ const process2 = getProcess(processId, sessionId);
54
+ if (!process2) return void 0;
55
+ process2.status = status;
56
+ if (exitCode !== void 0) {
57
+ process2.exitCode = exitCode;
58
+ }
59
+ if (status === "exited" || status === "stopping") {
60
+ process2.endedAt = Date.now();
61
+ }
62
+ return process2;
63
+ }
64
+ function removeProcess(processId, sessionId) {
65
+ const sessionProcesses = processesBySession.get(sessionId);
66
+ if (!sessionProcesses) return false;
67
+ const deleted = sessionProcesses.delete(processId);
68
+ logsByProcess.delete(processId);
69
+ return deleted;
70
+ }
71
+ function appendLog(processId, content, stream = "stdout") {
72
+ const logs = logsByProcess.get(processId);
73
+ if (!logs) return;
74
+ const offset = logs.length;
75
+ logs.push({
76
+ offset,
77
+ content,
78
+ timestamp: Date.now(),
79
+ stream
80
+ });
81
+ }
82
+ function getLogs(processId, offset = 0, limit) {
83
+ const logs = logsByProcess.get(processId) ?? [];
84
+ const start = Math.min(offset, logs.length);
85
+ const end = limit !== void 0 ? Math.min(start + limit, logs.length) : logs.length;
86
+ return logs.slice(start, end);
87
+ }
88
+ function getLogsPaginated(processId, since = 0, maxLines = 500) {
89
+ const logs = logsByProcess.get(processId) ?? [];
90
+ const totalLines = logs.length;
91
+ const filteredLogs = logs.filter((_, index) => index >= since);
92
+ const slicedLogs = filteredLogs.slice(0, maxLines);
93
+ const lastLine = slicedLogs[slicedLogs.length - 1];
94
+ const nextOffset = lastLine ? lastLine.offset + 1 : totalLines;
95
+ const hasMore = slicedLogs.length < filteredLogs.length;
96
+ return {
97
+ lines: slicedLogs,
98
+ totalLines,
99
+ nextOffset,
100
+ hasMore
101
+ };
102
+ }
103
+ function clearLogs(processId) {
104
+ logsByProcess.delete(processId);
105
+ logsByProcess.set(processId, []);
106
+ }
107
+ function getProcessById(processId) {
108
+ for (const sessionProcesses of processesBySession.values()) {
109
+ const proc = sessionProcesses.get(processId);
110
+ if (proc) return proc;
111
+ }
112
+ return void 0;
113
+ }
114
+ function cleanupAllProcesses() {
115
+ for (const sessionProcesses of processesBySession.values()) {
116
+ for (const proc of sessionProcesses.values()) {
117
+ if (proc.status === "running" && proc.pid) {
118
+ try {
119
+ process.kill(-proc.pid, "SIGTERM");
120
+ } catch {
121
+ try {
122
+ process.kill(proc.pid, "SIGTERM");
123
+ } catch (_) {
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ processesBySession.clear();
130
+ logsByProcess.clear();
131
+ }
132
+
133
+ export {
134
+ getMaxPerSession,
135
+ getProcess,
136
+ getSessionProcesses,
137
+ getSessionProcessCount,
138
+ createProcess,
139
+ startProcess,
140
+ updateStatus,
141
+ removeProcess,
142
+ appendLog,
143
+ getLogs,
144
+ getLogsPaginated,
145
+ clearLogs,
146
+ getProcessById,
147
+ cleanupAllProcesses
148
+ };
149
+ //# sourceMappingURL=chunk-574HZVLE.js.map
@@ -18,6 +18,8 @@ import {
18
18
  var EventStore = class {
19
19
  db;
20
20
  subscribers = /* @__PURE__ */ new Map();
21
+ globalSubscribers = /* @__PURE__ */ new Map();
22
+ globalSubscriberIdCounter = 0;
21
23
  constructor(db) {
22
24
  this.db = db;
23
25
  this.initSchema();
@@ -239,13 +241,82 @@ var EventStore = class {
239
241
  }
240
242
  notifySubscribers(sessionId, event) {
241
243
  const sessionSubs = this.subscribers.get(sessionId);
242
- if (!sessionSubs) return;
243
- for (const subscriber of sessionSubs) {
244
+ if (sessionSubs) {
245
+ for (const subscriber of sessionSubs) {
246
+ if (!subscriber.closed) {
247
+ subscriber.callback(event);
248
+ }
249
+ }
250
+ }
251
+ for (const subscriber of this.globalSubscribers.values()) {
244
252
  if (!subscriber.closed) {
245
253
  subscriber.callback(event);
246
254
  }
247
255
  }
248
256
  }
257
+ /**
258
+ * Subscribe to ALL events across ALL sessions.
259
+ * Unlike subscribe() which is session-specific, this receives every event.
260
+ * Used by WebSocket clients to receive real-time updates for all sessions.
261
+ *
262
+ * Returns an async iterator that yields events and an unsubscribe function
263
+ */
264
+ subscribeAll() {
265
+ const queue = [];
266
+ let resolveNext = null;
267
+ let closed = false;
268
+ const closeIterator = () => {
269
+ closed = true;
270
+ if (resolveNext) {
271
+ resolveNext({ value: void 0, done: true });
272
+ resolveNext = null;
273
+ }
274
+ };
275
+ const wsId = ++this.globalSubscriberIdCounter;
276
+ const subscriber = {
277
+ wsId,
278
+ callback: (event) => {
279
+ if (closed) return;
280
+ if (resolveNext) {
281
+ resolveNext({ value: event, done: false });
282
+ resolveNext = null;
283
+ } else {
284
+ queue.push(event);
285
+ }
286
+ },
287
+ close: closeIterator,
288
+ closed: false
289
+ };
290
+ this.globalSubscribers.set(wsId, subscriber);
291
+ const iterator = {
292
+ [Symbol.asyncIterator]() {
293
+ return this;
294
+ },
295
+ async next() {
296
+ if (closed) {
297
+ return { value: void 0, done: true };
298
+ }
299
+ const queued = queue.shift();
300
+ if (queued) {
301
+ return { value: queued, done: false };
302
+ }
303
+ return new Promise((resolve) => {
304
+ resolveNext = resolve;
305
+ });
306
+ },
307
+ async return() {
308
+ closed = true;
309
+ subscriber.closed = true;
310
+ return { value: void 0, done: true };
311
+ }
312
+ };
313
+ const unsubscribe = () => {
314
+ subscriber.closed = true;
315
+ closeIterator();
316
+ this.globalSubscribers.delete(wsId);
317
+ };
318
+ return { iterator, unsubscribe };
319
+ }
249
320
  // --------------------------------------------------------------------------
250
321
  // Cleanup
251
322
  // --------------------------------------------------------------------------
@@ -506,6 +577,13 @@ function updateSessionDangerLevel(id, dangerLevel) {
506
577
  UPDATE sessions SET danger_level = ?, updated_at = ? WHERE id = ?
507
578
  `).run(dangerLevel, now, id);
508
579
  }
580
+ function updateSessionRunning(id, isRunning) {
581
+ const db = getDatabase();
582
+ const now = (/* @__PURE__ */ new Date()).toISOString();
583
+ db.prepare(`
584
+ UPDATE sessions SET is_running = ?, updated_at = ? WHERE id = ?
585
+ `).run(isRunning ? 1 : 0, now, id);
586
+ }
509
587
  function updateSessionSummary(id, summary) {
510
588
  const db = getDatabase();
511
589
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1027,6 +1105,7 @@ export {
1027
1105
  getSession,
1028
1106
  updateSessionProvider,
1029
1107
  updateSessionDangerLevel,
1108
+ updateSessionRunning,
1030
1109
  updateSessionSummary,
1031
1110
  updateSessionMetadata,
1032
1111
  listSessions,
@@ -1064,4 +1143,4 @@ export {
1064
1143
  compactContext,
1065
1144
  getRecentUserPromptsForSession
1066
1145
  };
1067
- //# sourceMappingURL=chunk-NW7PIZH3.js.map
1146
+ //# sourceMappingURL=chunk-CIXMZ73F.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createServerMessage,
3
3
  isClientMessage
4
- } from "./chunk-TPT6HP4H.js";
4
+ } from "./chunk-XQQ2BRJA.js";
5
5
 
6
6
  // src/server/ws/protocol.ts
7
7
  function enrichMessagesWithToolResults(messages) {
@@ -340,4 +340,4 @@ export {
340
340
  isQueueCancelPayload,
341
341
  storedEventToServerMessage
342
342
  };
343
- //# sourceMappingURL=chunk-S4QKTRGJ.js.map
343
+ //# sourceMappingURL=chunk-EBOKO2PW.js.map
@@ -1,3 +1,16 @@
1
+ import {
2
+ createProcess,
3
+ getPlatformShell,
4
+ getProcessLogs,
5
+ getProcessStatus,
6
+ getSessionProcesses,
7
+ startProcessCommand,
8
+ stopProcess
9
+ } from "./chunk-NBU6KIOD.js";
10
+ import {
11
+ getMaxPerSession,
12
+ getSessionProcessCount
13
+ } from "./chunk-574HZVLE.js";
1
14
  import {
2
15
  streamWithSegments
3
16
  } from "./chunk-XFXOSPYH.js";
@@ -5,14 +18,14 @@ import {
5
18
  getContextMessages,
6
19
  getCurrentContextWindowId,
7
20
  getEventStore
8
- } from "./chunk-NW7PIZH3.js";
21
+ } from "./chunk-CIXMZ73F.js";
9
22
  import {
10
23
  createChatMessageMessage,
11
24
  createChatPathConfirmationMessage,
12
25
  createChatToolOutputMessage,
13
26
  createChatVisionFallbackMessage,
14
27
  createQueueStateMessage
15
- } from "./chunk-S4QKTRGJ.js";
28
+ } from "./chunk-EBOKO2PW.js";
16
29
  import {
17
30
  AskUserInterrupt,
18
31
  askUserTool
@@ -1121,8 +1134,8 @@ import { resolve as resolve2, isAbsolute } from "path";
1121
1134
 
1122
1135
  // src/server/tools/path-security.ts
1123
1136
  import { realpath } from "fs/promises";
1124
- import { resolve, normalize, join as join3 } from "path";
1125
- import { homedir } from "os";
1137
+ import { resolve, normalize, join as join3, basename, sep } from "path";
1138
+ import { homedir, tmpdir } from "os";
1126
1139
  var SAFE_PATHS = /* @__PURE__ */ new Set([
1127
1140
  "/dev/null",
1128
1141
  "/dev/zero",
@@ -1135,7 +1148,11 @@ var SAFE_PATHS = /* @__PURE__ */ new Set([
1135
1148
  "/dev/fd/1",
1136
1149
  "/dev/fd/2"
1137
1150
  ]);
1138
- var ALLOWED_ROOTS = ["/tmp", "/var/tmp"];
1151
+ var ALLOWED_ROOTS = [
1152
+ "/tmp",
1153
+ "/var/tmp",
1154
+ tmpdir()
1155
+ ];
1139
1156
  var SENSITIVE_FILE_PATTERNS = [
1140
1157
  // Dotenv files (but not .envrc, .env-example, .env.example)
1141
1158
  /^\.env$/,
@@ -1156,9 +1173,9 @@ var SENSITIVE_FILE_PATTERNS = [
1156
1173
  /^\.netrc$/
1157
1174
  ];
1158
1175
  function isSensitivePath(path) {
1159
- const basename = path.split("/").pop() ?? "";
1160
- if (!basename) return false;
1161
- return SENSITIVE_FILE_PATTERNS.some((pattern) => pattern.test(basename));
1176
+ const fileName = basename(path);
1177
+ if (!fileName) return false;
1178
+ return SENSITIVE_FILE_PATTERNS.some((pattern) => pattern.test(fileName));
1162
1179
  }
1163
1180
  var sessionAllowedPaths = /* @__PURE__ */ new Map();
1164
1181
  function addAllowedPath(sessionId, path) {
@@ -1200,12 +1217,12 @@ async function isPathWithinSandbox(path, workdir, sessionId) {
1200
1217
  }
1201
1218
  }
1202
1219
  resolvedPath = resolvedPath.replace(/\/+$/, "");
1203
- if (resolvedPath === normalizedWorkdir || resolvedPath.startsWith(normalizedWorkdir + "/")) {
1220
+ if (resolvedPath === normalizedWorkdir || resolvedPath.startsWith(normalizedWorkdir + sep)) {
1204
1221
  return { allowed: true, resolvedPath };
1205
1222
  }
1206
1223
  for (const root of ALLOWED_ROOTS) {
1207
1224
  const normalizedRoot = normalize(root);
1208
- if (resolvedPath === normalizedRoot || resolvedPath.startsWith(normalizedRoot + "/")) {
1225
+ if (resolvedPath === normalizedRoot || resolvedPath.startsWith(normalizedRoot + sep)) {
1209
1226
  return { allowed: true, resolvedPath };
1210
1227
  }
1211
1228
  }
@@ -2111,7 +2128,8 @@ function executeCommand(command, cwd, timeout, signal, onProgress) {
2111
2128
  reject(new Error("Command aborted before execution"));
2112
2129
  return;
2113
2130
  }
2114
- const proc = spawn2("sh", ["-c", command], {
2131
+ const shell = getPlatformShell();
2132
+ const proc = spawn2(shell.command, [...shell.args, command], {
2115
2133
  cwd,
2116
2134
  env: { ...process.env, FORCE_COLOR: "0" },
2117
2135
  stdio: ["ignore", "pipe", "pipe"],
@@ -3607,7 +3625,7 @@ var callSubAgentTool = {
3607
3625
  };
3608
3626
  }
3609
3627
  try {
3610
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-MY4TPRP4.js");
3628
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-GQ3PJL6N.js");
3611
3629
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3612
3630
  const turnMetrics = new TurnMetrics();
3613
3631
  const result = await executeSubAgent({
@@ -3954,7 +3972,8 @@ var DevServerManager = class {
3954
3972
  instance.errorMessage = void 0;
3955
3973
  instance.exited = false;
3956
3974
  const resolved = this.resolveWorkdir(workdir);
3957
- const proc = spawn3("sh", ["-c", config.command], {
3975
+ const shell = getPlatformShell();
3976
+ const proc = spawn3(shell.command, [...shell.args, config.command], {
3958
3977
  cwd: resolved,
3959
3978
  env: { ...process.env, FORCE_COLOR: "1" },
3960
3979
  stdio: ["ignore", "pipe", "pipe"],
@@ -4181,6 +4200,154 @@ var stepDoneTool = {
4181
4200
  }
4182
4201
  };
4183
4202
 
4203
+ // src/server/tools/background-process/index.ts
4204
+ var backgroundProcessTool = createTool(
4205
+ "background_process",
4206
+ {
4207
+ type: "function",
4208
+ function: {
4209
+ name: "background_process",
4210
+ description: `Start, stop, and monitor long-running background processes.
4211
+
4212
+ These processes run independently of agent turns and persist across session compaction. Processes are displayed in the right sidebar where you can view their logs and status.
4213
+
4214
+ **Actions:**
4215
+ - start: Launch a new background process (provide: name, command, cwd, timeout)
4216
+ - stop: Stop a running process and remove it from the sidebar
4217
+ - list: Show all processes for this session
4218
+ - status: Get detailed status of a specific process
4219
+ - logs: Retrieve process output with optional pagination`,
4220
+ parameters: {
4221
+ type: "object",
4222
+ properties: {
4223
+ action: {
4224
+ type: "string",
4225
+ enum: ["start", "stop", "list", "status", "logs"],
4226
+ description: "The action to perform on background processes"
4227
+ },
4228
+ name: {
4229
+ type: "string",
4230
+ description: "Display name for the process. Auto-generated from command if not provided."
4231
+ },
4232
+ command: {
4233
+ type: "string",
4234
+ description: 'Shell command to execute. Required for "start" action.'
4235
+ },
4236
+ cwd: {
4237
+ type: "string",
4238
+ description: "Working directory. Defaults to session workdir."
4239
+ },
4240
+ timeout: {
4241
+ type: "number",
4242
+ description: "Maximum runtime in milliseconds. Process will be terminated after this duration."
4243
+ },
4244
+ processId: {
4245
+ type: "string",
4246
+ description: "Process ID. Required for stop, status, and logs actions."
4247
+ },
4248
+ since: {
4249
+ type: "number",
4250
+ description: "Return logs after this offset. Default: 0"
4251
+ },
4252
+ maxLines: {
4253
+ type: "number",
4254
+ description: "Maximum lines to return. Default: 500, max: 2000"
4255
+ }
4256
+ },
4257
+ required: ["action"],
4258
+ dependentRequired: {
4259
+ start: ["command"],
4260
+ stop: ["processId"],
4261
+ status: ["processId"],
4262
+ logs: ["processId"]
4263
+ }
4264
+ }
4265
+ }
4266
+ },
4267
+ async (args, context, helpers) => {
4268
+ const sessionId = context.sessionId;
4269
+ const cwd = args.cwd ?? context.workdir;
4270
+ switch (args.action) {
4271
+ case "start": {
4272
+ const count = getSessionProcessCount(sessionId);
4273
+ const maxPerSession = getMaxPerSession();
4274
+ if (count >= maxPerSession) {
4275
+ return helpers.error(`Maximum number of background processes (${maxPerSession}) reached. Stop existing processes before starting new ones.`);
4276
+ }
4277
+ const name = args.name ?? args.command?.split(" ")[0] ?? "process";
4278
+ const process2 = createProcess(sessionId, name, args.command, cwd, args.timeout);
4279
+ if (!process2) {
4280
+ return helpers.error(`Failed to create process. Maximum limit may have been reached.`);
4281
+ }
4282
+ const pid = startProcessCommand(process2.id, sessionId, args.command, cwd);
4283
+ if (!pid) {
4284
+ return helpers.error(`Failed to start process.`);
4285
+ }
4286
+ return helpers.success(JSON.stringify({
4287
+ processId: process2.id,
4288
+ name: process2.name,
4289
+ pid,
4290
+ status: "running",
4291
+ maxReached: count + 1 >= maxPerSession
4292
+ }, null, 2));
4293
+ }
4294
+ case "stop": {
4295
+ const proc = getProcessStatus(args.processId, sessionId);
4296
+ if (!proc) {
4297
+ return helpers.error(`Process not found: ${args.processId}`);
4298
+ }
4299
+ if (proc.status !== "running") {
4300
+ return helpers.error(`Process is not running (status: ${proc.status}). Cannot stop.`);
4301
+ }
4302
+ await stopProcess(args.processId, sessionId);
4303
+ return helpers.success(JSON.stringify({
4304
+ processId: args.processId,
4305
+ status: "removed"
4306
+ }, null, 2));
4307
+ }
4308
+ case "list": {
4309
+ const processes = getSessionProcesses(sessionId);
4310
+ const maxPerSession = getMaxPerSession();
4311
+ return helpers.success(JSON.stringify({
4312
+ processes,
4313
+ maxPerSession,
4314
+ currentCount: processes.filter((p) => p.status !== "exited").length
4315
+ }, null, 2));
4316
+ }
4317
+ case "status": {
4318
+ const proc = getProcessStatus(args.processId, sessionId);
4319
+ if (!proc) {
4320
+ return helpers.error(`Process not found: ${args.processId}`);
4321
+ }
4322
+ const uptime = proc.startedAt ? Date.now() - proc.startedAt : null;
4323
+ return helpers.success(JSON.stringify({
4324
+ process: proc,
4325
+ uptime
4326
+ }, null, 2));
4327
+ }
4328
+ case "logs": {
4329
+ const proc = getProcessStatus(args.processId, sessionId);
4330
+ if (!proc) {
4331
+ return helpers.error(`Process not found: ${args.processId}`);
4332
+ }
4333
+ const since = args.since ?? 0;
4334
+ const maxLines = Math.min(args.maxLines ?? 500, 2e3);
4335
+ const { lines, totalLines, nextOffset, hasMore } = getProcessLogs(args.processId, since, maxLines);
4336
+ return helpers.success(JSON.stringify({
4337
+ processId: args.processId,
4338
+ lines,
4339
+ totalLines,
4340
+ nextOffset,
4341
+ hasMore,
4342
+ truncated: hasMore
4343
+ }, null, 2));
4344
+ }
4345
+ default:
4346
+ return helpers.error(`Unknown action: ${args.action}`);
4347
+ }
4348
+ }
4349
+ );
4350
+
4184
4351
  // src/server/tools/index.ts
4185
4352
  function parseToolPermissions(allowedTools) {
4186
4353
  const result = {};
@@ -4315,7 +4482,8 @@ function getAllToolsMap() {
4315
4482
  returnValueTool,
4316
4483
  webFetchTool,
4317
4484
  devServerTool,
4318
- stepDoneTool
4485
+ stepDoneTool,
4486
+ backgroundProcessTool
4319
4487
  ].map((t) => [t.name, t])
4320
4488
  ]);
4321
4489
  }
@@ -4590,4 +4758,4 @@ export {
4590
4758
  getToolRegistryForAgent,
4591
4759
  createToolRegistry
4592
4760
  };
4593
- //# sourceMappingURL=chunk-GXVA5W7Q.js.map
4761
+ //# sourceMappingURL=chunk-GBBTJ6ND.js.map