@the-open-engine/zeroshot 5.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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,381 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Attachable Watcher - spawns a CLI process with PTY for attach/detach support
5
+ * Runs detached from parent, provides Unix socket for attach clients.
6
+ */
7
+
8
+ import { appendFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
9
+ import { unlink } from 'fs/promises';
10
+ import { join } from 'path';
11
+ import { homedir } from 'os';
12
+ import { updateTask } from './store.js';
13
+ import {
14
+ detectProviderFatalError,
15
+ detectProviderStreamingModeError,
16
+ recoverProviderStructuredOutput,
17
+ supportsProviderStructuredOutputRecovery,
18
+ } from './provider-helper-runtime.js';
19
+ import { createRequire } from 'module';
20
+
21
+ // ═══════════════════════════════════════════════════════════════════════════
22
+ // 🔴 CRITICAL: Global error handlers - MUST be installed BEFORE any async ops
23
+ // Without these, uncaught errors cause SILENT process death (no logs, no status)
24
+ // ═══════════════════════════════════════════════════════════════════════════
25
+
26
+ const [, , taskIdArg, cwdArg, logFileArg, argsJsonArg, configJsonArg] = process.argv;
27
+ let commandSpecCleanup = [];
28
+ let cleanupStarted = false;
29
+
30
+ function emergencyLog(msg) {
31
+ if (logFileArg) {
32
+ try {
33
+ appendFileSync(logFileArg, msg);
34
+ } catch {
35
+ process.stderr.write(msg);
36
+ }
37
+ } else {
38
+ process.stderr.write(msg);
39
+ }
40
+ }
41
+
42
+ function crashWithError(error, source) {
43
+ const timestamp = Date.now();
44
+ const errorMsg = error instanceof Error ? error.stack || error.message : String(error);
45
+
46
+ emergencyLog(`\n[${timestamp}][CRASH] ${source}: ${errorMsg}\n`);
47
+ emergencyLog(`[${timestamp}][CRASH] Process terminating due to unhandled error\n`);
48
+ cleanupCommandSpecSync();
49
+
50
+ if (taskIdArg) {
51
+ try {
52
+ updateTask(taskIdArg, {
53
+ status: 'failed',
54
+ error: `${source}: ${errorMsg}`,
55
+ socketPath: null,
56
+ });
57
+ } catch (updateError) {
58
+ emergencyLog(`[${timestamp}][CRASH] Failed to update task status: ${updateError.message}\n`);
59
+ }
60
+ }
61
+
62
+ process.exit(1);
63
+ }
64
+
65
+ process.on('uncaughtException', (error) => {
66
+ crashWithError(error, 'uncaughtException');
67
+ });
68
+
69
+ process.on('unhandledRejection', (reason) => {
70
+ crashWithError(reason, 'unhandledRejection');
71
+ });
72
+
73
+ const require = createRequire(import.meta.url);
74
+ const { AttachServer } = require('../src/attach');
75
+ const { normalizeProviderName } = require('../lib/provider-names');
76
+
77
+ const taskId = taskIdArg;
78
+ const cwd = cwdArg;
79
+ const logFile = logFileArg;
80
+ const args = JSON.parse(argsJsonArg);
81
+ const config = configJsonArg ? JSON.parse(configJsonArg) : {};
82
+ const commandSpec = config.commandSpec || {
83
+ binary: config.command || 'claude',
84
+ args,
85
+ env: config.env || {},
86
+ cleanup: [],
87
+ };
88
+ commandSpecCleanup = commandSpec.cleanup || [];
89
+ let server = null;
90
+
91
+ const SOCKET_DIR = join(homedir(), '.zeroshot', 'sockets');
92
+ const socketPath = join(SOCKET_DIR, `${taskId}.sock`);
93
+
94
+ if (!existsSync(SOCKET_DIR)) {
95
+ mkdirSync(SOCKET_DIR, { recursive: true });
96
+ }
97
+
98
+ function log(msg) {
99
+ appendFileSync(logFile, msg);
100
+ }
101
+
102
+ const providerName = normalizeProviderName(config.provider || 'claude');
103
+ const enableRecovery = supportsProviderStructuredOutputRecovery(providerName);
104
+
105
+ const env = { ...process.env, ...(commandSpec.env || {}) };
106
+ const command = commandSpec.binary;
107
+ const finalArgs = [...(commandSpec.args || args)];
108
+
109
+ const silentJsonMode =
110
+ config.outputFormat === 'json' &&
111
+ config.jsonSchema &&
112
+ config.silentJsonOutput &&
113
+ supportsProviderStructuredOutputRecovery(providerName);
114
+
115
+ let finalResultJson = null;
116
+ let outputBuffer = '';
117
+ let streamingModeError = null;
118
+ let fatalError = null;
119
+
120
+ function splitBufferLines(buffer, chunk) {
121
+ const nextBuffer = buffer + chunk;
122
+ const lines = nextBuffer.split('\n');
123
+ const remaining = lines.pop() || '';
124
+ return { lines, remaining };
125
+ }
126
+
127
+ function maybeHandleFatalError(line, timestamp) {
128
+ if (fatalError) {
129
+ return false;
130
+ }
131
+
132
+ const detected = detectProviderFatalError(providerName, line);
133
+ if (!detected) {
134
+ return false;
135
+ }
136
+
137
+ fatalError = detected;
138
+
139
+ if (silentJsonMode) {
140
+ log(`[${timestamp}]${line}\n`);
141
+ }
142
+ log(`[${timestamp}][FATAL] ${detected}\n`);
143
+
144
+ if (server) {
145
+ server.stop('SIGTERM').catch((error) => {
146
+ log(`[${timestamp}][FATAL] Attach server stop failed: ${error.message}\n`);
147
+ });
148
+ }
149
+ return true;
150
+ }
151
+
152
+ function captureStreamingError(line, timestamp) {
153
+ const detectedError = detectProviderStreamingModeError(providerName, line);
154
+ if (!detectedError) {
155
+ return false;
156
+ }
157
+
158
+ streamingModeError = { ...detectedError, timestamp };
159
+ return true;
160
+ }
161
+
162
+ function maybeCaptureStructuredOutput(line) {
163
+ try {
164
+ const json = JSON.parse(line);
165
+ if (json.structured_output) {
166
+ finalResultJson = line;
167
+ }
168
+ } catch {
169
+ // Not JSON, skip
170
+ }
171
+ }
172
+
173
+ function handleSilentJsonLines(lines, timestamp) {
174
+ for (const line of lines) {
175
+ if (!line.trim()) continue;
176
+ maybeHandleFatalError(line, timestamp);
177
+ if (captureStreamingError(line, timestamp)) {
178
+ continue;
179
+ }
180
+ maybeCaptureStructuredOutput(line);
181
+ }
182
+ }
183
+
184
+ function handleStreamingLines(lines, timestamp) {
185
+ for (const line of lines) {
186
+ maybeHandleFatalError(line, timestamp);
187
+ if (captureStreamingError(line, timestamp)) {
188
+ continue;
189
+ }
190
+ log(`[${timestamp}]${line}\n`);
191
+ }
192
+ }
193
+
194
+ function flushOutputBuffer(timestamp) {
195
+ if (!outputBuffer.trim()) {
196
+ return;
197
+ }
198
+
199
+ if (!enableRecovery) {
200
+ if (!silentJsonMode) {
201
+ log(`[${timestamp}]${outputBuffer}\n`);
202
+ }
203
+ return;
204
+ }
205
+
206
+ maybeHandleFatalError(outputBuffer, timestamp);
207
+ if (captureStreamingError(outputBuffer, timestamp)) {
208
+ return;
209
+ }
210
+
211
+ if (silentJsonMode) {
212
+ maybeCaptureStructuredOutput(outputBuffer);
213
+ return;
214
+ }
215
+
216
+ log(`[${timestamp}]${outputBuffer}\n`);
217
+ }
218
+
219
+ function attemptRecovery(code, timestamp) {
220
+ if (!(code !== 0 && streamingModeError?.sessionId)) {
221
+ return null;
222
+ }
223
+
224
+ const recovered = recoverProviderStructuredOutput(providerName, streamingModeError.sessionId);
225
+ if (recovered?.payload) {
226
+ const recoveredLine = JSON.stringify(recovered.payload);
227
+ if (silentJsonMode) {
228
+ finalResultJson = recoveredLine;
229
+ } else {
230
+ log(`[${timestamp}]${recoveredLine}\n`);
231
+ }
232
+ } else if (streamingModeError.line) {
233
+ if (silentJsonMode) {
234
+ log(streamingModeError.line + '\n');
235
+ } else {
236
+ log(`[${streamingModeError.timestamp}]${streamingModeError.line}\n`);
237
+ }
238
+ }
239
+
240
+ return recovered;
241
+ }
242
+
243
+ async function cleanupCommandSpec() {
244
+ if (cleanupStarted) return;
245
+ cleanupStarted = true;
246
+ for (const file of commandSpecCleanup) {
247
+ try {
248
+ await unlink(file);
249
+ } catch (error) {
250
+ log(`[${Date.now()}][CLEANUP] Failed to delete ${file}: ${error.message}\n`);
251
+ }
252
+ }
253
+ }
254
+
255
+ function cleanupCommandSpecSync() {
256
+ if (cleanupStarted) return;
257
+ cleanupStarted = true;
258
+ for (const file of commandSpecCleanup) {
259
+ try {
260
+ unlinkSync(file);
261
+ } catch (error) {
262
+ emergencyLog(`[${Date.now()}][CLEANUP] Failed to delete ${file}: ${error.message}\n`);
263
+ }
264
+ }
265
+ }
266
+
267
+ function writeCompletionFooter(code, signal) {
268
+ if (config.outputFormat === 'json') {
269
+ return;
270
+ }
271
+
272
+ log(`\n${'='.repeat(50)}\n`);
273
+ log(`Finished: ${new Date().toISOString()}\n`);
274
+ log(`Exit code: ${code}, Signal: ${signal}\n`);
275
+ }
276
+
277
+ server = new AttachServer({
278
+ id: taskId,
279
+ socketPath,
280
+ command,
281
+ args: finalArgs,
282
+ cwd: commandSpec.cwd || cwd,
283
+ env,
284
+ cols: 120,
285
+ rows: 30,
286
+ });
287
+
288
+ server.on('output', (data) => {
289
+ const chunk = data.toString();
290
+ const timestamp = Date.now();
291
+
292
+ const { lines, remaining } = splitBufferLines(outputBuffer, chunk);
293
+ outputBuffer = remaining;
294
+
295
+ if (silentJsonMode) {
296
+ handleSilentJsonLines(lines, timestamp);
297
+ } else {
298
+ handleStreamingLines(lines, timestamp);
299
+ }
300
+ });
301
+
302
+ server.on('exit', async ({ exitCode, signal }) => {
303
+ const timestamp = Date.now();
304
+ const code = exitCode;
305
+
306
+ flushOutputBuffer(timestamp);
307
+
308
+ const recovered = attemptRecovery(code, timestamp);
309
+
310
+ if (silentJsonMode && finalResultJson) {
311
+ log(finalResultJson + '\n');
312
+ }
313
+
314
+ writeCompletionFooter(code, signal);
315
+ await cleanupCommandSpec();
316
+
317
+ const resolvedCode = fatalError ? 1 : recovered?.payload ? 0 : code;
318
+ const status = resolvedCode === 0 ? 'completed' : 'failed';
319
+ try {
320
+ await updateTask(taskId, {
321
+ status,
322
+ exitCode: resolvedCode,
323
+ error: fatalError || (resolvedCode !== 0 && signal ? `Killed by ${signal}` : null),
324
+ socketPath: null,
325
+ });
326
+ } catch (updateError) {
327
+ log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
328
+ }
329
+
330
+ setTimeout(() => {
331
+ process.exit(0);
332
+ }, 500);
333
+ });
334
+
335
+ server.on('error', async (err) => {
336
+ log(`\nError: ${err.message}\n`);
337
+ await cleanupCommandSpec();
338
+ try {
339
+ await updateTask(taskId, { status: 'failed', error: err.message });
340
+ } catch (updateError) {
341
+ log(`[${Date.now()}][ERROR] Failed to update task status: ${updateError.message}\n`);
342
+ }
343
+ process.exit(1);
344
+ });
345
+
346
+ server.on('clientAttach', ({ clientId }) => {
347
+ log(`[${Date.now()}][ATTACH] Client attached: ${clientId.slice(0, 8)}...\n`);
348
+ });
349
+
350
+ server.on('clientDetach', ({ clientId }) => {
351
+ log(`[${Date.now()}][DETACH] Client detached: ${clientId.slice(0, 8)}...\n`);
352
+ });
353
+
354
+ try {
355
+ await server.start();
356
+
357
+ updateTask(taskId, {
358
+ pid: server.pid,
359
+ socketPath,
360
+ attachable: true,
361
+ });
362
+
363
+ log(`[${Date.now()}][SYSTEM] Started with PTY (attachable)\n`);
364
+ log(`[${Date.now()}][SYSTEM] Socket: ${socketPath}\n`);
365
+ log(`[${Date.now()}][SYSTEM] PID: ${server.pid}\n`);
366
+ } catch (err) {
367
+ log(`\nFailed to start: ${err.message}\n`);
368
+ await cleanupCommandSpec();
369
+ updateTask(taskId, { status: 'failed', error: err.message });
370
+ process.exit(1);
371
+ }
372
+
373
+ process.on('SIGTERM', async () => {
374
+ log(`[${Date.now()}][SYSTEM] Received SIGTERM, stopping...\n`);
375
+ await server.stop('SIGTERM');
376
+ });
377
+
378
+ process.on('SIGINT', async () => {
379
+ log(`[${Date.now()}][SYSTEM] Received SIGINT, stopping...\n`);
380
+ await server.stop('SIGINT');
381
+ });
@@ -0,0 +1,50 @@
1
+ import { unlinkSync, existsSync } from 'fs';
2
+ import chalk from 'chalk';
3
+ import { loadTasks, saveTasks } from '../store.js';
4
+
5
+ export function cleanTasks(options = {}) {
6
+ const tasks = loadTasks();
7
+ const taskList = Object.values(tasks);
8
+
9
+ if (taskList.length === 0) {
10
+ console.log(chalk.dim('No tasks to clean.'));
11
+ return;
12
+ }
13
+
14
+ const toRemove = [];
15
+
16
+ for (const task of taskList) {
17
+ const shouldRemove =
18
+ options.all ||
19
+ (options.completed && task.status === 'completed') ||
20
+ (options.failed &&
21
+ (task.status === 'failed' || task.status === 'stale' || task.status === 'killed'));
22
+
23
+ if (shouldRemove) {
24
+ toRemove.push(task);
25
+ }
26
+ }
27
+
28
+ if (toRemove.length === 0) {
29
+ console.log(chalk.dim('No tasks match the criteria.'));
30
+ return;
31
+ }
32
+
33
+ console.log(chalk.dim(`Removing ${toRemove.length} task(s)...\n`));
34
+
35
+ for (const task of toRemove) {
36
+ // Delete log file
37
+ if (task.logFile && existsSync(task.logFile)) {
38
+ unlinkSync(task.logFile);
39
+ }
40
+
41
+ // Remove from tasks
42
+ delete tasks[task.id];
43
+
44
+ console.log(chalk.dim(` Removed: ${task.id} [${task.status}]`));
45
+ }
46
+
47
+ saveTasks(tasks);
48
+
49
+ console.log(chalk.green(`\n✓ Cleaned ${toRemove.length} task(s)`));
50
+ }
@@ -0,0 +1,105 @@
1
+ import chalk from 'chalk';
2
+ import { loadTasks } from '../store.js';
3
+ import { isProcessRunning } from '../runner.js';
4
+
5
+ export function listEpisodes(options = {}) {
6
+ const tasks = loadTasks();
7
+ const taskList = Object.values(tasks);
8
+
9
+ if (taskList.length === 0) {
10
+ console.log(chalk.dim('No episodes found.'));
11
+ return;
12
+ }
13
+
14
+ // Sort by creation date, oldest first (chronological order)
15
+ taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
16
+
17
+ // Filter by status if specified
18
+ let filtered = taskList;
19
+ if (options.status) {
20
+ filtered = taskList.filter((t) => t.status === options.status);
21
+ }
22
+
23
+ // Limit results
24
+ const limit = options.limit || 20;
25
+ filtered = filtered.slice(0, limit);
26
+
27
+ // Table format (default) or verbose format
28
+ if (options.verbose) {
29
+ // Verbose format (old behavior)
30
+ console.log(chalk.bold(`\nEpisodes (${filtered.length}/${taskList.length})\n`));
31
+
32
+ for (const task of filtered) {
33
+ // Verify running status
34
+ let status = task.status;
35
+ if (status === 'running' && !isProcessRunning(task.pid)) {
36
+ status = 'stale';
37
+ }
38
+
39
+ const statusColor =
40
+ {
41
+ running: chalk.green,
42
+ completed: chalk.green,
43
+ failed: chalk.red,
44
+ stale: chalk.yellow,
45
+ }[status] || chalk.dim;
46
+
47
+ const age = getAge(task.createdAt);
48
+ const timestamp = new Date(task.createdAt).toLocaleString();
49
+
50
+ console.log(
51
+ `${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
52
+ );
53
+ console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
54
+ console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
55
+ if (task.pid && status === 'running') {
56
+ console.log(` ${chalk.dim('PID:')} ${task.pid}`);
57
+ }
58
+ if (task.error) {
59
+ console.log(` ${chalk.red('Error:')} ${task.error}`);
60
+ }
61
+ console.log();
62
+ }
63
+ } else {
64
+ // Table format (clean, default)
65
+ console.log(chalk.bold(`\n=== Episodes (${filtered.length}/${taskList.length}) ===`));
66
+ console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
67
+ console.log('-'.repeat(100));
68
+
69
+ for (const task of filtered) {
70
+ // Verify running status
71
+ let status = task.status;
72
+ if (status === 'running' && !isProcessRunning(task.pid)) {
73
+ status = 'stale';
74
+ }
75
+
76
+ const statusColor =
77
+ {
78
+ running: chalk.green,
79
+ completed: chalk.green,
80
+ failed: chalk.red,
81
+ stale: chalk.yellow,
82
+ }[status] || chalk.dim;
83
+
84
+ const age = getAge(task.createdAt);
85
+ const cwd = task.cwd.replace(process.env.HOME, '~');
86
+
87
+ console.log(
88
+ `${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
89
+ );
90
+ }
91
+ console.log();
92
+ }
93
+ }
94
+
95
+ function getAge(dateStr) {
96
+ const diff = Date.now() - new Date(dateStr).getTime();
97
+ const mins = Math.floor(diff / 60000);
98
+ const hours = Math.floor(mins / 60);
99
+ const days = Math.floor(hours / 24);
100
+
101
+ if (days > 0) return `${days}d ago`;
102
+ if (hours > 0) return `${hours}h ago`;
103
+ if (mins > 0) return `${mins}m ago`;
104
+ return 'just now';
105
+ }
@@ -0,0 +1,23 @@
1
+ import { getTask } from '../store.js';
2
+
3
+ /**
4
+ * Get log file path for a task (machine-readable output)
5
+ * Used by cluster/agent-wrapper.js to follow task logs
6
+ * @param {string} taskId - Task ID
7
+ */
8
+ export function getLogPath(taskId) {
9
+ const task = getTask(taskId);
10
+
11
+ if (!task) {
12
+ console.error(`Task not found: ${taskId}`);
13
+ process.exit(1);
14
+ }
15
+
16
+ if (!task.logFile) {
17
+ console.error(`No log file for task: ${taskId}`);
18
+ process.exit(1);
19
+ }
20
+
21
+ // Output just the path (machine-readable)
22
+ console.log(task.logFile);
23
+ }
@@ -0,0 +1,32 @@
1
+ import chalk from 'chalk';
2
+ import { getTask, updateTask } from '../store.js';
3
+ import { killTask as killProcess, isProcessRunning } from '../runner.js';
4
+
5
+ export function killTaskCommand(taskId) {
6
+ const task = getTask(taskId);
7
+
8
+ if (!task) {
9
+ console.log(chalk.red(`Task not found: ${taskId}`));
10
+ process.exit(1);
11
+ }
12
+
13
+ if (task.status !== 'running') {
14
+ console.log(chalk.yellow(`Task is not running (status: ${task.status})`));
15
+ return;
16
+ }
17
+
18
+ if (!isProcessRunning(task.pid)) {
19
+ console.log(chalk.yellow('Process already dead, updating status...'));
20
+ updateTask(taskId, { status: 'stale', error: 'Process died unexpectedly' });
21
+ return;
22
+ }
23
+
24
+ const killed = killProcess(task.pid);
25
+
26
+ if (killed) {
27
+ console.log(chalk.green(`✓ Sent SIGTERM to task ${taskId} (PID: ${task.pid})`));
28
+ updateTask(taskId, { status: 'killed', error: 'Killed by user' });
29
+ } else {
30
+ console.log(chalk.red(`Failed to kill task ${taskId}`));
31
+ }
32
+ }
@@ -0,0 +1,105 @@
1
+ import chalk from 'chalk';
2
+ import { loadTasks } from '../store.js';
3
+ import { isProcessRunning } from '../runner.js';
4
+
5
+ export function listTasks(options = {}) {
6
+ const tasks = loadTasks();
7
+ const taskList = Object.values(tasks);
8
+
9
+ if (taskList.length === 0) {
10
+ console.log(chalk.dim('No tasks found.'));
11
+ return;
12
+ }
13
+
14
+ // Sort by creation date, oldest first (chronological)
15
+ taskList.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
16
+
17
+ // Filter by status if specified
18
+ let filtered = taskList;
19
+ if (options.status) {
20
+ filtered = taskList.filter((t) => t.status === options.status);
21
+ }
22
+
23
+ // Limit results
24
+ const limit = options.limit || 20;
25
+ filtered = filtered.slice(0, limit);
26
+
27
+ // Table format (default) or verbose format
28
+ if (options.verbose) {
29
+ // Verbose format (old behavior)
30
+ console.log(chalk.bold(`\nTasks (${filtered.length}/${taskList.length})\n`));
31
+
32
+ for (const task of filtered) {
33
+ // Verify running status
34
+ let status = task.status;
35
+ if (status === 'running' && !isProcessRunning(task.pid)) {
36
+ status = 'stale';
37
+ }
38
+
39
+ const statusColor =
40
+ {
41
+ running: chalk.green,
42
+ completed: chalk.green,
43
+ failed: chalk.red,
44
+ stale: chalk.yellow,
45
+ }[status] || chalk.dim;
46
+
47
+ const age = getAge(task.createdAt);
48
+ const timestamp = new Date(task.createdAt).toLocaleString();
49
+
50
+ console.log(
51
+ `${statusColor('●')} ${chalk.cyan(task.id)} ${statusColor(`[${status}]`)} ${chalk.dim(age + ' • ' + timestamp)}`
52
+ );
53
+ console.log(` ${chalk.dim('CWD:')} ${task.cwd}`);
54
+ console.log(` ${chalk.dim('Prompt:')} ${task.prompt}`);
55
+ if (task.pid && status === 'running') {
56
+ console.log(` ${chalk.dim('PID:')} ${task.pid}`);
57
+ }
58
+ if (task.error) {
59
+ console.log(` ${chalk.red('Error:')} ${task.error}`);
60
+ }
61
+ console.log();
62
+ }
63
+ } else {
64
+ // Table format (clean, default)
65
+ console.log(chalk.bold(`\n=== Tasks (${filtered.length}/${taskList.length}) ===`));
66
+ console.log(`${'ID'.padEnd(25)} ${'Status'.padEnd(12)} ${'Age'.padEnd(10)} CWD`);
67
+ console.log('-'.repeat(100));
68
+
69
+ for (const task of filtered) {
70
+ // Verify running status
71
+ let status = task.status;
72
+ if (status === 'running' && !isProcessRunning(task.pid)) {
73
+ status = 'stale';
74
+ }
75
+
76
+ const statusColor =
77
+ {
78
+ running: chalk.green,
79
+ completed: chalk.green,
80
+ failed: chalk.red,
81
+ stale: chalk.yellow,
82
+ }[status] || chalk.dim;
83
+
84
+ const age = getAge(task.createdAt);
85
+ const cwd = task.cwd.replace(process.env.HOME, '~');
86
+
87
+ console.log(
88
+ `${chalk.cyan(task.id.padEnd(25))} ${statusColor(status.padEnd(12))} ${chalk.dim(age.padEnd(10))} ${chalk.dim(cwd)}`
89
+ );
90
+ }
91
+ console.log();
92
+ }
93
+ }
94
+
95
+ function getAge(dateStr) {
96
+ const diff = Date.now() - new Date(dateStr).getTime();
97
+ const mins = Math.floor(diff / 60000);
98
+ const hours = Math.floor(mins / 60);
99
+ const days = Math.floor(hours / 24);
100
+
101
+ if (days > 0) return `${days}d ago`;
102
+ if (hours > 0) return `${hours}h ago`;
103
+ if (mins > 0) return `${mins}m ago`;
104
+ return 'just now';
105
+ }