skimpyclaw 0.3.14 → 0.4.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 (222) hide show
  1. package/README.md +47 -37
  2. package/dist/__tests__/adapter-types.test.d.ts +4 -0
  3. package/dist/__tests__/adapter-types.test.js +63 -0
  4. package/dist/__tests__/anthropic-adapter.test.d.ts +4 -0
  5. package/dist/__tests__/anthropic-adapter.test.js +264 -0
  6. package/dist/__tests__/api.test.js +0 -1
  7. package/dist/__tests__/cli.integration.test.js +2 -4
  8. package/dist/__tests__/cli.test.js +0 -1
  9. package/dist/__tests__/code-agents-notifications.test.js +137 -0
  10. package/dist/__tests__/code-agents-parser.test.js +19 -1
  11. package/dist/__tests__/code-agents-preflight.test.js +3 -28
  12. package/dist/__tests__/code-agents-utils.test.js +34 -9
  13. package/dist/__tests__/code-agents-worktrees.test.js +116 -0
  14. package/dist/__tests__/codex-adapter.test.js +184 -0
  15. package/dist/__tests__/codex-auth.test.js +66 -0
  16. package/dist/__tests__/codex-provider-gating.test.js +35 -0
  17. package/dist/__tests__/codex-unified-loop.test.js +111 -0
  18. package/dist/__tests__/config-security.test.js +127 -0
  19. package/dist/__tests__/config.test.js +23 -0
  20. package/dist/__tests__/context-manager.test.js +243 -164
  21. package/dist/__tests__/cron-run.test.js +250 -0
  22. package/dist/__tests__/cron.test.js +12 -38
  23. package/dist/__tests__/digests.test.js +67 -0
  24. package/dist/__tests__/discord-attachments.test.js +211 -0
  25. package/dist/__tests__/discord-docs.test.d.ts +1 -0
  26. package/dist/__tests__/discord-docs.test.js +27 -0
  27. package/dist/__tests__/discord-thread-agents.test.d.ts +1 -0
  28. package/dist/__tests__/discord-thread-agents.test.js +115 -0
  29. package/dist/__tests__/discord-thread-context.test.d.ts +1 -0
  30. package/dist/__tests__/discord-thread-context.test.js +42 -0
  31. package/dist/__tests__/doctor.formatters.test.js +4 -4
  32. package/dist/__tests__/doctor.index.test.js +1 -1
  33. package/dist/__tests__/doctor.runner.test.js +3 -15
  34. package/dist/__tests__/env-sanitizer.test.d.ts +1 -0
  35. package/dist/__tests__/env-sanitizer.test.js +45 -0
  36. package/dist/__tests__/exec-approval.test.js +61 -0
  37. package/dist/__tests__/fetch-tool.test.d.ts +1 -0
  38. package/dist/__tests__/fetch-tool.test.js +85 -0
  39. package/dist/__tests__/gateway-status-auth.test.d.ts +1 -0
  40. package/dist/__tests__/gateway-status-auth.test.js +72 -0
  41. package/dist/__tests__/heartbeat.test.js +3 -3
  42. package/dist/__tests__/interactive-sessions.test.d.ts +1 -0
  43. package/dist/__tests__/interactive-sessions.test.js +96 -0
  44. package/dist/__tests__/langfuse.test.js +6 -18
  45. package/dist/__tests__/model-selection.test.js +3 -4
  46. package/dist/__tests__/providers-init.test.js +2 -8
  47. package/dist/__tests__/providers-routing.test.js +1 -1
  48. package/dist/__tests__/providers-utils.test.js +13 -3
  49. package/dist/__tests__/sessions.test.js +14 -10
  50. package/dist/__tests__/setup.test.js +12 -29
  51. package/dist/__tests__/skills.test.js +10 -7
  52. package/dist/__tests__/stream-formatter.test.d.ts +1 -0
  53. package/dist/__tests__/stream-formatter.test.js +114 -0
  54. package/dist/__tests__/token-efficiency.test.js +131 -15
  55. package/dist/__tests__/tool-loop.test.d.ts +4 -0
  56. package/dist/__tests__/tool-loop.test.js +505 -0
  57. package/dist/__tests__/tools.test.js +101 -276
  58. package/dist/__tests__/utils.test.d.ts +1 -0
  59. package/dist/__tests__/utils.test.js +14 -0
  60. package/dist/__tests__/voice.test.js +21 -0
  61. package/dist/agent.js +35 -4
  62. package/dist/api.js +113 -37
  63. package/dist/channels/discord/attachments.d.ts +50 -0
  64. package/dist/channels/discord/attachments.js +137 -0
  65. package/dist/channels/discord/delegation.d.ts +5 -0
  66. package/dist/channels/discord/delegation.js +136 -0
  67. package/dist/channels/discord/handlers.js +694 -7
  68. package/dist/channels/discord/index.d.ts +16 -1
  69. package/dist/channels/discord/index.js +64 -1
  70. package/dist/channels/discord/thread-agents.d.ts +54 -0
  71. package/dist/channels/discord/thread-agents.js +323 -0
  72. package/dist/channels/discord/threads.d.ts +58 -0
  73. package/dist/channels/discord/threads.js +192 -0
  74. package/dist/channels/discord/types.js +4 -2
  75. package/dist/channels/discord/utils.d.ts +16 -0
  76. package/dist/channels/discord/utils.js +86 -6
  77. package/dist/channels/telegram/index.d.ts +1 -1
  78. package/dist/channels/telegram/types.js +1 -1
  79. package/dist/channels/telegram/utils.js +9 -3
  80. package/dist/channels.d.ts +1 -1
  81. package/dist/cli.js +20 -400
  82. package/dist/code-agents/executor.d.ts +1 -1
  83. package/dist/code-agents/executor.js +101 -45
  84. package/dist/code-agents/index.d.ts +2 -7
  85. package/dist/code-agents/index.js +111 -80
  86. package/dist/code-agents/interactive-resume.d.ts +6 -0
  87. package/dist/code-agents/interactive-resume.js +98 -0
  88. package/dist/code-agents/interactive-sessions.d.ts +20 -0
  89. package/dist/code-agents/interactive-sessions.js +132 -0
  90. package/dist/code-agents/parser.js +5 -1
  91. package/dist/code-agents/registry.d.ts +7 -1
  92. package/dist/code-agents/registry.js +11 -23
  93. package/dist/code-agents/stream-formatter.d.ts +8 -0
  94. package/dist/code-agents/stream-formatter.js +92 -0
  95. package/dist/code-agents/types.d.ts +16 -24
  96. package/dist/code-agents/utils.d.ts +35 -11
  97. package/dist/code-agents/utils.js +349 -95
  98. package/dist/code-agents/worktrees.d.ts +37 -0
  99. package/dist/code-agents/worktrees.js +116 -0
  100. package/dist/config.d.ts +2 -4
  101. package/dist/config.js +123 -23
  102. package/dist/cron.d.ts +1 -6
  103. package/dist/cron.js +175 -82
  104. package/dist/dashboard/assets/index-B345aOO-.js +65 -0
  105. package/dist/dashboard/assets/index-ZWK4dalJ.css +1 -0
  106. package/dist/dashboard/index.html +2 -2
  107. package/dist/digests.d.ts +1 -0
  108. package/dist/digests.js +132 -42
  109. package/dist/doctor/checks.d.ts +0 -3
  110. package/dist/doctor/checks.js +1 -108
  111. package/dist/doctor/runner.js +1 -4
  112. package/dist/env-sanitizer.d.ts +2 -0
  113. package/dist/env-sanitizer.js +61 -0
  114. package/dist/exec-approval.d.ts +11 -1
  115. package/dist/exec-approval.js +17 -4
  116. package/dist/gateway.d.ts +3 -1
  117. package/dist/gateway.js +17 -7
  118. package/dist/heartbeat.js +1 -6
  119. package/dist/langfuse.js +3 -29
  120. package/dist/model-selection.js +3 -1
  121. package/dist/providers/adapter.d.ts +118 -0
  122. package/dist/providers/adapter.js +6 -0
  123. package/dist/providers/adapters/anthropic-adapter.d.ts +22 -0
  124. package/dist/providers/adapters/anthropic-adapter.js +204 -0
  125. package/dist/providers/adapters/codex-adapter.d.ts +26 -0
  126. package/dist/providers/adapters/codex-adapter.js +203 -0
  127. package/dist/providers/anthropic.d.ts +1 -0
  128. package/dist/providers/anthropic.js +10 -272
  129. package/dist/providers/codex.d.ts +21 -0
  130. package/dist/providers/codex.js +149 -330
  131. package/dist/providers/content.d.ts +1 -1
  132. package/dist/providers/content.js +2 -2
  133. package/dist/providers/context-manager.d.ts +18 -6
  134. package/dist/providers/context-manager.js +199 -223
  135. package/dist/providers/index.d.ts +9 -1
  136. package/dist/providers/index.js +73 -64
  137. package/dist/providers/loop-utils.d.ts +20 -0
  138. package/dist/providers/loop-utils.js +30 -0
  139. package/dist/providers/tool-loop.d.ts +12 -0
  140. package/dist/providers/tool-loop.js +251 -0
  141. package/dist/providers/utils.d.ts +19 -3
  142. package/dist/providers/utils.js +100 -29
  143. package/dist/secure-store.d.ts +8 -0
  144. package/dist/secure-store.js +80 -0
  145. package/dist/service.js +3 -28
  146. package/dist/sessions.d.ts +3 -0
  147. package/dist/sessions.js +147 -18
  148. package/dist/setup-templates.js +13 -25
  149. package/dist/setup.d.ts +10 -6
  150. package/dist/setup.js +84 -292
  151. package/dist/skills.js +3 -11
  152. package/dist/tools/agent-delegation.d.ts +19 -0
  153. package/dist/tools/agent-delegation.js +49 -0
  154. package/dist/tools/bash-tool.js +89 -34
  155. package/dist/tools/definitions.d.ts +199 -302
  156. package/dist/tools/definitions.js +70 -123
  157. package/dist/tools/execute-context.d.ts +13 -4
  158. package/dist/tools/fetch-tool.js +109 -13
  159. package/dist/tools/file-tools.js +7 -1
  160. package/dist/tools.d.ts +7 -7
  161. package/dist/tools.js +133 -151
  162. package/dist/types.d.ts +37 -30
  163. package/dist/utils.js +4 -6
  164. package/dist/voice.d.ts +1 -1
  165. package/dist/voice.js +17 -4
  166. package/package.json +33 -23
  167. package/templates/TOOLS.md +0 -27
  168. package/dist/__tests__/audit.test.js +0 -122
  169. package/dist/__tests__/code-agents-orchestrator.test.js +0 -216
  170. package/dist/__tests__/code-agents-sandbox.test.js +0 -163
  171. package/dist/__tests__/orchestrator.test.js +0 -425
  172. package/dist/__tests__/sandbox-bridge.test.js +0 -116
  173. package/dist/__tests__/sandbox-manager.test.js +0 -144
  174. package/dist/__tests__/sandbox-mount-security.test.js +0 -139
  175. package/dist/__tests__/sandbox-runtime.test.js +0 -176
  176. package/dist/__tests__/subagent.test.js +0 -240
  177. package/dist/__tests__/telegram.test.js +0 -42
  178. package/dist/code-agents/orchestrator.d.ts +0 -29
  179. package/dist/code-agents/orchestrator.js +0 -694
  180. package/dist/code-agents/worktree.d.ts +0 -40
  181. package/dist/code-agents/worktree.js +0 -215
  182. package/dist/dashboard/assets/index-BoTHPby4.js +0 -65
  183. package/dist/dashboard/assets/index-D4mufvBg.css +0 -1
  184. package/dist/dashboard.d.ts +0 -8
  185. package/dist/dashboard.js +0 -4071
  186. package/dist/discord.d.ts +0 -8
  187. package/dist/discord.js +0 -792
  188. package/dist/mcp-context-a8c.d.ts +0 -13
  189. package/dist/mcp-context-a8c.js +0 -34
  190. package/dist/orchestrator.d.ts +0 -15
  191. package/dist/orchestrator.js +0 -676
  192. package/dist/providers/openai.d.ts +0 -10
  193. package/dist/providers/openai.js +0 -355
  194. package/dist/sandbox/bridge.d.ts +0 -5
  195. package/dist/sandbox/bridge.js +0 -63
  196. package/dist/sandbox/index.d.ts +0 -5
  197. package/dist/sandbox/index.js +0 -4
  198. package/dist/sandbox/manager.d.ts +0 -7
  199. package/dist/sandbox/manager.js +0 -100
  200. package/dist/sandbox/mount-security.d.ts +0 -12
  201. package/dist/sandbox/mount-security.js +0 -122
  202. package/dist/sandbox/runtime.d.ts +0 -39
  203. package/dist/sandbox/runtime.js +0 -192
  204. package/dist/sandbox-utils.d.ts +0 -6
  205. package/dist/sandbox-utils.js +0 -36
  206. package/dist/subagent.d.ts +0 -19
  207. package/dist/subagent.js +0 -407
  208. package/dist/telegram.d.ts +0 -2
  209. package/dist/telegram.js +0 -11
  210. package/dist/tools/browser-tool.d.ts +0 -3
  211. package/dist/tools/browser-tool.js +0 -266
  212. package/sandbox/Dockerfile +0 -40
  213. /package/dist/__tests__/{audit.test.d.ts → code-agents-notifications.test.d.ts} +0 -0
  214. /package/dist/__tests__/{code-agents-orchestrator.test.d.ts → code-agents-worktrees.test.d.ts} +0 -0
  215. /package/dist/__tests__/{code-agents-sandbox.test.d.ts → codex-adapter.test.d.ts} +0 -0
  216. /package/dist/__tests__/{orchestrator.test.d.ts → codex-auth.test.d.ts} +0 -0
  217. /package/dist/__tests__/{sandbox-bridge.test.d.ts → codex-provider-gating.test.d.ts} +0 -0
  218. /package/dist/__tests__/{sandbox-manager.test.d.ts → codex-unified-loop.test.d.ts} +0 -0
  219. /package/dist/__tests__/{sandbox-mount-security.test.d.ts → config-security.test.d.ts} +0 -0
  220. /package/dist/__tests__/{sandbox-runtime.test.d.ts → cron-run.test.d.ts} +0 -0
  221. /package/dist/__tests__/{subagent.test.d.ts → digests.test.d.ts} +0 -0
  222. /package/dist/__tests__/{telegram.test.d.ts → discord-attachments.test.d.ts} +0 -0
@@ -1,17 +1,29 @@
1
1
  // Code Agent Executor - Background execution logic
2
2
  import { spawn, exec, execSync } from 'child_process';
3
3
  import { createWriteStream, existsSync, readFileSync } from 'fs';
4
- import { join } from 'path';
4
+ import { join, dirname } from 'path';
5
+ /**
6
+ * Ensure PATH contains the directory of the running node binary. The claude /
7
+ * codex CLIs use `#!/usr/bin/env node` shebangs — when launchd-spawned processes
8
+ * inherit a minimal PATH, `env node` fails and spawn returns ENOENT.
9
+ */
10
+ function ensureNodeInPath(env) {
11
+ const nodeDir = dirname(process.execPath);
12
+ const path = env.PATH ?? '';
13
+ if (!path.split(':').includes(nodeDir)) {
14
+ env.PATH = path ? `${nodeDir}:${path}` : nodeDir;
15
+ }
16
+ }
5
17
  // SKIMPYCLAW_ROOT for log paths
6
18
  const SKIMPYCLAW_ROOT = join(import.meta.dirname || process.cwd(), '..', '..');
7
19
  import { toErrorMessage } from '../utils.js';
8
20
  import { VALIDATE_TIMEOUT_MS } from './types.js';
9
21
  import { getCodeAgentsDir, ensureCodeAgentsDir, writeCodeAgentTask, setCodeAgentCanceller, deleteCodeAgentCanceller, getCodeAgent, } from './registry.js';
10
- import { buildCodeAgentArgs, notifyCodeAgentResult } from './utils.js';
22
+ import { buildCodeAgentArgs, buildCodeAgentSpawnEnv, notifyCodeAgentResult } from './utils.js';
23
+ import { cleanupCodeAgentWorktree } from './worktrees.js';
11
24
  import { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
12
25
  import { startTrace, addEvent, endTrace } from '../audit.js';
13
26
  import { buildUsageRecord, recordUsage } from '../usage.js';
14
- import { ensureContainer, SANDBOX_DEFAULTS, getRuntime } from '../sandbox/index.js';
15
27
  const CANCELLED_MESSAGE = 'Cancelled by user';
16
28
  /**
17
29
  * Detect the package manager for a project directory.
@@ -112,6 +124,11 @@ function getChangedPackageDirs(workdir) {
112
124
  return [];
113
125
  }
114
126
  }
127
+ // Reject any value that could break out of an unquoted shell argument.
128
+ // npm package names: optional "@scope/", then [a-zA-Z0-9._-]. Max 214 chars.
129
+ const NPM_PKG_NAME_RE = /^(?:@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/;
130
+ // Workspace directory paths derived from git: allow nested path segments only.
131
+ const SAFE_PATH_RE = /^[a-zA-Z0-9._-]+(?:\/[a-zA-Z0-9._-]+)*$/;
115
132
  /**
116
133
  * Build scoped validation commands for a monorepo by detecting changed packages.
117
134
  * Returns a combined command that builds/tests only affected packages, or null
@@ -127,6 +144,11 @@ function buildMonorepoValidationCommand(workdir) {
127
144
  const pm = detectPackageManager(workdir);
128
145
  const parts = [];
129
146
  for (const dir of changedDirs) {
147
+ // Defence-in-depth: `dir` flows into shell command strings below.
148
+ if (!SAFE_PATH_RE.test(dir) || dir.length > 256) {
149
+ console.warn(`[validation] Skipping unsafe package dir: ${JSON.stringify(dir)}`);
150
+ continue;
151
+ }
130
152
  const pkgJsonPath = join(workdir, dir, 'package.json');
131
153
  if (!existsSync(pkgJsonPath))
132
154
  continue;
@@ -136,6 +158,13 @@ function buildMonorepoValidationCommand(workdir) {
136
158
  const scripts = pkg.scripts || {};
137
159
  if (!pkgName)
138
160
  continue;
161
+ // pkgName comes from an untrusted package.json and is interpolated into
162
+ // a shell command executed by exec(). Enforce npm's legal name charset
163
+ // to prevent command injection via crafted "name" fields.
164
+ if (typeof pkgName !== 'string' || pkgName.length > 214 || !NPM_PKG_NAME_RE.test(pkgName)) {
165
+ console.warn(`[validation] Skipping package with unsafe name: ${JSON.stringify(pkgName)}`);
166
+ continue;
167
+ }
139
168
  // Build with workspace command
140
169
  if (scripts.build) {
141
170
  if (pm === 'pnpm')
@@ -252,7 +281,7 @@ export function buildValidationCommand(workdir, validationCommands) {
252
281
  }
253
282
  return parts.join(' && ');
254
283
  }
255
- /** Run build/test validation. Shared by solo agents and team orchestrator. */
284
+ /** Run build/test validation. */
256
285
  export function runValidation(workdir, validationCommands) {
257
286
  const cmd = buildValidationCommand(workdir, validationCommands);
258
287
  if (!cmd) {
@@ -323,7 +352,9 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
323
352
  agent,
324
353
  workdir,
325
354
  model: input.model,
355
+ effort: input.effort,
326
356
  max_turns: input.max_turns,
357
+ sessionId: caTask.cliSessionId,
327
358
  });
328
359
  let stdout = '';
329
360
  let stderr = '';
@@ -331,29 +362,25 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
331
362
  const logPath = join(getCodeAgentsDir(), `${id}.log`);
332
363
  ensureCodeAgentsDir();
333
364
  const logStream = createWriteStream(logPath, { flags: 'w' });
334
- logStream.write(`=== ${id} | ${agent} | ${new Date().toISOString()} ===\n`);
335
- logStream.write(`Task: ${task.slice(0, 500)}\n`);
336
- logStream.write(`Workdir: ${workdir}\n\n`);
365
+ let logStreamEnded = false;
366
+ const logWrite = (data) => { if (!logStreamEnded)
367
+ logStream.write(data); };
368
+ logWrite(`=== ${id} | ${agent} | ${new Date().toISOString()} ===\n`);
369
+ logWrite(`Task: ${task.slice(0, 500)}\n`);
370
+ logWrite(`Workdir: ${workdir}\n\n`);
371
+ if (caTask.sourceWorkdir)
372
+ logWrite(`Source workdir: ${caTask.sourceWorkdir}\n`);
373
+ if (caTask.worktreePath)
374
+ logWrite(`Worktree: ${caTask.worktreePath}\n`);
337
375
  try {
338
- // Resolve sandbox container name if enabled (used for spawn wrapping)
339
- let sandboxContainer;
340
- if (options?.sandboxConfig?.enabled) {
341
- const merged = { ...SANDBOX_DEFAULTS, ...options.sandboxConfig };
342
- sandboxContainer = await ensureContainer(`code-${id}`, merged, options.allowedPaths || [workdir]);
343
- console.log(`[code-agent] Running in sandbox container: ${sandboxContainer}`);
344
- }
345
376
  ensureNotCancelled();
346
377
  const exitCode = await new Promise((resolvePromise, reject) => {
347
- const spawnEnv = { ...process.env };
348
- delete spawnEnv.CLAUDECODE;
349
- // Apply extra env vars (e.g. team mode feature flag)
378
+ const spawnEnv = buildCodeAgentSpawnEnv();
379
+ ensureNodeInPath(spawnEnv);
350
380
  if (options?.env)
351
381
  Object.assign(spawnEnv, options.env);
352
- // When sandbox is enabled, wrap the spawn: container exec <name> <cmd> <args>
353
- const spawnCmd = sandboxContainer ? getRuntime() : cmd;
354
- const spawnArgs = sandboxContainer ? ['exec', sandboxContainer, cmd, ...args] : args;
355
- const proc = spawn(spawnCmd, spawnArgs, {
356
- cwd: sandboxContainer ? undefined : workdir, // container has its own cwd
382
+ const proc = spawn(cmd, args, {
383
+ cwd: workdir,
357
384
  stdio: ['ignore', 'pipe', 'pipe'],
358
385
  env: spawnEnv,
359
386
  });
@@ -362,7 +389,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
362
389
  const STATUS_WRITE_INTERVAL = 3000;
363
390
  proc.stdout.on('data', (chunk) => {
364
391
  stdout += chunk.toString();
365
- logStream.write(chunk);
392
+ logWrite(chunk);
366
393
  const now = Date.now();
367
394
  if (now - lastStatusWrite > STATUS_WRITE_INTERVAL) {
368
395
  lastStatusWrite = now;
@@ -375,7 +402,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
375
402
  });
376
403
  proc.stderr.on('data', (chunk) => {
377
404
  stderr += chunk.toString();
378
- logStream.write(chunk);
405
+ logWrite(chunk);
379
406
  const now = Date.now();
380
407
  if (now - lastStatusWrite > STATUS_WRITE_INTERVAL) {
381
408
  lastStatusWrite = now;
@@ -412,8 +439,11 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
412
439
  clearTimeout(activeTimer);
413
440
  activeTimer = null;
414
441
  activeProc = null;
415
- logStream.write(`\n=== EXIT ${code} | ${new Date().toISOString()} ===\n`);
416
- logStream.end();
442
+ if (!logStreamEnded) {
443
+ logStreamEnded = true;
444
+ logStream.write(`\n=== EXIT ${code} | ${new Date().toISOString()} ===\n`);
445
+ logStream.end();
446
+ }
417
447
  if (cancelled || caTask.status === 'cancelled') {
418
448
  reject(new Error(CANCELLED_MESSAGE));
419
449
  return;
@@ -430,8 +460,11 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
430
460
  clearTimeout(activeTimer);
431
461
  activeTimer = null;
432
462
  activeProc = null;
433
- logStream.write(`\n=== ERROR: ${err.message} ===\n`);
434
- logStream.end();
463
+ if (!logStreamEnded) {
464
+ logStreamEnded = true;
465
+ logStream.write(`\n=== ERROR: ${err.message} ===\n`);
466
+ logStream.end();
467
+ }
435
468
  reject(err);
436
469
  });
437
470
  });
@@ -479,8 +512,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
479
512
  liveOutput: undefined,
480
513
  });
481
514
  writeCodeAgentTask(caTask);
482
- if (!options?.skipNotification)
483
- await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
515
+ await notifyCodeAgentResult(caTask);
484
516
  return;
485
517
  }
486
518
  // Post-validation gate
@@ -490,6 +522,25 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
490
522
  caTask.liveOutput = undefined;
491
523
  writeCodeAgentTask(caTask);
492
524
  const validationCmd = buildValidationCommand(workdir, options?.validationCommands);
525
+ if (!validationCmd) {
526
+ // No build/test scripts — skip validation, mark complete
527
+ const endedAt = new Date();
528
+ const duration = Math.round((endedAt.getTime() - startedAt.getTime()) / 1000);
529
+ addEvent(traceId, { type: 'validation', summary: 'Skipped (no build/test scripts found)', durationMs: Date.now() - startedAt.getTime() });
530
+ await endTrace(traceId, 'ok');
531
+ Object.assign(caTask, {
532
+ status: 'completed',
533
+ endedAt: endedAt.toISOString(),
534
+ durationSeconds: duration,
535
+ exitCode,
536
+ validationPassed: true,
537
+ validationOutput: undefined,
538
+ outputPreview: agentOutput.slice(0, 5000),
539
+ });
540
+ writeCodeAgentTask(caTask);
541
+ await notifyCodeAgentResult(caTask);
542
+ return;
543
+ }
493
544
  const runValidationPromise = () => new Promise((res) => {
494
545
  const validationProc = exec(validationCmd, {
495
546
  cwd: workdir,
@@ -531,15 +582,14 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
531
582
  agent,
532
583
  workdir,
533
584
  model: input.model,
585
+ effort: input.effort,
534
586
  max_turns: input.max_turns,
535
587
  });
536
588
  const retryExitCode = await new Promise((resolveRetry, rejectRetry) => {
537
- const spawnEnv = { ...process.env };
538
- delete spawnEnv.CLAUDECODE;
539
- const retrySpawnCmd = sandboxContainer ? getRuntime() : retryCmd;
540
- const retrySpawnArgs = sandboxContainer ? ['exec', sandboxContainer, retryCmd, ...retryArgs] : retryArgs;
541
- const retryProc = spawn(retrySpawnCmd, retrySpawnArgs, {
542
- cwd: sandboxContainer ? undefined : workdir,
589
+ const spawnEnv = buildCodeAgentSpawnEnv();
590
+ ensureNodeInPath(spawnEnv);
591
+ const retryProc = spawn(retryCmd, retryArgs, {
592
+ cwd: workdir,
543
593
  stdio: ['ignore', 'pipe', 'pipe'],
544
594
  env: spawnEnv,
545
595
  });
@@ -633,7 +683,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
633
683
  }
634
684
  }
635
685
  else {
636
- agentOutput = stdout || '(no output)';
686
+ agentOutput = parseCodexOutput(stdout);
637
687
  }
638
688
  caTask.status = 'validating';
639
689
  caTask.liveOutput = undefined;
@@ -659,8 +709,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
659
709
  error: 'Validation failed',
660
710
  });
661
711
  writeCodeAgentTask(caTask);
662
- if (!options?.skipNotification)
663
- await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
712
+ await notifyCodeAgentResult(caTask);
664
713
  return;
665
714
  }
666
715
  addEvent(traceId, { type: 'validation', summary: 'Build/test validation passed', durationMs: Date.now() - startedAt.getTime() });
@@ -675,8 +724,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
675
724
  outputPreview: agentOutput.slice(0, 5000),
676
725
  });
677
726
  writeCodeAgentTask(caTask);
678
- if (!options?.skipNotification)
679
- await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
727
+ await notifyCodeAgentResult(caTask);
680
728
  return;
681
729
  }
682
730
  // No validation — mark complete
@@ -691,8 +739,7 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
691
739
  liveOutput: undefined,
692
740
  });
693
741
  writeCodeAgentTask(caTask);
694
- if (!options?.skipNotification)
695
- await notifyCodeAgentResult(caTask, (id) => getCodeAgent(id) ?? null);
742
+ await notifyCodeAgentResult(caTask);
696
743
  }
697
744
  catch (err) {
698
745
  const errMsg = toErrorMessage(err);
@@ -710,8 +757,8 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
710
757
  liveOutput: undefined,
711
758
  });
712
759
  writeCodeAgentTask(caTask);
713
- if (!options?.skipNotification && caTask.status !== 'cancelled')
714
- await notifyCodeAgentResult(caTask, getCodeAgent);
760
+ if (caTask.status !== "cancelled")
761
+ await notifyCodeAgentResult(caTask);
715
762
  }
716
763
  finally {
717
764
  if (activeTimer)
@@ -719,6 +766,15 @@ export async function runCodeAgentBackground(id, agent, task, workdir, validate,
719
766
  activeTimer = null;
720
767
  activeProc = null;
721
768
  activeExecProc = null;
769
+ if (caTask.worktreePath && !['running', 'validating', 'pending'].includes(caTask.status)) {
770
+ caTask.worktreeCleanup = cleanupCodeAgentWorktree({
771
+ sourceWorkdir: caTask.sourceWorkdir,
772
+ worktreePath: caTask.worktreePath,
773
+ worktreeRef: caTask.worktreeRef,
774
+ config: options?.worktreeConfig,
775
+ });
776
+ writeCodeAgentTask(caTask);
777
+ }
722
778
  deleteCodeAgentCanceller(id);
723
779
  }
724
780
  }
@@ -1,11 +1,10 @@
1
1
  import type { ToolConfig } from '../types.js';
2
2
  import type { ExecuteToolContext } from '../tools/execute-context.js';
3
- export type { CodeAgentTask, DecomposedSubtask, CodeAgentBackgroundOptions, BuildCodeAgentArgsInput, ValidationResult, ChildResult, } from './types.js';
3
+ export type { CodeAgentTask, CodeAgentBackgroundOptions, BuildCodeAgentArgsInput, ValidationResult, } from './types.js';
4
4
  export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
5
5
  export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, getCodeAgentsDir, } from './registry.js';
6
6
  export { runCodeAgentBackground, runValidation } from './executor.js';
7
- export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
8
- export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
7
+ export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveCodeAgentModelLabel, resolveWorkdir, resolveModelAlias, } from './utils.js';
9
8
  export { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
10
9
  export type { ClaudeOutputResult } from './parser.js';
11
10
  /**
@@ -16,7 +15,3 @@ export declare function executeCheckCodeAgent(input: Record<string, any>): strin
16
15
  * Execute code_with_agent tool - single agent mode.
17
16
  */
18
17
  export declare function executeCodeWithAgent(input: Record<string, any>, config: ToolConfig, context?: ExecuteToolContext): Promise<string>;
19
- /**
20
- * Execute code_with_team tool - multi-agent team mode.
21
- */
22
- export declare function executeCodeWithTeam(input: Record<string, any>, config: ToolConfig, context?: ExecuteToolContext): Promise<string>;
@@ -1,25 +1,26 @@
1
1
  // Code Agents - Public API
2
2
  // Background multi-agent coding task execution
3
3
  import { resolve } from 'path';
4
+ import { randomUUID } from 'crypto';
4
5
  import { isPathAllowed } from '../tools/path-utils.js';
5
6
  import { getNextCodeAgentId, storeCodeAgentTask, writeCodeAgentTask, getActiveCodeAgents, getRecentCodeAgents, getCodeAgent, } from './registry.js';
6
7
  import { runCodeAgentBackground } from './executor.js';
7
- import { runTeamOrchestrator } from './orchestrator.js';
8
- import { resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, getCodingCliPreflightError, } from './utils.js';
8
+ import { addPendingSession } from './interactive-sessions.js';
9
+ import { resolveSelectedCodeAgent, resolveCodeAgentModelLabel, isModelCompatibleWithAgent, resolveWorkdir, resolveModelAlias, getCodingCliPreflightError, } from './utils.js';
10
+ import { normalizeWorktreeRequest, prepareCodeAgentWorktree, shouldUseCodeAgentWorktree, } from './worktrees.js';
9
11
  // Re-export timeout constants
10
12
  export { CODE_AGENT_TIMEOUT_MS, VALIDATE_TIMEOUT_MS } from './types.js';
11
13
  // Re-export registry functions
12
14
  export { getActiveCodeAgents, getRecentCodeAgents, getAllCodeAgents, getCodeAgent, cancelCodeAgent, restoreCodeAgentTasks, getCodeAgentsDir, } from './registry.js';
13
15
  // Re-export executor functions
14
16
  export { runCodeAgentBackground, runValidation } from './executor.js';
15
- // Re-export orchestrator functions
16
- export { runTeamOrchestrator, computeWaves, decomposeTask, synthesizeResults, gatherCodebaseContext, } from './orchestrator.js';
17
17
  // Re-export utility functions
18
- export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveWorkdir, resolveModelAlias, readTeamState, } from './utils.js';
18
+ export { setCodeAgentConfig, getCodeAgentConfig, buildCodeAgentArgs, resolveSelectedCodeAgent, resolveCodeAgentModelLabel, resolveWorkdir, resolveModelAlias, } from './utils.js';
19
19
  // Re-export parser functions
20
20
  export { parseStreamJsonForLive, parseClaudeOutput, parseCodexOutput } from './parser.js';
21
21
  // SKIMPYCLAW_ROOT for workdir default
22
22
  const SKIMPYCLAW_ROOT = resolve(import.meta.dirname || process.cwd(), '..', '..');
23
+ const EFFORT_LEVELS = new Set(['none', 'low', 'medium', 'high', 'xhigh']);
23
24
  /**
24
25
  * Execute check_code_agent tool — list all or get details for one agent.
25
26
  */
@@ -35,7 +36,12 @@ export function executeCheckCodeAgent(input) {
35
36
  status: task.status,
36
37
  task: task.task,
37
38
  workdir: task.workdir,
39
+ sourceWorkdir: task.sourceWorkdir,
40
+ worktreePath: task.worktreePath,
41
+ worktreeRef: task.worktreeRef,
38
42
  model: task.model,
43
+ modelLabel: task.modelLabel || resolveCodeAgentModelLabel(task.agent, task.model),
44
+ effort: task.effort,
39
45
  startedAt: task.startedAt,
40
46
  endedAt: task.endedAt,
41
47
  durationSeconds: task.durationSeconds,
@@ -58,7 +64,9 @@ export function executeCheckCodeAgent(input) {
58
64
  ? (t.durationSeconds < 60 ? `${t.durationSeconds}s` : `${Math.floor(t.durationSeconds / 60)}m`)
59
65
  : (Math.round((Date.now() - new Date(t.startedAt).getTime()) / 1000) + 's');
60
66
  const taskPreview = t.task.length > 60 ? t.task.slice(0, 60) + '...' : t.task;
61
- return `${t.id}: ${t.status.toUpperCase()} (${t.agent}, ${elapsed}) — ${taskPreview}`;
67
+ const model = `, ${t.modelLabel || resolveCodeAgentModelLabel(t.agent, t.model)}`;
68
+ const effort = t.effort ? `, effort ${t.effort}` : '';
69
+ return `${t.id}: ${t.status.toUpperCase()} (${t.agent}${model}${effort}, ${elapsed}) — ${taskPreview}`;
62
70
  });
63
71
  return lines.join('\n');
64
72
  }
@@ -70,7 +78,7 @@ export async function executeCodeWithAgent(input, config, context) {
70
78
  if (!task)
71
79
  return 'Error: task is required';
72
80
  // Resolve model alias first so agent auto-selection can inspect it.
73
- // Fall back to current session model so codex/kimi models auto-select the right CLI.
81
+ // Fall back to current session model so Codex models auto-select the right CLI.
74
82
  let rawModel = input.model;
75
83
  if (!rawModel) {
76
84
  try {
@@ -84,7 +92,42 @@ export async function executeCodeWithAgent(input, config, context) {
84
92
  const requestedAgent = input.agent;
85
93
  const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
86
94
  if (!agent) {
87
- return `Error: Invalid agent "${requestedAgent}". Must be claude, codex, or kimi.`;
95
+ return `Error: Invalid agent "${requestedAgent}". Must be claude or codex.`;
96
+ }
97
+ // Interactive mode prerequisites: Discord server channel (NOT DM) + claude only.
98
+ // Discord DMs do not support threads, which interactive mode requires.
99
+ const isInteractive = input.interactive === true;
100
+ if (isInteractive) {
101
+ if (context?.channel !== 'discord') {
102
+ return 'Error: interactive mode requires Discord. Telegram and other channels are not supported yet.';
103
+ }
104
+ if (context?.isDm === true) {
105
+ return 'Error: interactive mode requires a Discord server channel. Direct messages do not support threads. Please move to a server channel and try again.';
106
+ }
107
+ if (agent !== 'claude' && agent !== 'codex') {
108
+ return `Error: interactive mode supports claude and codex only (requested: ${agent}).`;
109
+ }
110
+ if (agent === 'codex') {
111
+ return 'Error: interactive mode for codex is not yet implemented. Use claude for now.';
112
+ }
113
+ }
114
+ // Don't pass a non-matching session model to a different agent CLI.
115
+ // e.g. if session is gpt-5.3-codex but agent is claude, let claude use its own default.
116
+ const isModelFromSession = !input.model;
117
+ const modelIncompatible = isModelFromSession && resolvedModel && !isModelCompatibleWithAgent(resolvedModel, agent);
118
+ const modelForAgent = modelIncompatible ? undefined : resolvedModel;
119
+ const effortInput = typeof input.effort === 'string'
120
+ ? input.effort
121
+ : typeof input.thinking === 'string'
122
+ ? input.thinking
123
+ : typeof input.reasoning_effort === 'string'
124
+ ? input.reasoning_effort
125
+ : undefined;
126
+ const effort = effortInput
127
+ ? effortInput.trim().toLowerCase().replace(/^x[-_ ]?high$/, 'xhigh')
128
+ : undefined;
129
+ if (effort && !EFFORT_LEVELS.has(effort)) {
130
+ return `Error: Invalid effort "${effortInput}". Use none, low, medium, high, or xhigh.`;
88
131
  }
89
132
  const projects = context?.fullConfig?.projects ?? {};
90
133
  const rawWorkdir = input.workdir;
@@ -112,96 +155,84 @@ export async function executeCodeWithAgent(input, config, context) {
112
155
  // Create task with unique ID
113
156
  const id = getNextCodeAgentId();
114
157
  const startedAt = new Date();
158
+ const worktreeRequest = normalizeWorktreeRequest(input.worktree);
159
+ const worktreeConfig = context?.fullConfig?.codeAgents?.worktrees;
160
+ const useWorktree = shouldUseCodeAgentWorktree(task, worktreeRequest, worktreeConfig);
161
+ const worktreeRequired = worktreeRequest === true || worktreeConfig?.mode === 'always';
162
+ let executionWorkdir = workdir;
163
+ let sourceWorkdir;
164
+ let worktreePath;
165
+ let worktreeRef;
166
+ let agentTask = task;
167
+ if (useWorktree) {
168
+ try {
169
+ const worktree = prepareCodeAgentWorktree({
170
+ id,
171
+ sourceWorkdir: workdir,
172
+ config: worktreeConfig,
173
+ required: worktreeRequired,
174
+ });
175
+ if (worktree) {
176
+ executionWorkdir = worktree.runWorkdir;
177
+ sourceWorkdir = worktree.sourceWorkdir;
178
+ worktreePath = worktree.worktreePath;
179
+ worktreeRef = worktree.worktreeRef;
180
+ agentTask = `${task}\n\nSkimpyClaw worktree isolation:\n- Source checkout: ${sourceWorkdir}\n- Isolated worktree: ${worktreePath}\n- Run all repository commands from the isolated worktree, not the source checkout.\n- If rebasing a branch that is already checked out elsewhere, create a temporary branch in this worktree and report before pushing.`;
181
+ }
182
+ }
183
+ catch (err) {
184
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
185
+ }
186
+ }
187
+ // Interactive mode: generate session UUID up-front so --session-id can pin it.
188
+ const cliSessionId = (isInteractive && agent === 'claude') ? randomUUID() : undefined;
115
189
  const caTask = {
116
190
  id,
117
191
  agent,
118
192
  task,
119
193
  status: 'running',
120
194
  chatId: context?.chatId,
195
+ discordThreadId: context?.discordThreadId,
196
+ discordChannelId: context?.discordChannelId,
121
197
  startedAt: startedAt.toISOString(),
122
- workdir,
123
- model: resolvedModel,
198
+ workdir: executionWorkdir,
199
+ sourceWorkdir,
200
+ worktreePath,
201
+ worktreeRef,
202
+ model: modelForAgent,
203
+ modelLabel: resolveCodeAgentModelLabel(agent, modelForAgent),
204
+ effort,
205
+ interactive: isInteractive || undefined,
206
+ cliSessionId,
124
207
  };
125
208
  storeCodeAgentTask(caTask);
126
209
  writeCodeAgentTask(caTask);
210
+ // Register a pending interactive session keyed by the task ID.
211
+ // The Discord thread-creation handler will call linkThread(taskId, threadId)
212
+ // to promote it into the threadId-keyed map so follow-up messages work.
213
+ if (isInteractive && cliSessionId) {
214
+ addPendingSession(id, {
215
+ cliSessionId,
216
+ cliAgent: agent,
217
+ status: 'active',
218
+ createdAt: startedAt.toISOString(),
219
+ lastActivityAt: startedAt.toISOString(),
220
+ initialTask: task,
221
+ });
222
+ }
127
223
  // Fire-and-forget: spawn background process
128
224
  const configTimeout = context?.fullConfig?.codeAgents?.timeoutMinutes ?? 30;
129
225
  const soloTimeout = Math.min(input.timeout_minutes || configTimeout, 60);
130
- const resolvedInput = { ...input, model: resolvedModel, timeout_minutes: soloTimeout };
131
- runCodeAgentBackground(id, agent, task, workdir, validate, resolvedInput, startedAt, {
226
+ const resolvedInput = { ...input, model: modelForAgent, effort, timeout_minutes: soloTimeout };
227
+ runCodeAgentBackground(id, agent, agentTask, executionWorkdir, validate, resolvedInput, startedAt, {
132
228
  defaultTimeoutMinutes: soloTimeout,
133
229
  maxTimeoutMinutes: 60,
134
230
  validationCommands: context?.fullConfig?.codeAgents?.validationCommands,
231
+ worktreeConfig,
135
232
  }).catch((err) => {
136
233
  console.error(`[code-agent] Background error for ${id}:`, err);
137
234
  });
138
235
  const taskPreview = task.length > 100 ? task.slice(0, 100) + '...' : task;
139
- return `Started coding agent ${id} (${agent}). Task: ${taskPreview}\n\nUse check_code_agent to poll status.`;
140
- }
141
- /**
142
- * Execute code_with_team tool - multi-agent team mode.
143
- */
144
- export async function executeCodeWithTeam(input, config, context) {
145
- const task = input.task;
146
- if (!task)
147
- return 'Error: task is required';
148
- // Resolve model alias. Only fall back to session model when an explicit model
149
- // was requested — otherwise the session model (e.g. gpt-5.3-codex) would
150
- // override agent selection even when the user wants claude.
151
- const rawTeamModel = input.model;
152
- const resolvedModel = rawTeamModel
153
- ? resolveModelAlias(rawTeamModel, context?.fullConfig?.models?.aliases)
154
- : undefined;
155
- const configDefault = context?.fullConfig?.codeAgents?.defaultAgent || 'claude';
156
- const requestedAgent = input.agent;
157
- const agent = resolveSelectedCodeAgent(requestedAgent, configDefault, resolvedModel);
158
- if (!agent) {
159
- return `Error: Invalid agent "${requestedAgent}". Must be claude, codex, or kimi.`;
160
- }
161
- const teamSize = Math.max(2, Math.min(5, input.team_size || 3));
162
- const projects = context?.fullConfig?.projects ?? {};
163
- const rawWorkdir = input.workdir;
164
- // Resolve project name → path
165
- const workdir = resolveWorkdir(rawWorkdir, projects, SKIMPYCLAW_ROOT);
166
- // Project paths are always allowed
167
- const projectPaths = Object.values(projects).map(p => resolve(p));
168
- const effectiveAllowedPaths = [...config.allowedPaths, ...projectPaths];
169
- if (!isPathAllowed(workdir, effectiveAllowedPaths)) {
170
- const projectNames = Object.keys(projects).length > 0
171
- ? ` (or project names: ${Object.keys(projects).join(', ')})`
172
- : '';
173
- return `Error: Working directory not allowed. Permitted: ${config.allowedPaths.join(', ')}${projectNames}`;
174
- }
175
- const validate = input.validate !== false;
176
- // Concurrency check — need room for teamSize children
177
- const maxConcurrent = context?.fullConfig?.codeAgents?.maxConcurrent ?? 5;
178
- const activeCount = getActiveCodeAgents().length;
179
- if (activeCount + teamSize > maxConcurrent) {
180
- return `Error: Concurrency limit — need ${teamSize} slots but only ${maxConcurrent - activeCount} available (${activeCount}/${maxConcurrent} running). Wait for agents to finish.`;
181
- }
182
- const cliPreflightError = getCodingCliPreflightError();
183
- if (cliPreflightError)
184
- return cliPreflightError;
185
- // Create parent task
186
- const id = getNextCodeAgentId();
187
- const startedAt = new Date();
188
- const caTask = {
189
- id,
190
- agent: 'team-coordinator',
191
- task,
192
- status: 'running',
193
- chatId: context?.chatId,
194
- startedAt: startedAt.toISOString(),
195
- workdir,
196
- model: resolvedModel,
197
- childTaskIds: [],
198
- };
199
- storeCodeAgentTask(caTask);
200
- writeCodeAgentTask(caTask);
201
- // Fire-and-forget: orchestrator decomposes, spawns children, monitors, synthesizes
202
- runTeamOrchestrator(id, task, teamSize, workdir, validate, agent, resolvedModel, startedAt, context).catch((err) => {
203
- console.error(`[code-team] Background error for ${id}:`, err);
204
- });
205
- const taskPreview = task.length > 100 ? task.slice(0, 100) + '...' : task;
206
- return `Started coding team ${id} (${teamSize} parallel ${agent} agents). Task: ${taskPreview}\n\nUse check_code_agent to poll status.`;
236
+ const worktreeLine = worktreePath ? `\nWorktree: ${worktreePath}` : '';
237
+ return `Started coding agent ${id} (${agent}). Task: ${taskPreview}${worktreeLine}\n\nUse check_code_agent to poll status.`;
207
238
  }
@@ -0,0 +1,6 @@
1
+ export interface ResumeOptions {
2
+ discordThreadId: string;
3
+ userMessage: string;
4
+ postToThread: (chunks: string[]) => Promise<void>;
5
+ }
6
+ export declare function handleInteractiveThreadMessage(opts: ResumeOptions): Promise<void>;