chainlesschain 0.45.11 → 0.45.19

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 (81) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
  3. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
  4. package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
  6. package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
  7. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  8. package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
  9. package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
  10. package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
  14. package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
  15. package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
  16. package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
  17. package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
  18. package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
  19. package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
  20. package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
  21. package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
  22. package/src/assets/web-panel/index.html +2 -2
  23. package/src/commands/agent.js +7 -8
  24. package/src/commands/chat.js +9 -11
  25. package/src/commands/serve.js +11 -106
  26. package/src/commands/session.js +185 -18
  27. package/src/commands/ui.js +10 -151
  28. package/src/gateways/repl/agent-repl.js +1 -0
  29. package/src/gateways/repl/chat-repl.js +1 -0
  30. package/src/gateways/ui/web-ui-server.js +1 -0
  31. package/src/gateways/ws/action-protocol.js +83 -0
  32. package/src/gateways/ws/message-dispatcher.js +73 -0
  33. package/src/gateways/ws/session-protocol.js +396 -0
  34. package/src/gateways/ws/task-protocol.js +55 -0
  35. package/src/gateways/ws/worktree-protocol.js +315 -0
  36. package/src/gateways/ws/ws-server.js +4 -0
  37. package/src/gateways/ws/ws-session-gateway.js +1 -0
  38. package/src/harness/background-task-manager.js +506 -0
  39. package/src/harness/background-task-worker.js +48 -0
  40. package/src/harness/compression-telemetry.js +214 -0
  41. package/src/harness/feature-flags.js +157 -0
  42. package/src/harness/jsonl-session-store.js +452 -0
  43. package/src/harness/prompt-compressor.js +416 -0
  44. package/src/harness/worktree-isolator.js +845 -0
  45. package/src/lib/agent-core.js +246 -45
  46. package/src/lib/background-task-manager.js +1 -305
  47. package/src/lib/background-task-worker.js +1 -50
  48. package/src/lib/compression-telemetry.js +5 -0
  49. package/src/lib/feature-flags.js +7 -182
  50. package/src/lib/interaction-adapter.js +32 -6
  51. package/src/lib/jsonl-session-store.js +21 -237
  52. package/src/lib/prompt-compressor.js +10 -351
  53. package/src/lib/sub-agent-context.js +91 -0
  54. package/src/lib/worktree-isolator.js +13 -231
  55. package/src/lib/ws-agent-handler.js +1 -0
  56. package/src/lib/ws-server.js +155 -359
  57. package/src/lib/ws-session-manager.js +82 -1
  58. package/src/repl/agent-repl.js +114 -32
  59. package/src/runtime/agent-runtime.js +417 -0
  60. package/src/runtime/contracts/agent-turn.js +11 -0
  61. package/src/runtime/contracts/session-record.js +31 -0
  62. package/src/runtime/contracts/task-record.js +18 -0
  63. package/src/runtime/contracts/telemetry-record.js +23 -0
  64. package/src/runtime/contracts/worktree-record.js +14 -0
  65. package/src/runtime/index.js +13 -0
  66. package/src/runtime/policies/agent-policy.js +45 -0
  67. package/src/runtime/runtime-context.js +14 -0
  68. package/src/runtime/runtime-events.js +37 -0
  69. package/src/runtime/runtime-factory.js +50 -0
  70. package/src/tools/index.js +22 -0
  71. package/src/tools/legacy-agent-tools.js +171 -0
  72. package/src/tools/registry.js +141 -0
  73. package/src/tools/tool-context.js +28 -0
  74. package/src/tools/tool-permissions.js +28 -0
  75. package/src/tools/tool-telemetry.js +39 -0
  76. package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
  77. package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
  78. package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
  79. package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
  80. package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
  81. package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
@@ -0,0 +1,506 @@
1
+ /**
2
+ * Background Task Manager - daemon task queue with completion notifications.
3
+ *
4
+ * Tasks run in child_process.fork() for isolation.
5
+ * Queue persisted to .chainlesschain/tasks/queue.jsonl.
6
+ * Completion notifications delivered to REPL callback.
7
+ *
8
+ * Feature-flag gated: BACKGROUND_TASKS
9
+ */
10
+
11
+ import { fork } from "node:child_process";
12
+ import {
13
+ existsSync,
14
+ mkdirSync,
15
+ appendFileSync,
16
+ readFileSync,
17
+ } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { createHash } from "node:crypto";
20
+ import { EventEmitter } from "node:events";
21
+ import { getHomeDir } from "../lib/paths.js";
22
+
23
+ function getTasksDir() {
24
+ const dir = join(getHomeDir(), "tasks");
25
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
26
+ return dir;
27
+ }
28
+
29
+ function queuePath() {
30
+ return join(getTasksDir(), "queue.jsonl");
31
+ }
32
+
33
+ export const TaskStatus = {
34
+ PENDING: "pending",
35
+ RUNNING: "running",
36
+ COMPLETED: "completed",
37
+ FAILED: "failed",
38
+ TIMEOUT: "timeout",
39
+ };
40
+
41
+ const RECOVERABLE_TASK_STATUSES = new Set([TaskStatus.PENDING, TaskStatus.RUNNING]);
42
+
43
+ export class BackgroundTaskManager extends EventEmitter {
44
+ constructor(options = {}) {
45
+ super();
46
+ this.maxConcurrent = options.maxConcurrent || 3;
47
+ this.heartbeatTimeout = options.heartbeatTimeout || 60000;
48
+ this.historyLimit = options.historyLimit || 50;
49
+ this.nodeId =
50
+ options.nodeId || process.env.CC_NODE_ID || `${process.pid}@${process.platform}`;
51
+ this.recoveryPolicy = options.recoveryPolicy || "claim-stale";
52
+ this.staleNodeTimeout = options.staleNodeTimeout || 5 * 60 * 1000;
53
+ this.tasks = new Map();
54
+ this.processes = new Map();
55
+ this._checkInterval = null;
56
+ if (options.recoverOnStart) {
57
+ this._loadPersistedTasks({
58
+ recoverPending: options.recoverPending !== false,
59
+ });
60
+ }
61
+ }
62
+
63
+ create(spec) {
64
+ if (this._runningCount() >= this.maxConcurrent) {
65
+ throw new Error(
66
+ `Max concurrent tasks reached (${this.maxConcurrent}). Wait for a task to finish.`,
67
+ );
68
+ }
69
+
70
+ const id = `task-${Date.now()}-${createHash("sha256").update(Math.random().toString()).digest("hex").slice(0, 6)}`;
71
+ const task = {
72
+ id,
73
+ type: spec.type || "shell",
74
+ command: spec.command,
75
+ cwd: spec.cwd || process.cwd(),
76
+ description: spec.description || spec.command,
77
+ status: TaskStatus.PENDING,
78
+ createdAt: Date.now(),
79
+ startedAt: null,
80
+ completedAt: null,
81
+ lastHeartbeat: null,
82
+ result: null,
83
+ error: null,
84
+ history: [],
85
+ outputSummary: null,
86
+ recoveredFromRestart: false,
87
+ recoverySourceStatus: null,
88
+ ownerNodeId: spec.ownerNodeId || this.nodeId,
89
+ recoveryDecision: null,
90
+ };
91
+
92
+ this._recordHistory(task, "created", {
93
+ status: task.status,
94
+ description: task.description,
95
+ });
96
+ this.tasks.set(id, task);
97
+ this._persistTask(task, "created");
98
+ return task;
99
+ }
100
+
101
+ start(taskId) {
102
+ const task = this.tasks.get(taskId);
103
+ if (!task) throw new Error(`Task not found: ${taskId}`);
104
+ if (task.status !== TaskStatus.PENDING) {
105
+ throw new Error(`Task ${taskId} is not pending (status: ${task.status})`);
106
+ }
107
+
108
+ task.status = TaskStatus.RUNNING;
109
+ task.startedAt = Date.now();
110
+ task.lastHeartbeat = Date.now();
111
+ task.recoveredFromRestart = false;
112
+ task.recoverySourceStatus = null;
113
+ this._recordHistory(task, "started", { status: task.status });
114
+
115
+ const child = fork(
116
+ join(import.meta.dirname || ".", "background-task-worker.js"),
117
+ [task.command, task.cwd, task.type],
118
+ {
119
+ cwd: task.cwd,
120
+ silent: true,
121
+ env: { ...process.env, CC_TASK_ID: taskId },
122
+ },
123
+ );
124
+
125
+ this.processes.set(taskId, child);
126
+
127
+ child.on("message", (msg) => {
128
+ if (msg.type === "heartbeat") {
129
+ task.lastHeartbeat = Date.now();
130
+ } else if (msg.type === "result") {
131
+ this._complete(taskId, TaskStatus.COMPLETED, msg.data, null);
132
+ } else if (msg.type === "error") {
133
+ this._complete(taskId, TaskStatus.FAILED, null, msg.error);
134
+ }
135
+ });
136
+
137
+ child.on("exit", (code) => {
138
+ if (task.status === TaskStatus.RUNNING) {
139
+ if (code === 0) {
140
+ this._complete(taskId, TaskStatus.COMPLETED, "Process exited (0)", null);
141
+ } else {
142
+ this._complete(
143
+ taskId,
144
+ TaskStatus.FAILED,
145
+ null,
146
+ `Process exited with code ${code}`,
147
+ );
148
+ }
149
+ }
150
+ this.processes.delete(taskId);
151
+ });
152
+
153
+ child.on("error", (err) => {
154
+ this._complete(taskId, TaskStatus.FAILED, null, err.message);
155
+ this.processes.delete(taskId);
156
+ });
157
+
158
+ this._persistTask(task, "started");
159
+ this._ensureHeartbeatChecker();
160
+ return task;
161
+ }
162
+
163
+ run(spec) {
164
+ const task = this.create(spec);
165
+ this.start(task.id);
166
+ return task;
167
+ }
168
+
169
+ get(taskId) {
170
+ return this.tasks.get(taskId) || null;
171
+ }
172
+
173
+ getDetails(taskId) {
174
+ return this.get(taskId);
175
+ }
176
+
177
+ getHistory(taskId, options = {}) {
178
+ const history = this.get(taskId)?.history || [];
179
+ const limit = Number.isFinite(options.limit) ? Math.max(1, options.limit) : null;
180
+ const offset = Number.isFinite(options.offset) ? Math.max(0, options.offset) : 0;
181
+
182
+ if (limit === null && offset === 0) {
183
+ return history;
184
+ }
185
+
186
+ const items = history.slice(offset, offset + limit);
187
+ return {
188
+ items,
189
+ total: history.length,
190
+ offset,
191
+ limit: limit || history.length,
192
+ hasMore: offset + items.length < history.length,
193
+ nextOffset: offset + items.length < history.length ? offset + items.length : null,
194
+ };
195
+ }
196
+
197
+ list(filter = {}) {
198
+ let tasks = [...this.tasks.values()];
199
+ if (filter.status) {
200
+ tasks = tasks.filter((task) => task.status === filter.status);
201
+ }
202
+ return tasks.sort((a, b) => b.createdAt - a.createdAt);
203
+ }
204
+
205
+ stop(taskId) {
206
+ const child = this.processes.get(taskId);
207
+ if (child) {
208
+ child.kill("SIGTERM");
209
+ setTimeout(() => {
210
+ if (child.exitCode === null) child.kill("SIGKILL");
211
+ }, 2000);
212
+ }
213
+ const task = this.tasks.get(taskId);
214
+ if (task) {
215
+ this._recordHistory(task, "stop-requested", { requestedBy: "user" });
216
+ }
217
+ this._complete(taskId, TaskStatus.FAILED, null, "Stopped by user");
218
+ }
219
+
220
+ cleanup(maxAge = 3600000) {
221
+ const cutoff = Date.now() - maxAge;
222
+ let removed = 0;
223
+ for (const [id, task] of this.tasks) {
224
+ if (
225
+ (task.status === TaskStatus.COMPLETED ||
226
+ task.status === TaskStatus.FAILED ||
227
+ task.status === TaskStatus.TIMEOUT) &&
228
+ task.completedAt &&
229
+ task.completedAt < cutoff
230
+ ) {
231
+ this.tasks.delete(id);
232
+ this._persistTask(task, "cleaned-up", { removedAt: Date.now() });
233
+ removed++;
234
+ }
235
+ }
236
+ return removed;
237
+ }
238
+
239
+ destroy() {
240
+ if (this._checkInterval) {
241
+ clearInterval(this._checkInterval);
242
+ this._checkInterval = null;
243
+ }
244
+ for (const [id] of this.processes) {
245
+ this.stop(id);
246
+ }
247
+ this.tasks.clear();
248
+ this.processes.clear();
249
+ }
250
+
251
+ _complete(taskId, status, result, error) {
252
+ const task = this.tasks.get(taskId);
253
+ if (!task) return;
254
+
255
+ task.status = status;
256
+ task.completedAt = Date.now();
257
+ task.result = result;
258
+ task.error = error;
259
+ task.outputSummary = this._buildOutputSummary({ result, error, status });
260
+ this._recordHistory(task, "completed", { status, result, error });
261
+ this._persistTask(task, "completed");
262
+ this.emit("task:complete", task);
263
+ }
264
+
265
+ _runningCount() {
266
+ let count = 0;
267
+ for (const task of this.tasks.values()) {
268
+ if (task.status === TaskStatus.RUNNING) count++;
269
+ }
270
+ return count;
271
+ }
272
+
273
+ _persistTask(task, eventType = "snapshot", meta = {}) {
274
+ try {
275
+ const line =
276
+ JSON.stringify({
277
+ kind: "task_snapshot",
278
+ eventType,
279
+ persistedAt: Date.now(),
280
+ meta,
281
+ task,
282
+ }) + "\n";
283
+ appendFileSync(queuePath(), line, "utf-8");
284
+ } catch (_e) {
285
+ // Non-critical
286
+ }
287
+ }
288
+
289
+ _recordHistory(task, event, meta = {}) {
290
+ if (!Array.isArray(task.history)) {
291
+ task.history = [];
292
+ }
293
+ task.history.push({
294
+ event,
295
+ timestamp: Date.now(),
296
+ ...meta,
297
+ });
298
+ if (task.history.length > this.historyLimit) {
299
+ task.history = task.history.slice(-this.historyLimit);
300
+ }
301
+ }
302
+
303
+ _loadPersistedTasks(options = {}) {
304
+ const filePath = queuePath();
305
+ if (!existsSync(filePath)) return;
306
+
307
+ try {
308
+ const content = readFileSync(filePath, "utf-8");
309
+ for (const line of content.split("\n")) {
310
+ if (!line.trim()) continue;
311
+ let parsed;
312
+ try {
313
+ parsed = JSON.parse(line);
314
+ } catch {
315
+ continue;
316
+ }
317
+
318
+ const task = this._normalizePersistedTask(parsed);
319
+ if (!task?.id) continue;
320
+ this.tasks.set(task.id, task);
321
+ }
322
+
323
+ if (options.recoverPending !== false) {
324
+ this._recoverInterruptedTasks();
325
+ }
326
+ } catch (_err) {
327
+ // Non-critical
328
+ }
329
+ }
330
+
331
+ _normalizePersistedTask(entry) {
332
+ const task = entry?.kind === "task_snapshot" && entry.task ? entry.task : entry;
333
+
334
+ if (!task || typeof task !== "object") {
335
+ return null;
336
+ }
337
+
338
+ const normalized = {
339
+ ...task,
340
+ history: Array.isArray(task.history) ? [...task.history] : [],
341
+ outputSummary: task.outputSummary || this._buildOutputSummary(task),
342
+ recoveredFromRestart: Boolean(task.recoveredFromRestart),
343
+ recoverySourceStatus: task.recoverySourceStatus || null,
344
+ ownerNodeId: task.ownerNodeId || this.nodeId,
345
+ recoveryDecision: task.recoveryDecision || null,
346
+ };
347
+
348
+ if (normalized.history.length === 0) {
349
+ normalized.history.push({
350
+ event: "loaded",
351
+ timestamp: normalized.completedAt || normalized.startedAt || normalized.createdAt || Date.now(),
352
+ status: normalized.status,
353
+ });
354
+ }
355
+
356
+ return normalized;
357
+ }
358
+
359
+ _recoverInterruptedTasks() {
360
+ for (const task of this.tasks.values()) {
361
+ if (!RECOVERABLE_TASK_STATUSES.has(task.status)) continue;
362
+ const previousStatus = task.status;
363
+ const decision = this._decideRecovery(task);
364
+ task.recoveryDecision = decision;
365
+
366
+ if (!decision.shouldRecover) {
367
+ this._recordHistory(task, "recovery-skipped", {
368
+ fromStatus: previousStatus,
369
+ policy: decision.policy,
370
+ reason: decision.reason,
371
+ ownerNodeId: task.ownerNodeId || null,
372
+ candidateNodeId: this.nodeId,
373
+ });
374
+ this._persistTask(task, "recovery-skipped", {
375
+ fromStatus: previousStatus,
376
+ policy: decision.policy,
377
+ reason: decision.reason,
378
+ });
379
+ continue;
380
+ }
381
+
382
+ task.status = TaskStatus.PENDING;
383
+ task.startedAt = null;
384
+ task.lastHeartbeat = null;
385
+ task.completedAt = null;
386
+ task.result = null;
387
+ task.error = null;
388
+ task.outputSummary = null;
389
+ task.recoveredFromRestart = true;
390
+ task.recoverySourceStatus = previousStatus;
391
+ task.recoveredAt = Date.now();
392
+ task.ownerNodeId = this.nodeId;
393
+ this._recordHistory(task, "recovered", {
394
+ fromStatus: previousStatus,
395
+ status: task.status,
396
+ policy: decision.policy,
397
+ reason: decision.reason,
398
+ previousOwnerNodeId: decision.previousOwnerNodeId,
399
+ claimedByNodeId: this.nodeId,
400
+ });
401
+ this._persistTask(task, "recovered", {
402
+ fromStatus: previousStatus,
403
+ policy: decision.policy,
404
+ reason: decision.reason,
405
+ });
406
+ }
407
+ }
408
+
409
+ _decideRecovery(task) {
410
+ const previousOwnerNodeId = task.ownerNodeId || null;
411
+ const sameNode = previousOwnerNodeId === this.nodeId || !previousOwnerNodeId;
412
+ const lastSeenAt = task.lastHeartbeat || task.startedAt || task.createdAt || 0;
413
+ const stale = Date.now() - lastSeenAt > this.staleNodeTimeout;
414
+
415
+ if (sameNode) {
416
+ return {
417
+ shouldRecover: true,
418
+ policy: this.recoveryPolicy,
419
+ reason: "same-node",
420
+ previousOwnerNodeId,
421
+ };
422
+ }
423
+
424
+ if (this.recoveryPolicy === "local-only") {
425
+ return {
426
+ shouldRecover: false,
427
+ policy: this.recoveryPolicy,
428
+ reason: "owned-by-other-node",
429
+ previousOwnerNodeId,
430
+ };
431
+ }
432
+
433
+ if (this.recoveryPolicy === "observe-only") {
434
+ return {
435
+ shouldRecover: false,
436
+ policy: this.recoveryPolicy,
437
+ reason: stale ? "foreign-node-stale-observed" : "foreign-node-active-observed",
438
+ previousOwnerNodeId,
439
+ };
440
+ }
441
+
442
+ return {
443
+ shouldRecover: stale,
444
+ policy: this.recoveryPolicy,
445
+ reason: stale ? "stale-foreign-node-claimed" : "foreign-node-still-fresh",
446
+ previousOwnerNodeId,
447
+ };
448
+ }
449
+
450
+ _buildOutputSummary(task = {}) {
451
+ const resultText = this._stringifyOutput(task.result);
452
+ const errorText = this._stringifyOutput(task.error);
453
+ const primary = errorText || resultText;
454
+
455
+ if (!primary) return null;
456
+
457
+ const lines = primary.split(/\r?\n/).filter(Boolean);
458
+ return {
459
+ kind: errorText ? "error" : "result",
460
+ status: task.status || null,
461
+ preview: primary.slice(0, 240),
462
+ lineCount: lines.length,
463
+ charCount: primary.length,
464
+ truncated: primary.length > 240,
465
+ };
466
+ }
467
+
468
+ _stringifyOutput(value) {
469
+ if (value == null) return "";
470
+ if (typeof value === "string") return value.trim();
471
+ try {
472
+ return JSON.stringify(value).trim();
473
+ } catch {
474
+ return String(value).trim();
475
+ }
476
+ }
477
+
478
+ _ensureHeartbeatChecker() {
479
+ if (this._checkInterval) return;
480
+
481
+ this._checkInterval = setInterval(
482
+ () => {
483
+ const now = Date.now();
484
+ for (const [id, task] of this.tasks) {
485
+ if (
486
+ task.status === TaskStatus.RUNNING &&
487
+ task.lastHeartbeat &&
488
+ now - task.lastHeartbeat > this.heartbeatTimeout
489
+ ) {
490
+ this._complete(id, TaskStatus.TIMEOUT, null, "Heartbeat timeout");
491
+ const child = this.processes.get(id);
492
+ if (child) {
493
+ child.kill("SIGKILL");
494
+ this.processes.delete(id);
495
+ }
496
+ }
497
+ }
498
+ },
499
+ Math.min(this.heartbeatTimeout / 2, 10000),
500
+ );
501
+
502
+ if (this._checkInterval.unref) {
503
+ this._checkInterval.unref();
504
+ }
505
+ }
506
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Background Task Worker - child process that executes a command.
3
+ *
4
+ * Args: [command, cwd, type]
5
+ * Sends messages to parent: { type: "heartbeat"|"result"|"error", ... }
6
+ */
7
+
8
+ import { execSync } from "node:child_process";
9
+
10
+ const [command, cwd, type] = process.argv.slice(2);
11
+
12
+ const heartbeat = setInterval(() => {
13
+ if (process.send) process.send({ type: "heartbeat" });
14
+ }, 5000);
15
+
16
+ try {
17
+ let result;
18
+
19
+ if (type === "shell") {
20
+ result = execSync(command, {
21
+ cwd: cwd || process.cwd(),
22
+ encoding: "utf-8",
23
+ timeout: 300000,
24
+ maxBuffer: 10 * 1024 * 1024,
25
+ });
26
+ } else {
27
+ result = execSync(command, {
28
+ cwd: cwd || process.cwd(),
29
+ encoding: "utf-8",
30
+ timeout: 300000,
31
+ maxBuffer: 10 * 1024 * 1024,
32
+ });
33
+ }
34
+
35
+ if (process.send) {
36
+ process.send({ type: "result", data: result || "Done" });
37
+ }
38
+ } catch (err) {
39
+ if (process.send) {
40
+ process.send({
41
+ type: "error",
42
+ error: err.stderr || err.message || String(err),
43
+ });
44
+ }
45
+ process.exitCode = 1;
46
+ } finally {
47
+ clearInterval(heartbeat);
48
+ }