aicodeman 0.2.8

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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +403 -0
  3. package/dist/ai-checker-base.d.ts +175 -0
  4. package/dist/ai-checker-base.d.ts.map +1 -0
  5. package/dist/ai-checker-base.js +424 -0
  6. package/dist/ai-checker-base.js.map +1 -0
  7. package/dist/ai-idle-checker.d.ts +53 -0
  8. package/dist/ai-idle-checker.d.ts.map +1 -0
  9. package/dist/ai-idle-checker.js +141 -0
  10. package/dist/ai-idle-checker.js.map +1 -0
  11. package/dist/ai-plan-checker.d.ts +52 -0
  12. package/dist/ai-plan-checker.d.ts.map +1 -0
  13. package/dist/ai-plan-checker.js +103 -0
  14. package/dist/ai-plan-checker.js.map +1 -0
  15. package/dist/bash-tool-parser.d.ts +191 -0
  16. package/dist/bash-tool-parser.d.ts.map +1 -0
  17. package/dist/bash-tool-parser.js +598 -0
  18. package/dist/bash-tool-parser.js.map +1 -0
  19. package/dist/cli.d.ts +12 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +460 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config/buffer-limits.d.ts +59 -0
  24. package/dist/config/buffer-limits.d.ts.map +1 -0
  25. package/dist/config/buffer-limits.js +74 -0
  26. package/dist/config/buffer-limits.js.map +1 -0
  27. package/dist/config/map-limits.d.ts +40 -0
  28. package/dist/config/map-limits.d.ts.map +1 -0
  29. package/dist/config/map-limits.js +52 -0
  30. package/dist/config/map-limits.js.map +1 -0
  31. package/dist/file-stream-manager.d.ts +148 -0
  32. package/dist/file-stream-manager.d.ts.map +1 -0
  33. package/dist/file-stream-manager.js +351 -0
  34. package/dist/file-stream-manager.js.map +1 -0
  35. package/dist/hooks-config.d.ts +31 -0
  36. package/dist/hooks-config.d.ts.map +1 -0
  37. package/dist/hooks-config.js +115 -0
  38. package/dist/hooks-config.js.map +1 -0
  39. package/dist/image-watcher.d.ts +86 -0
  40. package/dist/image-watcher.d.ts.map +1 -0
  41. package/dist/image-watcher.js +275 -0
  42. package/dist/image-watcher.js.map +1 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +54 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/mux-factory.d.ts +13 -0
  48. package/dist/mux-factory.d.ts.map +1 -0
  49. package/dist/mux-factory.js +19 -0
  50. package/dist/mux-factory.js.map +1 -0
  51. package/dist/mux-interface.d.ts +145 -0
  52. package/dist/mux-interface.d.ts.map +1 -0
  53. package/dist/mux-interface.js +9 -0
  54. package/dist/mux-interface.js.map +1 -0
  55. package/dist/plan-orchestrator.d.ts +123 -0
  56. package/dist/plan-orchestrator.d.ts.map +1 -0
  57. package/dist/plan-orchestrator.js +500 -0
  58. package/dist/plan-orchestrator.js.map +1 -0
  59. package/dist/prompts/index.d.ts +9 -0
  60. package/dist/prompts/index.d.ts.map +1 -0
  61. package/dist/prompts/index.js +9 -0
  62. package/dist/prompts/index.js.map +1 -0
  63. package/dist/prompts/planner.d.ts +14 -0
  64. package/dist/prompts/planner.d.ts.map +1 -0
  65. package/dist/prompts/planner.js +83 -0
  66. package/dist/prompts/planner.js.map +1 -0
  67. package/dist/prompts/research-agent.d.ts +10 -0
  68. package/dist/prompts/research-agent.d.ts.map +1 -0
  69. package/dist/prompts/research-agent.js +143 -0
  70. package/dist/prompts/research-agent.js.map +1 -0
  71. package/dist/push-store.d.ts +41 -0
  72. package/dist/push-store.d.ts.map +1 -0
  73. package/dist/push-store.js +168 -0
  74. package/dist/push-store.js.map +1 -0
  75. package/dist/ralph-config.d.ts +67 -0
  76. package/dist/ralph-config.d.ts.map +1 -0
  77. package/dist/ralph-config.js +134 -0
  78. package/dist/ralph-config.js.map +1 -0
  79. package/dist/ralph-loop.d.ts +124 -0
  80. package/dist/ralph-loop.d.ts.map +1 -0
  81. package/dist/ralph-loop.js +418 -0
  82. package/dist/ralph-loop.js.map +1 -0
  83. package/dist/ralph-tracker.d.ts +1081 -0
  84. package/dist/ralph-tracker.d.ts.map +1 -0
  85. package/dist/ralph-tracker.js +3343 -0
  86. package/dist/ralph-tracker.js.map +1 -0
  87. package/dist/respawn-controller.d.ts +1182 -0
  88. package/dist/respawn-controller.d.ts.map +1 -0
  89. package/dist/respawn-controller.js +2754 -0
  90. package/dist/respawn-controller.js.map +1 -0
  91. package/dist/run-summary.d.ts +123 -0
  92. package/dist/run-summary.d.ts.map +1 -0
  93. package/dist/run-summary.js +325 -0
  94. package/dist/run-summary.js.map +1 -0
  95. package/dist/session-lifecycle-log.d.ts +36 -0
  96. package/dist/session-lifecycle-log.d.ts.map +1 -0
  97. package/dist/session-lifecycle-log.js +101 -0
  98. package/dist/session-lifecycle-log.js.map +1 -0
  99. package/dist/session-manager.d.ts +97 -0
  100. package/dist/session-manager.d.ts.map +1 -0
  101. package/dist/session-manager.js +224 -0
  102. package/dist/session-manager.js.map +1 -0
  103. package/dist/session.d.ts +686 -0
  104. package/dist/session.d.ts.map +1 -0
  105. package/dist/session.js +2025 -0
  106. package/dist/session.js.map +1 -0
  107. package/dist/state-store.d.ts +189 -0
  108. package/dist/state-store.d.ts.map +1 -0
  109. package/dist/state-store.js +730 -0
  110. package/dist/state-store.js.map +1 -0
  111. package/dist/subagent-watcher.d.ts +345 -0
  112. package/dist/subagent-watcher.d.ts.map +1 -0
  113. package/dist/subagent-watcher.js +1469 -0
  114. package/dist/subagent-watcher.js.map +1 -0
  115. package/dist/task-queue.d.ts +108 -0
  116. package/dist/task-queue.d.ts.map +1 -0
  117. package/dist/task-queue.js +235 -0
  118. package/dist/task-queue.js.map +1 -0
  119. package/dist/task-tracker.d.ts +306 -0
  120. package/dist/task-tracker.d.ts.map +1 -0
  121. package/dist/task-tracker.js +488 -0
  122. package/dist/task-tracker.js.map +1 -0
  123. package/dist/task.d.ts +73 -0
  124. package/dist/task.d.ts.map +1 -0
  125. package/dist/task.js +177 -0
  126. package/dist/task.js.map +1 -0
  127. package/dist/team-watcher.d.ts +53 -0
  128. package/dist/team-watcher.d.ts.map +1 -0
  129. package/dist/team-watcher.js +313 -0
  130. package/dist/team-watcher.js.map +1 -0
  131. package/dist/templates/case-template.md +461 -0
  132. package/dist/templates/claude-md.d.ts +26 -0
  133. package/dist/templates/claude-md.d.ts.map +1 -0
  134. package/dist/templates/claude-md.js +74 -0
  135. package/dist/templates/claude-md.js.map +1 -0
  136. package/dist/tmux-manager.d.ts +181 -0
  137. package/dist/tmux-manager.d.ts.map +1 -0
  138. package/dist/tmux-manager.js +1405 -0
  139. package/dist/tmux-manager.js.map +1 -0
  140. package/dist/transcript-watcher.d.ts +110 -0
  141. package/dist/transcript-watcher.d.ts.map +1 -0
  142. package/dist/transcript-watcher.js +338 -0
  143. package/dist/transcript-watcher.js.map +1 -0
  144. package/dist/tunnel-manager.d.ts +54 -0
  145. package/dist/tunnel-manager.d.ts.map +1 -0
  146. package/dist/tunnel-manager.js +251 -0
  147. package/dist/tunnel-manager.js.map +1 -0
  148. package/dist/types.d.ts +1139 -0
  149. package/dist/types.d.ts.map +1 -0
  150. package/dist/types.js +215 -0
  151. package/dist/types.js.map +1 -0
  152. package/dist/utils/buffer-accumulator.d.ts +111 -0
  153. package/dist/utils/buffer-accumulator.d.ts.map +1 -0
  154. package/dist/utils/buffer-accumulator.js +172 -0
  155. package/dist/utils/buffer-accumulator.js.map +1 -0
  156. package/dist/utils/claude-cli-resolver.d.ts +26 -0
  157. package/dist/utils/claude-cli-resolver.d.ts.map +1 -0
  158. package/dist/utils/claude-cli-resolver.js +78 -0
  159. package/dist/utils/claude-cli-resolver.js.map +1 -0
  160. package/dist/utils/cleanup-manager.d.ts +165 -0
  161. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  162. package/dist/utils/cleanup-manager.js +274 -0
  163. package/dist/utils/cleanup-manager.js.map +1 -0
  164. package/dist/utils/index.d.ts +19 -0
  165. package/dist/utils/index.d.ts.map +1 -0
  166. package/dist/utils/index.js +19 -0
  167. package/dist/utils/index.js.map +1 -0
  168. package/dist/utils/lru-map.d.ts +140 -0
  169. package/dist/utils/lru-map.d.ts.map +1 -0
  170. package/dist/utils/lru-map.js +234 -0
  171. package/dist/utils/lru-map.js.map +1 -0
  172. package/dist/utils/nice-wrapper.d.ts +13 -0
  173. package/dist/utils/nice-wrapper.d.ts.map +1 -0
  174. package/dist/utils/nice-wrapper.js +17 -0
  175. package/dist/utils/nice-wrapper.js.map +1 -0
  176. package/dist/utils/opencode-cli-resolver.d.ts +21 -0
  177. package/dist/utils/opencode-cli-resolver.d.ts.map +1 -0
  178. package/dist/utils/opencode-cli-resolver.js +67 -0
  179. package/dist/utils/opencode-cli-resolver.js.map +1 -0
  180. package/dist/utils/regex-patterns.d.ts +64 -0
  181. package/dist/utils/regex-patterns.d.ts.map +1 -0
  182. package/dist/utils/regex-patterns.js +74 -0
  183. package/dist/utils/regex-patterns.js.map +1 -0
  184. package/dist/utils/stale-expiration-map.d.ts +159 -0
  185. package/dist/utils/stale-expiration-map.d.ts.map +1 -0
  186. package/dist/utils/stale-expiration-map.js +277 -0
  187. package/dist/utils/stale-expiration-map.js.map +1 -0
  188. package/dist/utils/string-similarity.d.ts +108 -0
  189. package/dist/utils/string-similarity.d.ts.map +1 -0
  190. package/dist/utils/string-similarity.js +189 -0
  191. package/dist/utils/string-similarity.js.map +1 -0
  192. package/dist/utils/token-validation.d.ts +39 -0
  193. package/dist/utils/token-validation.d.ts.map +1 -0
  194. package/dist/utils/token-validation.js +59 -0
  195. package/dist/utils/token-validation.js.map +1 -0
  196. package/dist/utils/type-safety.d.ts +33 -0
  197. package/dist/utils/type-safety.d.ts.map +1 -0
  198. package/dist/utils/type-safety.js +35 -0
  199. package/dist/utils/type-safety.js.map +1 -0
  200. package/dist/web/public/app.js +491 -0
  201. package/dist/web/public/app.js.br +0 -0
  202. package/dist/web/public/app.js.gz +0 -0
  203. package/dist/web/public/index.html +1675 -0
  204. package/dist/web/public/index.html.br +0 -0
  205. package/dist/web/public/index.html.gz +0 -0
  206. package/dist/web/public/manifest.json +8 -0
  207. package/dist/web/public/mobile.css +1 -0
  208. package/dist/web/public/mobile.css.br +0 -0
  209. package/dist/web/public/mobile.css.gz +0 -0
  210. package/dist/web/public/ralph-wizard.js +1037 -0
  211. package/dist/web/public/ralph-wizard.js.br +0 -0
  212. package/dist/web/public/ralph-wizard.js.gz +0 -0
  213. package/dist/web/public/styles.css +1 -0
  214. package/dist/web/public/styles.css.br +0 -0
  215. package/dist/web/public/styles.css.gz +0 -0
  216. package/dist/web/public/sw.js +67 -0
  217. package/dist/web/public/sw.js.br +0 -0
  218. package/dist/web/public/sw.js.gz +0 -0
  219. package/dist/web/public/upload.html +155 -0
  220. package/dist/web/public/upload.html.br +0 -0
  221. package/dist/web/public/upload.html.gz +0 -0
  222. package/dist/web/public/vendor/xterm-addon-fit.min.js +1 -0
  223. package/dist/web/public/vendor/xterm-addon-fit.min.js.br +0 -0
  224. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  225. package/dist/web/public/vendor/xterm-addon-unicode11.min.js +1 -0
  226. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.br +0 -0
  227. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  228. package/dist/web/public/vendor/xterm-addon-webgl.min.js +2 -0
  229. package/dist/web/public/vendor/xterm-addon-webgl.min.js.br +0 -0
  230. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  231. package/dist/web/public/vendor/xterm.css +209 -0
  232. package/dist/web/public/vendor/xterm.css.br +0 -0
  233. package/dist/web/public/vendor/xterm.css.gz +0 -0
  234. package/dist/web/public/vendor/xterm.min.js +9 -0
  235. package/dist/web/public/vendor/xterm.min.js.br +0 -0
  236. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  237. package/dist/web/schemas.d.ts +479 -0
  238. package/dist/web/schemas.d.ts.map +1 -0
  239. package/dist/web/schemas.js +448 -0
  240. package/dist/web/schemas.js.map +1 -0
  241. package/dist/web/server.d.ts +207 -0
  242. package/dist/web/server.d.ts.map +1 -0
  243. package/dist/web/server.js +5784 -0
  244. package/dist/web/server.js.map +1 -0
  245. package/package.json +110 -0
  246. package/scripts/postinstall.js +390 -0
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview Centralized Map size limits for memory management.
3
+ *
4
+ * These constants define maximum sizes for Maps that track ephemeral data.
5
+ * Without limits, long-running sessions can accumulate unbounded entries
6
+ * leading to memory leaks.
7
+ *
8
+ * Memory Budget Rationale:
9
+ * - Assuming average entry size of ~1KB
10
+ * - MAX_TRACKED_AGENTS=500 × 1KB = ~500KB for agent tracking
11
+ * - Activity/results per agent × agents = bounded by these limits
12
+ * - Total Map overhead: <50MB even under heavy load
13
+ *
14
+ * @module config/map-limits
15
+ */
16
+ /**
17
+ * Maximum concurrent sessions allowed.
18
+ * Each session consumes significant resources (PTY, buffers, watchers).
19
+ */
20
+ export declare const MAX_CONCURRENT_SESSIONS = 50;
21
+ /**
22
+ * Maximum concurrent SSE client connections.
23
+ * Each connection holds an open HTTP response and receives all broadcast events.
24
+ */
25
+ export declare const MAX_SSE_CLIENTS = 100;
26
+ /**
27
+ * Maximum todo items to track per session.
28
+ */
29
+ export declare const MAX_TODOS_PER_SESSION = 500;
30
+ /**
31
+ * Maximum pending tool calls to track per subagent.
32
+ * Entries should be cleaned up on tool_result, but this prevents leaks.
33
+ */
34
+ export declare const MAX_PENDING_TOOL_CALLS = 100;
35
+ /**
36
+ * TTL for orphaned pending tool calls (5 minutes).
37
+ * If no tool_result received, entry is cleaned up.
38
+ */
39
+ export declare const PENDING_TOOL_CALL_TTL_MS: number;
40
+ //# sourceMappingURL=map-limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-limits.d.ts","sourceRoot":"","sources":["../../src/config/map-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH;;;GAGG;AACH,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAM1C;;;GAGG;AACH,eAAO,MAAM,eAAe,MAAM,CAAC;AAMnC;;GAEG;AACH,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAMzC;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAgB,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Centralized Map size limits for memory management.
3
+ *
4
+ * These constants define maximum sizes for Maps that track ephemeral data.
5
+ * Without limits, long-running sessions can accumulate unbounded entries
6
+ * leading to memory leaks.
7
+ *
8
+ * Memory Budget Rationale:
9
+ * - Assuming average entry size of ~1KB
10
+ * - MAX_TRACKED_AGENTS=500 × 1KB = ~500KB for agent tracking
11
+ * - Activity/results per agent × agents = bounded by these limits
12
+ * - Total Map overhead: <50MB even under heavy load
13
+ *
14
+ * @module config/map-limits
15
+ */
16
+ // ============================================================================
17
+ // Session Tracking Limits
18
+ // ============================================================================
19
+ /**
20
+ * Maximum concurrent sessions allowed.
21
+ * Each session consumes significant resources (PTY, buffers, watchers).
22
+ */
23
+ export const MAX_CONCURRENT_SESSIONS = 50;
24
+ // ============================================================================
25
+ // SSE Client Limits
26
+ // ============================================================================
27
+ /**
28
+ * Maximum concurrent SSE client connections.
29
+ * Each connection holds an open HTTP response and receives all broadcast events.
30
+ */
31
+ export const MAX_SSE_CLIENTS = 100;
32
+ // ============================================================================
33
+ // Todo Item Limits (Ralph Tracker)
34
+ // ============================================================================
35
+ /**
36
+ * Maximum todo items to track per session.
37
+ */
38
+ export const MAX_TODOS_PER_SESSION = 500;
39
+ // ============================================================================
40
+ // Pending Tool Calls Limits
41
+ // ============================================================================
42
+ /**
43
+ * Maximum pending tool calls to track per subagent.
44
+ * Entries should be cleaned up on tool_result, but this prevents leaks.
45
+ */
46
+ export const MAX_PENDING_TOOL_CALLS = 100;
47
+ /**
48
+ * TTL for orphaned pending tool calls (5 minutes).
49
+ * If no tool_result received, entry is cleaned up.
50
+ */
51
+ export const PENDING_TOOL_CALL_TTL_MS = 5 * 60 * 1000;
52
+ //# sourceMappingURL=map-limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"map-limits.js","sourceRoot":"","sources":["../../src/config/map-limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+EAA+E;AAC/E,0BAA0B;AAC1B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC,+EAA+E;AAC/E,mCAAmC;AACnC,+EAA+E;AAE/E;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAEzC,+EAA+E;AAC/E,4BAA4B;AAC5B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * @fileoverview File Stream Manager - Manages tail -f processes for live log viewing
3
+ *
4
+ * This module spawns and manages `tail -f` processes for streaming live file
5
+ * content to the frontend. It handles:
6
+ * - Spawning tail processes with initial history
7
+ * - Streaming output via callbacks
8
+ * - Cleanup when streams are closed
9
+ * - Security validation (path within working directory)
10
+ *
11
+ * @module file-stream-manager
12
+ */
13
+ import { ChildProcess } from 'node:child_process';
14
+ import { EventEmitter } from 'node:events';
15
+ /**
16
+ * Represents an active file stream.
17
+ */
18
+ export interface FileStream {
19
+ /** Unique stream identifier */
20
+ id: string;
21
+ /** Session this stream belongs to */
22
+ sessionId: string;
23
+ /** Absolute path to the file being streamed */
24
+ filePath: string;
25
+ /** Tail process handle */
26
+ process: ChildProcess;
27
+ /** Timestamp when stream was created */
28
+ createdAt: number;
29
+ /** Timestamp of last data received */
30
+ lastDataAt: number;
31
+ /** Whether the stream is still active */
32
+ active: boolean;
33
+ /** Callback for sending data to client */
34
+ onData: (data: string) => void;
35
+ /** Callback for stream end */
36
+ onEnd: () => void;
37
+ /** Callback for errors */
38
+ onError: (error: string) => void;
39
+ }
40
+ /**
41
+ * Options for creating a file stream.
42
+ */
43
+ export interface CreateStreamOptions {
44
+ /** Session ID requesting the stream */
45
+ sessionId: string;
46
+ /** Path to the file to stream */
47
+ filePath: string;
48
+ /** Working directory for path validation */
49
+ workingDir: string;
50
+ /** Number of historical lines to show (default: 50) */
51
+ lines?: number;
52
+ /** Callback for data */
53
+ onData: (data: string) => void;
54
+ /** Callback for stream end */
55
+ onEnd: () => void;
56
+ /** Callback for errors */
57
+ onError: (error: string) => void;
58
+ }
59
+ /**
60
+ * Result of creating a stream.
61
+ */
62
+ export interface CreateStreamResult {
63
+ success: boolean;
64
+ streamId?: string;
65
+ error?: string;
66
+ }
67
+ /**
68
+ * Manages file streaming via tail -f processes.
69
+ * Ensures security by validating paths and limiting concurrent streams.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const manager = new FileStreamManager();
74
+ *
75
+ * const result = await manager.createStream({
76
+ * sessionId: 'session-123',
77
+ * filePath: '/var/log/app.log',
78
+ * workingDir: '/var/log',
79
+ * onData: (data) => sseClient.write(data),
80
+ * onEnd: () => sseClient.close(),
81
+ * onError: (err) => console.error(err),
82
+ * });
83
+ *
84
+ * // Later, to stop:
85
+ * manager.closeStream(result.streamId);
86
+ * ```
87
+ */
88
+ export declare class FileStreamManager extends EventEmitter {
89
+ private streams;
90
+ private sessionStreamCounts;
91
+ private cleanupTimer;
92
+ constructor();
93
+ /**
94
+ * Create a new file stream.
95
+ *
96
+ * @param options - Stream configuration
97
+ * @returns Result with stream ID on success, error on failure
98
+ */
99
+ createStream(options: CreateStreamOptions): Promise<CreateStreamResult>;
100
+ /**
101
+ * Close a file stream.
102
+ *
103
+ * @param streamId - ID of the stream to close
104
+ * @returns true if stream was closed, false if not found
105
+ */
106
+ closeStream(streamId: string): boolean;
107
+ /**
108
+ * Close all streams for a session.
109
+ *
110
+ * @param sessionId - Session ID whose streams to close
111
+ * @returns Number of streams closed
112
+ */
113
+ closeSessionStreams(sessionId: string): number;
114
+ /**
115
+ * Get all active streams for a session.
116
+ *
117
+ * @param sessionId - Session ID to query
118
+ * @returns Array of stream info
119
+ */
120
+ getSessionStreams(sessionId: string): Array<{
121
+ id: string;
122
+ filePath: string;
123
+ createdAt: number;
124
+ }>;
125
+ /**
126
+ * Get count of active streams.
127
+ */
128
+ get activeStreamCount(): number;
129
+ /**
130
+ * Clean up and destroy the manager.
131
+ */
132
+ destroy(): void;
133
+ /**
134
+ * Validate that a file path is safe to access.
135
+ * Must be within the working directory (no path traversal).
136
+ */
137
+ private validatePath;
138
+ /**
139
+ * Remove a stream from tracking.
140
+ */
141
+ private removeStream;
142
+ /**
143
+ * Clean up streams that have been inactive for too long.
144
+ */
145
+ private cleanupInactiveStreams;
146
+ }
147
+ export declare const fileStreamManager: FileStreamManager;
148
+ //# sourceMappingURL=file-stream-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-stream-manager.d.ts","sourceRoot":"","sources":["../src/file-stream-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAS,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIzD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA4B3C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,OAAO,EAAE,YAAY,CAAC;IACtB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,0CAA0C;IAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,0BAA0B;IAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,iBAAkB,SAAQ,YAAY;IACjD,OAAO,CAAC,OAAO,CAAsC;IACrD,OAAO,CAAC,mBAAmB,CAAkC;IAC7D,OAAO,CAAC,YAAY,CAA+C;;IAUnE;;;;;OAKG;IACG,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAuH7E;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAoCtC;;;;;OAKG;IACH,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;IAW9C;;;;;OAKG;IACH,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAchG;;OAEG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED;;OAEG;IACH,OAAO,IAAI,IAAI;IAmBf;;;OAGG;IACH,OAAO,CAAC,YAAY;IAoDpB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAS/B;AAGD,eAAO,MAAM,iBAAiB,mBAA0B,CAAC"}
@@ -0,0 +1,351 @@
1
+ /**
2
+ * @fileoverview File Stream Manager - Manages tail -f processes for live log viewing
3
+ *
4
+ * This module spawns and manages `tail -f` processes for streaming live file
5
+ * content to the frontend. It handles:
6
+ * - Spawning tail processes with initial history
7
+ * - Streaming output via callbacks
8
+ * - Cleanup when streams are closed
9
+ * - Security validation (path within working directory)
10
+ *
11
+ * @module file-stream-manager
12
+ */
13
+ import { spawn } from 'node:child_process';
14
+ import { existsSync, statSync, realpathSync } from 'node:fs';
15
+ import { resolve, relative, isAbsolute } from 'node:path';
16
+ import { homedir } from 'node:os';
17
+ import { EventEmitter } from 'node:events';
18
+ // ========== Configuration Constants ==========
19
+ /**
20
+ * Default number of historical lines to show when opening a file.
21
+ */
22
+ const DEFAULT_TAIL_LINES = 50;
23
+ /**
24
+ * Maximum file size to stream (100MB).
25
+ * Skip files larger than this to prevent memory issues.
26
+ */
27
+ const MAX_FILE_SIZE = 100 * 1024 * 1024;
28
+ /**
29
+ * Maximum concurrent streams per session.
30
+ */
31
+ const MAX_STREAMS_PER_SESSION = 5;
32
+ /**
33
+ * Inactivity timeout for streams (5 minutes).
34
+ * Streams with no data for this long will be auto-closed.
35
+ */
36
+ const STREAM_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
37
+ // ========== FileStreamManager Class ==========
38
+ /**
39
+ * Manages file streaming via tail -f processes.
40
+ * Ensures security by validating paths and limiting concurrent streams.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const manager = new FileStreamManager();
45
+ *
46
+ * const result = await manager.createStream({
47
+ * sessionId: 'session-123',
48
+ * filePath: '/var/log/app.log',
49
+ * workingDir: '/var/log',
50
+ * onData: (data) => sseClient.write(data),
51
+ * onEnd: () => sseClient.close(),
52
+ * onError: (err) => console.error(err),
53
+ * });
54
+ *
55
+ * // Later, to stop:
56
+ * manager.closeStream(result.streamId);
57
+ * ```
58
+ */
59
+ export class FileStreamManager extends EventEmitter {
60
+ streams = new Map();
61
+ sessionStreamCounts = new Map();
62
+ cleanupTimer = null;
63
+ constructor() {
64
+ super();
65
+ // Start cleanup timer for inactive streams
66
+ this.cleanupTimer = setInterval(() => this.cleanupInactiveStreams(), 60 * 1000);
67
+ }
68
+ // ========== Public Methods ==========
69
+ /**
70
+ * Create a new file stream.
71
+ *
72
+ * @param options - Stream configuration
73
+ * @returns Result with stream ID on success, error on failure
74
+ */
75
+ async createStream(options) {
76
+ const { sessionId, filePath, workingDir, lines = DEFAULT_TAIL_LINES, onData, onEnd, onError } = options;
77
+ // Check concurrent stream limit for this session
78
+ const currentCount = this.sessionStreamCounts.get(sessionId) || 0;
79
+ if (currentCount >= MAX_STREAMS_PER_SESSION) {
80
+ return {
81
+ success: false,
82
+ error: `Maximum ${MAX_STREAMS_PER_SESSION} concurrent streams per session`,
83
+ };
84
+ }
85
+ // Resolve and validate path
86
+ const validationResult = this.validatePath(filePath, workingDir);
87
+ if (!validationResult.valid) {
88
+ return { success: false, error: validationResult.error };
89
+ }
90
+ let absolutePath = validationResult.absolutePath;
91
+ // Check file exists and size
92
+ try {
93
+ const stats = statSync(absolutePath);
94
+ if (stats.size > MAX_FILE_SIZE) {
95
+ return {
96
+ success: false,
97
+ error: `File too large (${Math.round(stats.size / 1024 / 1024)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB limit)`,
98
+ };
99
+ }
100
+ }
101
+ catch (err) {
102
+ const errorCode = err instanceof Error && 'code' in err ? err.code : 'UNKNOWN';
103
+ console.warn(`[FileStreamManager] Failed to stat file "${absolutePath}" (${errorCode}):`, err instanceof Error ? err.message : String(err));
104
+ return { success: false, error: 'File not found or not accessible' };
105
+ }
106
+ // Re-resolve symlinks right before spawn to minimize TOCTOU window.
107
+ // A symlink could have been swapped between validatePath() and here.
108
+ try {
109
+ const resolvedPath = realpathSync(absolutePath);
110
+ if (resolvedPath !== absolutePath) {
111
+ // Symlink target changed — re-validate against allowed paths
112
+ const recheck = this.validatePath(resolvedPath, workingDir);
113
+ if (!recheck.valid) {
114
+ return { success: false, error: recheck.error };
115
+ }
116
+ absolutePath = resolvedPath;
117
+ }
118
+ }
119
+ catch {
120
+ return { success: false, error: 'File not found or not accessible' };
121
+ }
122
+ // Generate stream ID
123
+ const streamId = `${sessionId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
124
+ // Spawn tail process
125
+ const tailProcess = spawn('tail', ['-f', '-n', String(lines), absolutePath], {
126
+ stdio: ['ignore', 'pipe', 'pipe'],
127
+ });
128
+ if (!tailProcess.pid) {
129
+ return { success: false, error: 'Failed to spawn tail process' };
130
+ }
131
+ const stream = {
132
+ id: streamId,
133
+ sessionId,
134
+ filePath: absolutePath,
135
+ process: tailProcess,
136
+ createdAt: Date.now(),
137
+ lastDataAt: Date.now(),
138
+ active: true,
139
+ onData,
140
+ onEnd,
141
+ onError,
142
+ };
143
+ // Handle stdout
144
+ tailProcess.stdout?.on('data', (data) => {
145
+ if (stream.active) {
146
+ stream.lastDataAt = Date.now();
147
+ onData(data.toString());
148
+ }
149
+ });
150
+ // Handle stderr
151
+ tailProcess.stderr?.on('data', (data) => {
152
+ if (stream.active) {
153
+ onError(data.toString());
154
+ }
155
+ });
156
+ // Handle process exit
157
+ tailProcess.on('exit', (_code) => {
158
+ if (stream.active) {
159
+ stream.active = false;
160
+ onEnd();
161
+ this.removeStream(streamId);
162
+ }
163
+ });
164
+ // Handle errors
165
+ tailProcess.on('error', (err) => {
166
+ if (stream.active) {
167
+ stream.active = false;
168
+ onError(err.message);
169
+ this.removeStream(streamId);
170
+ }
171
+ });
172
+ // Store stream
173
+ this.streams.set(streamId, stream);
174
+ this.sessionStreamCounts.set(sessionId, currentCount + 1);
175
+ return { success: true, streamId };
176
+ }
177
+ /**
178
+ * Close a file stream.
179
+ *
180
+ * @param streamId - ID of the stream to close
181
+ * @returns true if stream was closed, false if not found
182
+ */
183
+ closeStream(streamId) {
184
+ const stream = this.streams.get(streamId);
185
+ if (!stream)
186
+ return false;
187
+ stream.active = false;
188
+ // Remove all event listeners to prevent memory leaks (closures hold references)
189
+ stream.process.stdout?.removeAllListeners();
190
+ stream.process.stderr?.removeAllListeners();
191
+ stream.process.removeAllListeners();
192
+ // Kill the tail process
193
+ try {
194
+ stream.process.kill('SIGTERM');
195
+ // Force kill after 1 second if still running
196
+ const forceKillTimer = setTimeout(() => {
197
+ try {
198
+ stream.process.kill('SIGKILL');
199
+ }
200
+ catch {
201
+ // Already dead
202
+ }
203
+ }, 1000);
204
+ // Clear the force kill timer if process exits naturally
205
+ stream.process.once('exit', () => {
206
+ clearTimeout(forceKillTimer);
207
+ });
208
+ }
209
+ catch {
210
+ // Process may have already exited
211
+ }
212
+ stream.onEnd();
213
+ this.removeStream(streamId);
214
+ return true;
215
+ }
216
+ /**
217
+ * Close all streams for a session.
218
+ *
219
+ * @param sessionId - Session ID whose streams to close
220
+ * @returns Number of streams closed
221
+ */
222
+ closeSessionStreams(sessionId) {
223
+ let closed = 0;
224
+ for (const [streamId, stream] of this.streams) {
225
+ if (stream.sessionId === sessionId) {
226
+ this.closeStream(streamId);
227
+ closed++;
228
+ }
229
+ }
230
+ return closed;
231
+ }
232
+ /**
233
+ * Get all active streams for a session.
234
+ *
235
+ * @param sessionId - Session ID to query
236
+ * @returns Array of stream info
237
+ */
238
+ getSessionStreams(sessionId) {
239
+ const result = [];
240
+ for (const [, stream] of this.streams) {
241
+ if (stream.sessionId === sessionId && stream.active) {
242
+ result.push({
243
+ id: stream.id,
244
+ filePath: stream.filePath,
245
+ createdAt: stream.createdAt,
246
+ });
247
+ }
248
+ }
249
+ return result;
250
+ }
251
+ /**
252
+ * Get count of active streams.
253
+ */
254
+ get activeStreamCount() {
255
+ return this.streams.size;
256
+ }
257
+ /**
258
+ * Clean up and destroy the manager.
259
+ */
260
+ destroy() {
261
+ // Stop cleanup timer
262
+ if (this.cleanupTimer) {
263
+ clearInterval(this.cleanupTimer);
264
+ this.cleanupTimer = null;
265
+ }
266
+ // Close all streams
267
+ for (const streamId of this.streams.keys()) {
268
+ this.closeStream(streamId);
269
+ }
270
+ // Clear Maps to release references
271
+ this.streams.clear();
272
+ this.sessionStreamCounts.clear();
273
+ }
274
+ // ========== Private Methods ==========
275
+ /**
276
+ * Validate that a file path is safe to access.
277
+ * Must be within the working directory (no path traversal).
278
+ */
279
+ validatePath(filePath, workingDir) {
280
+ // Expand ~ to home directory
281
+ let expandedPath = filePath;
282
+ if (expandedPath.startsWith('~')) {
283
+ expandedPath = expandedPath.replace(/^~/, homedir());
284
+ }
285
+ // Resolve to absolute path
286
+ let absolutePath = isAbsolute(expandedPath) ? resolve(expandedPath) : resolve(workingDir, expandedPath);
287
+ // Resolve symlinks to prevent symlink attacks — validate the real target,
288
+ // not the symlink itself. Fall back to resolved path if file doesn't exist yet.
289
+ try {
290
+ absolutePath = realpathSync(absolutePath);
291
+ }
292
+ catch {
293
+ // File may not exist yet (tail -f can wait); keep the resolved path
294
+ // which will be caught by the existsSync check below
295
+ }
296
+ // Normalize the working directory
297
+ const normalizedWorkingDir = resolve(workingDir);
298
+ // Check if the resolved path is within the working directory
299
+ // or common log directories (/tmp intentionally excluded — world-writable)
300
+ const allowedPaths = [normalizedWorkingDir, '/var/log', resolve(homedir(), 'logs')];
301
+ const isAllowed = allowedPaths.some((allowed) => {
302
+ const rel = relative(allowed, absolutePath);
303
+ return rel && !rel.startsWith('..') && !isAbsolute(rel);
304
+ });
305
+ if (!isAllowed) {
306
+ return {
307
+ valid: false,
308
+ error: `Path must be within working directory or allowed log directories`,
309
+ };
310
+ }
311
+ // Note: No need to check for '..' — resolve() already normalizes the path,
312
+ // and realpathSync() resolves symlinks. Both eliminate traversal sequences.
313
+ // Check file exists
314
+ if (!existsSync(absolutePath)) {
315
+ return { valid: false, error: 'File does not exist' };
316
+ }
317
+ return { valid: true, absolutePath };
318
+ }
319
+ /**
320
+ * Remove a stream from tracking.
321
+ */
322
+ removeStream(streamId) {
323
+ const stream = this.streams.get(streamId);
324
+ if (stream) {
325
+ // Decrement session count
326
+ const count = this.sessionStreamCounts.get(stream.sessionId) || 0;
327
+ if (count <= 1) {
328
+ this.sessionStreamCounts.delete(stream.sessionId);
329
+ }
330
+ else {
331
+ this.sessionStreamCounts.set(stream.sessionId, count - 1);
332
+ }
333
+ }
334
+ this.streams.delete(streamId);
335
+ }
336
+ /**
337
+ * Clean up streams that have been inactive for too long.
338
+ */
339
+ cleanupInactiveStreams() {
340
+ const now = Date.now();
341
+ for (const [streamId, stream] of this.streams) {
342
+ if (now - stream.lastDataAt > STREAM_INACTIVITY_TIMEOUT_MS) {
343
+ console.log(`[FileStreamManager] Closing inactive stream: ${streamId}`);
344
+ this.closeStream(streamId);
345
+ }
346
+ }
347
+ }
348
+ }
349
+ // Export singleton instance
350
+ export const fileStreamManager = new FileStreamManager();
351
+ //# sourceMappingURL=file-stream-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-stream-manager.js","sourceRoot":"","sources":["../src/file-stream-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @fileoverview Claude Code hooks configuration generator
3
+ *
4
+ * Generates .claude/settings.local.json with hook definitions that POST
5
+ * to Codeman's /api/hook-event endpoint when Claude Code fires
6
+ * notification or stop hooks. Uses $CODEMAN_API_URL and
7
+ * $CODEMAN_SESSION_ID env vars (set on every managed session) so the
8
+ * config is static per case directory.
9
+ */
10
+ /**
11
+ * Generates the hooks section for .claude/settings.local.json
12
+ *
13
+ * The hook commands read stdin JSON from Claude Code (contains tool_name,
14
+ * tool_input, etc.) and forward it as the `data` field to Codeman's API.
15
+ * Env vars are resolved at runtime by the shell, so the config is static
16
+ * per case directory.
17
+ */
18
+ export declare function generateHooksConfig(): {
19
+ hooks: Record<string, unknown[]>;
20
+ };
21
+ /**
22
+ * Updates env vars in .claude/settings.local.json for the given case path.
23
+ * Merges with existing env field; removes vars set to empty string.
24
+ */
25
+ export declare function updateCaseEnvVars(casePath: string, envVars: Record<string, string>): Promise<void>;
26
+ /**
27
+ * Writes hooks config to .claude/settings.local.json in the given case path.
28
+ * Merges with existing file content, only touching the `hooks` key.
29
+ */
30
+ export declare function writeHooksConfig(casePath: string): Promise<void>;
31
+ //# sourceMappingURL=hooks-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks-config.d.ts","sourceRoot":"","sources":["../src/hooks-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CA4C1E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BxG;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBtE"}