@stackmemoryai/stackmemory 1.10.5 → 1.14.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 (225) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +107 -24
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/claude-sm.js +266 -84
  11. package/dist/src/cli/codex-sm.js +225 -33
  12. package/dist/src/cli/commands/bench.js +209 -2
  13. package/dist/src/cli/commands/brain.js +206 -0
  14. package/dist/src/cli/commands/cache.js +126 -0
  15. package/dist/src/cli/commands/company-os.js +184 -0
  16. package/dist/src/cli/commands/context.js +5 -0
  17. package/dist/src/cli/commands/daemon.js +41 -0
  18. package/dist/src/cli/commands/handoff.js +40 -9
  19. package/dist/src/cli/commands/onboard.js +70 -3
  20. package/dist/src/cli/commands/operator.js +127 -0
  21. package/dist/src/cli/commands/optimize.js +117 -0
  22. package/dist/src/cli/commands/orchestrate.js +232 -5
  23. package/dist/src/cli/commands/orchestrator.js +315 -26
  24. package/dist/src/cli/commands/pack.js +322 -0
  25. package/dist/src/cli/commands/patterns.js +254 -0
  26. package/dist/src/cli/commands/portal.js +161 -0
  27. package/dist/src/cli/commands/scaffold.js +92 -0
  28. package/dist/src/cli/commands/search.js +40 -1
  29. package/dist/src/cli/commands/setup.js +178 -11
  30. package/dist/src/cli/commands/skills.js +10 -1
  31. package/dist/src/cli/commands/state.js +265 -0
  32. package/dist/src/cli/commands/sync.js +253 -0
  33. package/dist/src/cli/commands/tasks.js +130 -1
  34. package/dist/src/cli/commands/vision.js +221 -0
  35. package/dist/src/cli/gemini-sm.js +19 -29
  36. package/dist/src/cli/hermes-sm.js +224 -0
  37. package/dist/src/cli/index.js +105 -39
  38. package/dist/src/cli/opencode-sm.js +38 -21
  39. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  40. package/dist/src/cli/utils/real-cli-bin.js +116 -0
  41. package/dist/src/core/brain/brain-store.js +187 -0
  42. package/dist/src/core/brain/brain-sync.js +193 -0
  43. package/dist/src/core/brain/index.js +78 -0
  44. package/dist/src/core/brain/types.js +10 -0
  45. package/dist/src/core/cache/content-cache.js +238 -0
  46. package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
  47. package/dist/src/core/cache/token-estimator.js +39 -0
  48. package/dist/src/core/config/feature-flags.js +2 -6
  49. package/dist/src/core/context/frame-database.js +79 -27
  50. package/dist/src/core/context/recursive-context-manager.js +1 -1
  51. package/dist/src/core/context/rehydration.js +2 -1
  52. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  53. package/dist/src/core/cross-search/index.js +10 -0
  54. package/dist/src/core/database/sqlite-adapter.js +14 -84
  55. package/dist/src/core/extensions/provider-adapter.js +5 -0
  56. package/dist/src/core/models/model-router.js +54 -2
  57. package/dist/src/core/models/provider-pricing.js +58 -4
  58. package/dist/src/core/monitoring/logger.js +2 -1
  59. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  60. package/dist/src/core/patterns/index.js +22 -0
  61. package/dist/src/core/patterns/pattern-applier.js +39 -0
  62. package/dist/src/core/patterns/pattern-observer.js +157 -0
  63. package/dist/src/core/patterns/pattern-store.js +259 -0
  64. package/dist/src/core/patterns/types.js +19 -0
  65. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  66. package/dist/src/core/provenance/index.js +40 -0
  67. package/dist/src/core/provenance/provenance-store.js +194 -0
  68. package/dist/src/core/provenance/types.js +82 -0
  69. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  70. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  71. package/dist/src/core/session/project-handoff.js +64 -0
  72. package/dist/src/core/session/session-manager.js +28 -0
  73. package/dist/src/core/shared-state/canonical-store.js +564 -0
  74. package/dist/src/core/skill-packs/index.js +18 -0
  75. package/dist/src/core/skill-packs/parser.js +42 -0
  76. package/dist/src/core/skill-packs/registry.js +224 -0
  77. package/dist/src/core/skill-packs/types.js +79 -0
  78. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  79. package/dist/src/core/storage/cloud-sync.js +574 -0
  80. package/dist/src/core/storage/two-tier-storage.js +5 -1
  81. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  82. package/dist/src/core/tasks/md-task-parser.js +138 -0
  83. package/dist/src/core/trace/trace-event-store.js +282 -0
  84. package/dist/src/core/vision/index.js +27 -0
  85. package/dist/src/core/vision/signals.js +79 -0
  86. package/dist/src/core/vision/types.js +22 -0
  87. package/dist/src/core/vision/vision-file.js +146 -0
  88. package/dist/src/core/vision/vision-loop.js +220 -0
  89. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  90. package/dist/src/daemon/daemon-config.js +52 -0
  91. package/dist/src/daemon/services/desire-path-service.js +566 -0
  92. package/dist/src/daemon/services/github-service.js +126 -0
  93. package/dist/src/daemon/services/research-stream-service.js +320 -0
  94. package/dist/src/daemon/services/telemetry-service.js +192 -0
  95. package/dist/src/daemon/unified-daemon.js +58 -1
  96. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  97. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  98. package/dist/src/features/operator/adapter-factory.js +62 -0
  99. package/dist/src/features/operator/browser-adapter.js +109 -0
  100. package/dist/src/features/operator/desktop-adapter.js +125 -0
  101. package/dist/src/features/operator/index.js +39 -0
  102. package/dist/src/features/operator/llm-decision.js +137 -0
  103. package/dist/src/features/operator/operator-logger.js +92 -0
  104. package/dist/src/features/operator/overnight-runner.js +327 -0
  105. package/dist/src/features/operator/screen-adapter.js +91 -0
  106. package/dist/src/features/operator/session-manager.js +127 -0
  107. package/dist/src/features/operator/state-machine.js +227 -0
  108. package/dist/src/features/operator/task-queue.js +81 -0
  109. package/dist/src/features/portal/index.js +26 -0
  110. package/dist/src/features/portal/server.js +240 -0
  111. package/dist/src/features/portal/types.js +14 -0
  112. package/dist/src/features/portal/ui.js +195 -0
  113. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  114. package/dist/src/features/tasks/task-aware-context.js +2 -1
  115. package/dist/src/features/tui/simple-monitor.js +0 -23
  116. package/dist/src/features/tui/swarm-monitor.js +8 -66
  117. package/dist/src/features/web/client/hooks/use-socket.js +12 -0
  118. package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
  119. package/dist/src/features/web/client/next-env.d.js +4 -0
  120. package/dist/src/features/web/client/stores/session-store.js +12 -0
  121. package/dist/src/features/web/server/gcp-billing.js +76 -0
  122. package/dist/src/features/web/server/index.js +10 -0
  123. package/dist/src/features/web/server/spend-calculator.js +228 -0
  124. package/dist/src/hooks/schemas.js +6 -1
  125. package/dist/src/integrations/anthropic/client.js +3 -2
  126. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  127. package/dist/src/integrations/claude-code/subagent-client.js +307 -11
  128. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  129. package/dist/src/integrations/github/pr-state.js +158 -0
  130. package/dist/src/integrations/linear/client.js +4 -1
  131. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  132. package/dist/src/integrations/linear/webhook-server.js +18 -22
  133. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  134. package/dist/src/integrations/mcp/handlers/index.js +40 -84
  135. package/dist/src/integrations/mcp/server.js +542 -641
  136. package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
  137. package/dist/src/integrations/mcp/tool-definitions.js +152 -682
  138. package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
  139. package/dist/src/orchestrators/multimodal/determinism.js +244 -0
  140. package/dist/src/orchestrators/multimodal/harness.js +149 -78
  141. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  142. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  143. package/dist/src/utils/hook-installer.js +0 -8
  144. package/dist/src/utils/process-cleanup.js +1 -7
  145. package/docs/README.md +42 -0
  146. package/docs/guides/README_INSTALL.md +208 -0
  147. package/package.json +27 -9
  148. package/packs/coding/python-fastapi/instructions.md +60 -0
  149. package/packs/coding/python-fastapi/pack.yaml +28 -0
  150. package/packs/coding/typescript-react/instructions.md +47 -0
  151. package/packs/coding/typescript-react/pack.yaml +28 -0
  152. package/packs/core/commands/capture.md +32 -0
  153. package/packs/core/commands/learn.md +73 -0
  154. package/packs/core/commands/next.md +36 -0
  155. package/packs/core/commands/restart.md +58 -0
  156. package/packs/core/commands/restore.md +29 -0
  157. package/packs/core/commands/start.md +57 -0
  158. package/packs/core/commands/stop.md +65 -0
  159. package/packs/core/commands/summary.md +40 -0
  160. package/packs/core/manifest.json +24 -0
  161. package/packs/ops/decision-recovery/instructions.md +65 -0
  162. package/packs/ops/decision-recovery/pack.yaml +89 -0
  163. package/scripts/claude-code-wrapper.sh +11 -0
  164. package/scripts/claude-sm-setup.sh +12 -1
  165. package/scripts/codex-wrapper.sh +11 -0
  166. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  167. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  168. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  169. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  170. package/scripts/hooks/cleanup-shell.sh +12 -1
  171. package/scripts/hooks/task-complete.sh +12 -1
  172. package/scripts/install-code-execution-hooks.sh +12 -1
  173. package/scripts/install-sweep-hook.sh +12 -0
  174. package/scripts/install.sh +11 -0
  175. package/scripts/opencode-wrapper.sh +11 -0
  176. package/scripts/portal/cloud-init.yaml +69 -0
  177. package/scripts/portal/setup.sh +69 -0
  178. package/scripts/portal/stackmemory-portal.service +34 -0
  179. package/scripts/setup-claude-integration.sh +12 -1
  180. package/scripts/smoke-init-db.sh +23 -0
  181. package/scripts/stackmemory-daemon.sh +11 -0
  182. package/scripts/verify-dist.cjs +11 -4
  183. package/dist/src/cli/commands/ralph.js +0 -1053
  184. package/dist/src/cli/commands/team.js +0 -168
  185. package/dist/src/core/context/shared-context-layer.js +0 -620
  186. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  187. package/dist/src/core/merge/conflict-detector.js +0 -430
  188. package/dist/src/core/merge/resolution-engine.js +0 -557
  189. package/dist/src/core/merge/stack-diff.js +0 -531
  190. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  191. package/dist/src/hooks/diffmem-hooks.js +0 -376
  192. package/dist/src/integrations/diffmem/client.js +0 -208
  193. package/dist/src/integrations/diffmem/config.js +0 -14
  194. package/dist/src/integrations/greptile/client.js +0 -101
  195. package/dist/src/integrations/greptile/config.js +0 -14
  196. package/dist/src/integrations/greptile/index.js +0 -11
  197. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  198. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  199. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  200. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  201. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  202. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  203. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  204. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
  205. package/dist/src/integrations/ralph/index.js +0 -17
  206. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
  207. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  208. package/dist/src/integrations/ralph/loopmax.js +0 -488
  209. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  210. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  211. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  212. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  213. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  214. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  215. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  216. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  217. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  218. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  219. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
  220. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  221. package/scripts/ralph-loop-implementation.js +0 -404
  222. /package/dist/src/core/{merge → cache}/types.js +0 -0
  223. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  224. /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
  225. /package/dist/src/{integrations/ralph → features/operator}/types.js +0 -0
@@ -0,0 +1,327 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { existsSync, unlinkSync } from "fs";
6
+ import { execSync } from "child_process";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ import {
10
+ detectState,
11
+ decideAction,
12
+ detectCompletion
13
+ } from "./state-machine.js";
14
+ import { TaskQueue } from "./task-queue.js";
15
+ import { SessionManager } from "./session-manager.js";
16
+ import { OperatorLogger } from "./operator-logger.js";
17
+ import {
18
+ classifyScreenState,
19
+ generateNudge
20
+ } from "./llm-decision.js";
21
+ const STOP_SIGNAL_DIR = join(homedir(), ".stackmemory", "operator");
22
+ const STOP_SIGNAL_FILE = join(STOP_SIGNAL_DIR, "stop-signal");
23
+ class OvernightRunner {
24
+ queue;
25
+ session;
26
+ logger;
27
+ config;
28
+ adapter;
29
+ llmConfig;
30
+ checkpoint;
31
+ lastScreen = "";
32
+ lastChangeAt = Date.now();
33
+ stopped = false;
34
+ constructor(config) {
35
+ this.config = config;
36
+ this.queue = new TaskQueue(config.taskFilePath);
37
+ this.session = new SessionManager({
38
+ sessionName: config.sessionName,
39
+ cwd: config.cwd,
40
+ model: config.model
41
+ });
42
+ this.logger = new OperatorLogger(config.logDir, STOP_SIGNAL_DIR);
43
+ this.llmConfig = config.anthropicApiKey ? { apiKey: config.anthropicApiKey, model: config.llmModel } : void 0;
44
+ this.checkpoint = this.freshCheckpoint();
45
+ }
46
+ /** Main entry point — runs until queue drained or stopped */
47
+ async run() {
48
+ this.session.preflight();
49
+ if (this.queue.isEmpty()) {
50
+ process.stderr.write(
51
+ "[operator] No actionable tasks in queue. Nothing to do.\n"
52
+ );
53
+ return;
54
+ }
55
+ const onSignal = () => {
56
+ this.stopped = true;
57
+ };
58
+ process.on("SIGINT", onSignal);
59
+ process.on("SIGTERM", onSignal);
60
+ this.logger.logEvent("start", {
61
+ taskFile: this.config.taskFilePath,
62
+ cwd: this.config.cwd,
63
+ session: this.config.sessionName,
64
+ tasksRemaining: this.queue.remaining()
65
+ });
66
+ try {
67
+ this.adapter = this.session.start();
68
+ this.lastChangeAt = Date.now();
69
+ while (!this.stopped) {
70
+ await this.tick();
71
+ if (existsSync(STOP_SIGNAL_FILE)) {
72
+ this.logger.logEvent("shutdown", { reason: "stop signal" });
73
+ try {
74
+ unlinkSync(STOP_SIGNAL_FILE);
75
+ } catch {
76
+ }
77
+ this.stopped = true;
78
+ break;
79
+ }
80
+ if (!this.checkpoint.currentTaskId && this.queue.isEmpty()) {
81
+ this.logger.logEvent("queue_drained");
82
+ break;
83
+ }
84
+ await this.sleep(this.config.pollIntervalMs);
85
+ }
86
+ } finally {
87
+ this.session.stop();
88
+ process.off("SIGINT", onSignal);
89
+ process.off("SIGTERM", onSignal);
90
+ this.logger.printSummary({
91
+ totalRuntimeMs: Date.now() - this.checkpoint.startedAt,
92
+ tasksCompleted: this.checkpoint.tasksCompleted.length,
93
+ tasksBlocked: this.checkpoint.tasksBlocked.length,
94
+ tasksRemaining: this.queue.remaining(),
95
+ totalRestarts: this.checkpoint.totalRestarts,
96
+ totalPermissionApprovals: this.checkpoint.totalPermissionApprovals,
97
+ totalRateLimitHits: this.checkpoint.totalRateLimitHits
98
+ });
99
+ }
100
+ }
101
+ // ── Tick ─────────────────────────────────────────────
102
+ async tick() {
103
+ if (!this.adapter) return;
104
+ const screen = this.adapter.readScreen();
105
+ if (screen !== this.lastScreen) {
106
+ this.lastChangeAt = Date.now();
107
+ this.lastScreen = screen;
108
+ }
109
+ const detection = detectState(
110
+ screen,
111
+ this.checkpoint.currentState,
112
+ this.lastChangeAt,
113
+ this.config.stuckTimeoutMs
114
+ );
115
+ if (detection.state === "IDLE" && this.checkpoint.currentTaskId) {
116
+ const completion = detectCompletion(screen);
117
+ if (completion.completed) {
118
+ this.handleTaskComplete(this.checkpoint.currentTaskId);
119
+ return;
120
+ }
121
+ if (completion.blocked) {
122
+ this.handleTaskBlocked(
123
+ this.checkpoint.currentTaskId,
124
+ completion.blockedReason ?? "unknown"
125
+ );
126
+ return;
127
+ }
128
+ }
129
+ if (detection.state === "UNKNOWN" && this.llmConfig) {
130
+ const screenshot = this.adapter.readScreenshot?.();
131
+ const llmResult = await classifyScreenState(
132
+ screenshot ?? screen,
133
+ this.llmConfig
134
+ );
135
+ if (llmResult.state !== "UNKNOWN") {
136
+ detection.state = llmResult.state;
137
+ detection.confidence = llmResult.confidence;
138
+ detection.detail = `llm: ${llmResult.detail}`;
139
+ }
140
+ }
141
+ if (detection.state === "STUCK" && this.llmConfig && this.checkpoint.currentTaskId) {
142
+ const taskContent = this.queue.dequeue();
143
+ const nudge = await generateNudge(
144
+ screen,
145
+ taskContent?.task ?? this.checkpoint.currentTaskId,
146
+ this.llmConfig
147
+ );
148
+ if (nudge) {
149
+ this.adapter.sendInput(nudge);
150
+ this.lastChangeAt = Date.now();
151
+ this.logger.logEvent("stuck_detected", { action: "nudge", nudge });
152
+ return;
153
+ }
154
+ }
155
+ const nextTask = this.checkpoint.currentTaskId ? void 0 : this.queue.dequeue();
156
+ const action = decideAction(detection, this.checkpoint, nextTask);
157
+ this.logger.logTick({
158
+ state: detection.state,
159
+ confidence: detection.confidence,
160
+ action: action.type,
161
+ currentTask: this.checkpoint.currentTaskId ?? void 0,
162
+ screenSnippet: screen.slice(-200)
163
+ });
164
+ this.executeAction(action);
165
+ this.checkpoint.currentState = detection.state;
166
+ this.checkpoint.lastTickAt = Date.now();
167
+ this.logger.writeCheckpoint(this.checkpoint);
168
+ }
169
+ // ── Action Execution ────────────────────────────────
170
+ executeAction(action) {
171
+ if (!this.adapter) return;
172
+ switch (action.type) {
173
+ case "INJECT_TASK":
174
+ this.injectTask(action.task);
175
+ break;
176
+ case "AUTO_APPROVE":
177
+ this.adapter.sendKey("y");
178
+ this.adapter.sendKey("Enter");
179
+ this.checkpoint.totalPermissionApprovals++;
180
+ this.logger.logEvent("permission_approved");
181
+ break;
182
+ case "NUDGE":
183
+ this.adapter.sendInput(action.message);
184
+ this.lastChangeAt = Date.now();
185
+ this.checkpoint.nudgeCount++;
186
+ this.logger.logEvent("stuck_detected", {
187
+ action: "nudge",
188
+ nudgeCount: this.checkpoint.nudgeCount,
189
+ message: action.message
190
+ });
191
+ break;
192
+ case "BACKOFF":
193
+ this.checkpoint.totalRateLimitHits++;
194
+ this.checkpoint.consecutiveRateLimitHits++;
195
+ this.logger.logEvent("rate_limit_backoff", {
196
+ durationMs: action.durationMs
197
+ });
198
+ this.sleepSync(action.durationMs);
199
+ break;
200
+ case "RESTART_SESSION":
201
+ this.checkpoint.totalRestarts++;
202
+ this.checkpoint.consecutiveRestarts++;
203
+ this.logger.logEvent("session_restarted", {
204
+ consecutive: this.checkpoint.consecutiveRestarts
205
+ });
206
+ this.adapter = this.session.restart();
207
+ this.lastChangeAt = Date.now();
208
+ this.lastScreen = "";
209
+ break;
210
+ case "KILL_AND_RESTART":
211
+ this.adapter.sendKey("C-c");
212
+ this.sleepSync(2e3);
213
+ this.checkpoint.totalRestarts++;
214
+ this.logger.logEvent("stuck_detected");
215
+ this.adapter = this.session.restart();
216
+ this.lastChangeAt = Date.now();
217
+ this.lastScreen = "";
218
+ break;
219
+ case "MARK_COMPLETE":
220
+ this.handleTaskComplete(action.taskId);
221
+ break;
222
+ case "MARK_BLOCKED":
223
+ this.handleTaskBlocked(action.taskId, action.reason);
224
+ break;
225
+ case "LOG_ERROR":
226
+ this.logger.logEvent("error", { error: action.error });
227
+ if (action.error.includes("max consecutive restarts")) {
228
+ this.stopped = true;
229
+ }
230
+ break;
231
+ case "WAIT":
232
+ this.sleepSync(action.durationMs);
233
+ break;
234
+ case "NOOP":
235
+ if (this.checkpoint.currentState === "WORKING") {
236
+ this.checkpoint.consecutiveRateLimitHits = 0;
237
+ this.checkpoint.consecutiveRestarts = 0;
238
+ }
239
+ break;
240
+ }
241
+ }
242
+ // ── Task Lifecycle ──────────────────────────────────
243
+ injectTask(task) {
244
+ if (!this.adapter) return;
245
+ this.adapter.clearHistory();
246
+ const prompt = this.queue.buildTaskPrompt(task);
247
+ this.adapter.sendInput(prompt);
248
+ this.queue.markActive(task.id);
249
+ this.checkpoint.currentTaskId = task.id;
250
+ this.checkpoint.nudgeCount = 0;
251
+ this.lastChangeAt = Date.now();
252
+ this.logger.logEvent("task_injected", {
253
+ taskId: task.id,
254
+ priority: task.priority,
255
+ task: task.task
256
+ });
257
+ }
258
+ handleTaskComplete(taskId) {
259
+ this.queue.markDone(taskId);
260
+ this.checkpoint.tasksCompleted.push(taskId);
261
+ this.checkpoint.currentTaskId = null;
262
+ this.checkpoint.consecutiveRestarts = 0;
263
+ this.logger.logEvent("task_completed", { taskId });
264
+ if (this.config.autoCommit) {
265
+ this.autoCommit(taskId);
266
+ }
267
+ }
268
+ handleTaskBlocked(taskId, reason) {
269
+ this.queue.markBlocked(taskId, reason);
270
+ this.checkpoint.tasksBlocked.push({ id: taskId, reason });
271
+ this.checkpoint.currentTaskId = null;
272
+ this.logger.logEvent("task_blocked", { taskId, reason });
273
+ }
274
+ autoCommit(taskId) {
275
+ try {
276
+ const status = execSync("git status --porcelain", {
277
+ cwd: this.config.cwd,
278
+ encoding: "utf-8",
279
+ timeout: 1e4
280
+ }).trim();
281
+ if (!status) return;
282
+ execSync("git add -A", {
283
+ cwd: this.config.cwd,
284
+ stdio: "ignore",
285
+ timeout: 1e4
286
+ });
287
+ execSync(`git commit -m "operator: complete ${taskId}"`, {
288
+ cwd: this.config.cwd,
289
+ stdio: "ignore",
290
+ timeout: 3e4
291
+ });
292
+ } catch {
293
+ }
294
+ }
295
+ // ── Helpers ─────────────────────────────────────────
296
+ freshCheckpoint() {
297
+ return {
298
+ startedAt: Date.now(),
299
+ lastTickAt: Date.now(),
300
+ currentState: "UNKNOWN",
301
+ currentTaskId: null,
302
+ tasksCompleted: [],
303
+ tasksBlocked: [],
304
+ totalRestarts: 0,
305
+ consecutiveRestarts: 0,
306
+ totalPermissionApprovals: 0,
307
+ totalRateLimitHits: 0,
308
+ consecutiveRateLimitHits: 0,
309
+ nudgeCount: 0
310
+ };
311
+ }
312
+ sleep(ms) {
313
+ return new Promise((resolve) => {
314
+ const timer = setTimeout(resolve, ms);
315
+ timer.unref();
316
+ });
317
+ }
318
+ sleepSync(ms) {
319
+ try {
320
+ execSync(`sleep ${ms / 1e3}`, { stdio: "ignore" });
321
+ } catch {
322
+ }
323
+ }
324
+ }
325
+ export {
326
+ OvernightRunner
327
+ };
@@ -0,0 +1,91 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { execSync } from "child_process";
6
+ function shellEscape(str) {
7
+ return "'" + str.replace(/'/g, "'\\''") + "'";
8
+ }
9
+ class TmuxAdapter {
10
+ constructor(session, pane = "0") {
11
+ this.session = session;
12
+ this.pane = pane;
13
+ }
14
+ adapterType = "tmux";
15
+ readScreen() {
16
+ try {
17
+ return execSync(
18
+ `tmux capture-pane -t ${this.session}:${this.pane} -p -S -200`,
19
+ { encoding: "utf-8", timeout: 5e3 }
20
+ ).trimEnd();
21
+ } catch {
22
+ return "";
23
+ }
24
+ }
25
+ sendInput(text, opts) {
26
+ try {
27
+ if (opts?.raw) {
28
+ execSync(
29
+ `tmux send-keys -t ${this.session}:${this.pane} -l ${shellEscape(text)}`,
30
+ { stdio: "ignore", timeout: 5e3 }
31
+ );
32
+ } else {
33
+ execSync(
34
+ `tmux send-keys -t ${this.session}:${this.pane} ${shellEscape(text)} Enter`,
35
+ { stdio: "ignore", timeout: 5e3 }
36
+ );
37
+ }
38
+ } catch {
39
+ }
40
+ }
41
+ sendKey(key) {
42
+ try {
43
+ execSync(`tmux send-keys -t ${this.session}:${this.pane} ${key}`, {
44
+ stdio: "ignore",
45
+ timeout: 5e3
46
+ });
47
+ } catch {
48
+ }
49
+ }
50
+ isAlive() {
51
+ try {
52
+ execSync(`tmux has-session -t ${this.session}`, { stdio: "ignore" });
53
+ return true;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+ clearHistory() {
59
+ try {
60
+ execSync(`tmux clear-history -t ${this.session}:${this.pane}`, {
61
+ stdio: "ignore",
62
+ timeout: 5e3
63
+ });
64
+ } catch {
65
+ }
66
+ }
67
+ }
68
+ class ScreenshotAdapter {
69
+ adapterType = "desktop";
70
+ readScreen() {
71
+ throw new Error(
72
+ "ScreenshotAdapter not implemented \u2014 use TmuxAdapter for CLI mode"
73
+ );
74
+ }
75
+ sendInput(_text, _opts) {
76
+ throw new Error("ScreenshotAdapter not implemented");
77
+ }
78
+ sendKey(_key) {
79
+ throw new Error("ScreenshotAdapter not implemented");
80
+ }
81
+ isAlive() {
82
+ throw new Error("ScreenshotAdapter not implemented");
83
+ }
84
+ clearHistory() {
85
+ throw new Error("ScreenshotAdapter not implemented");
86
+ }
87
+ }
88
+ export {
89
+ ScreenshotAdapter,
90
+ TmuxAdapter
91
+ };
@@ -0,0 +1,127 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import { execSync } from "child_process";
6
+ import {
7
+ isTmuxAvailable,
8
+ sessionExists,
9
+ killTmuxSession,
10
+ sendCtrlC
11
+ } from "../workers/tmux-manager.js";
12
+ import { TmuxAdapter } from "./screen-adapter.js";
13
+ class SessionManager {
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ restartCount = 0;
18
+ /** Preflight: check tmux is available */
19
+ preflight() {
20
+ if (!isTmuxAvailable()) {
21
+ throw new Error(
22
+ "tmux is required but not found. Install with: brew install tmux"
23
+ );
24
+ }
25
+ }
26
+ /** Start a new Claude Code session in tmux. Returns a ScreenAdapter. */
27
+ start() {
28
+ const { sessionName, cwd } = this.config;
29
+ if (sessionExists(sessionName)) {
30
+ try {
31
+ killTmuxSession(sessionName);
32
+ } catch {
33
+ }
34
+ }
35
+ execSync(`tmux new-session -d -s ${sessionName} -c ${shellEscape(cwd)}`, {
36
+ stdio: "ignore",
37
+ timeout: 1e4
38
+ });
39
+ const claudeCmd = this.buildClaudeCommand();
40
+ execSync(
41
+ `tmux send-keys -t ${sessionName}:0 ${shellEscape(claudeCmd)} Enter`,
42
+ { stdio: "ignore", timeout: 5e3 }
43
+ );
44
+ const adapter = new TmuxAdapter(sessionName, "0");
45
+ this.waitForReady(adapter, 3e4);
46
+ this.restartCount = 0;
47
+ return adapter;
48
+ }
49
+ /** Stop the Claude session gracefully */
50
+ stop() {
51
+ const { sessionName } = this.config;
52
+ if (!sessionExists(sessionName)) return;
53
+ try {
54
+ sendCtrlC(sessionName, "0");
55
+ this.sleep(2e3);
56
+ execSync(`tmux send-keys -t ${sessionName}:0 '/exit' Enter`, {
57
+ stdio: "ignore",
58
+ timeout: 5e3
59
+ });
60
+ this.sleep(1e3);
61
+ } catch {
62
+ }
63
+ try {
64
+ killTmuxSession(sessionName);
65
+ } catch {
66
+ }
67
+ }
68
+ /** Restart with exponential cooldown */
69
+ restart() {
70
+ this.restartCount++;
71
+ const cooldownMs = Math.min(
72
+ 5e3 * Math.pow(2, this.restartCount - 1),
73
+ 3e5
74
+ );
75
+ this.sleep(cooldownMs);
76
+ this.stop();
77
+ return this.start();
78
+ }
79
+ /** Get consecutive restart count */
80
+ getRestartCount() {
81
+ return this.restartCount;
82
+ }
83
+ /** Reset restart counter (called when work succeeds) */
84
+ resetRestartCount() {
85
+ this.restartCount = 0;
86
+ }
87
+ /** Attach user terminal to the tmux session for live observation */
88
+ attach() {
89
+ const { sessionName } = this.config;
90
+ if (!sessionExists(sessionName)) {
91
+ throw new Error(
92
+ `No operator session '${sessionName}' running. Start one first.`
93
+ );
94
+ }
95
+ execSync(`tmux attach-session -t ${sessionName}`, { stdio: "inherit" });
96
+ }
97
+ // ── Private ───────────────────────────────────────────
98
+ buildClaudeCommand() {
99
+ const parts = ["claude"];
100
+ if (this.config.model) {
101
+ parts.push("--model", this.config.model);
102
+ }
103
+ return parts.join(" ");
104
+ }
105
+ waitForReady(adapter, timeoutMs) {
106
+ const start = Date.now();
107
+ while (Date.now() - start < timeoutMs) {
108
+ const screen = adapter.readScreen();
109
+ if (/^>\s*$/m.test(screen) || /How can I help/i.test(screen)) {
110
+ return;
111
+ }
112
+ this.sleep(500);
113
+ }
114
+ }
115
+ sleep(ms) {
116
+ try {
117
+ execSync(`sleep ${ms / 1e3}`, { stdio: "ignore" });
118
+ } catch {
119
+ }
120
+ }
121
+ }
122
+ function shellEscape(str) {
123
+ return "'" + str.replace(/'/g, "'\\''") + "'";
124
+ }
125
+ export {
126
+ SessionManager
127
+ };