@stackmemoryai/stackmemory 1.12.0 → 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 (162) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +3 -1
  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/codex-sm.js +51 -11
  11. package/dist/src/cli/commands/brain.js +206 -0
  12. package/dist/src/cli/commands/company-os.js +184 -0
  13. package/dist/src/cli/commands/context.js +5 -0
  14. package/dist/src/cli/commands/operator.js +127 -0
  15. package/dist/src/cli/commands/orchestrate.js +2 -0
  16. package/dist/src/cli/commands/orchestrator.js +3 -2
  17. package/dist/src/cli/commands/patterns.js +254 -0
  18. package/dist/src/cli/commands/portal.js +161 -0
  19. package/dist/src/cli/commands/scaffold.js +92 -0
  20. package/dist/src/cli/commands/setup.js +1 -4
  21. package/dist/src/cli/commands/sync.js +253 -0
  22. package/dist/src/cli/commands/tasks.js +130 -1
  23. package/dist/src/cli/commands/vision.js +221 -0
  24. package/dist/src/cli/hermes-sm.js +224 -0
  25. package/dist/src/cli/index.js +15 -10
  26. package/dist/src/cli/utils/real-cli-bin.js +72 -0
  27. package/dist/src/core/brain/brain-store.js +187 -0
  28. package/dist/src/core/brain/brain-sync.js +193 -0
  29. package/dist/src/core/brain/index.js +78 -0
  30. package/dist/src/core/brain/types.js +10 -0
  31. package/dist/src/core/cache/token-estimator.js +24 -1
  32. package/dist/src/core/config/feature-flags.js +2 -6
  33. package/dist/src/core/context/frame-database.js +44 -0
  34. package/dist/src/core/context/recursive-context-manager.js +1 -1
  35. package/dist/src/core/context/rehydration.js +2 -1
  36. package/dist/src/core/database/sqlite-adapter.js +14 -1
  37. package/dist/src/core/models/model-router.js +33 -1
  38. package/dist/src/core/models/provider-pricing.js +58 -4
  39. package/dist/src/core/patterns/index.js +22 -0
  40. package/dist/src/core/patterns/pattern-applier.js +39 -0
  41. package/dist/src/core/patterns/pattern-observer.js +157 -0
  42. package/dist/src/core/patterns/pattern-store.js +259 -0
  43. package/dist/src/core/patterns/types.js +19 -0
  44. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  45. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  46. package/dist/src/core/skill-packs/types.js +14 -1
  47. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  48. package/dist/src/core/storage/cloud-sync.js +574 -0
  49. package/dist/src/core/storage/two-tier-storage.js +5 -1
  50. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  51. package/dist/src/core/tasks/md-task-parser.js +138 -0
  52. package/dist/src/core/vision/index.js +27 -0
  53. package/dist/src/core/vision/signals.js +79 -0
  54. package/dist/src/core/vision/types.js +22 -0
  55. package/dist/src/core/vision/vision-file.js +146 -0
  56. package/dist/src/core/vision/vision-loop.js +220 -0
  57. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  58. package/dist/src/daemon/daemon-config.js +45 -0
  59. package/dist/src/daemon/services/desire-path-service.js +566 -0
  60. package/dist/src/daemon/services/research-stream-service.js +320 -0
  61. package/dist/src/daemon/services/telemetry-service.js +192 -0
  62. package/dist/src/daemon/unified-daemon.js +28 -1
  63. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  64. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  65. package/dist/src/features/operator/adapter-factory.js +62 -0
  66. package/dist/src/features/operator/browser-adapter.js +109 -0
  67. package/dist/src/features/operator/desktop-adapter.js +125 -0
  68. package/dist/src/features/operator/index.js +39 -0
  69. package/dist/src/features/operator/llm-decision.js +137 -0
  70. package/dist/src/features/operator/operator-logger.js +92 -0
  71. package/dist/src/features/operator/overnight-runner.js +327 -0
  72. package/dist/src/features/operator/screen-adapter.js +91 -0
  73. package/dist/src/features/operator/session-manager.js +127 -0
  74. package/dist/src/features/operator/state-machine.js +227 -0
  75. package/dist/src/features/operator/task-queue.js +81 -0
  76. package/dist/src/features/portal/index.js +26 -0
  77. package/dist/src/features/portal/server.js +240 -0
  78. package/dist/src/features/portal/types.js +14 -0
  79. package/dist/src/features/portal/ui.js +195 -0
  80. package/dist/src/features/tasks/task-aware-context.js +2 -1
  81. package/dist/src/features/tui/simple-monitor.js +0 -23
  82. package/dist/src/features/tui/swarm-monitor.js +8 -66
  83. package/dist/src/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
  84. package/dist/src/features/web/client/lib/utils.js +12 -0
  85. package/dist/src/features/web/client/stores/session-store.js +12 -0
  86. package/dist/src/features/web/server/gcp-billing.js +76 -0
  87. package/dist/src/features/web/server/index.js +10 -0
  88. package/dist/src/features/web/server/spend-calculator.js +228 -0
  89. package/dist/src/hooks/schemas.js +4 -1
  90. package/dist/src/integrations/anthropic/client.js +3 -2
  91. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  92. package/dist/src/integrations/claude-code/subagent-client.js +218 -11
  93. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  94. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  95. package/dist/src/integrations/linear/webhook-server.js +18 -22
  96. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  97. package/dist/src/integrations/mcp/handlers/index.js +27 -52
  98. package/dist/src/integrations/mcp/server.js +122 -335
  99. package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
  100. package/dist/src/integrations/mcp/tool-definitions.js +111 -510
  101. package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
  102. package/dist/src/orchestrators/multimodal/determinism.js +2 -1
  103. package/dist/src/orchestrators/multimodal/harness.js +2 -1
  104. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  105. package/dist/src/utils/process-cleanup.js +1 -7
  106. package/docs/README.md +42 -0
  107. package/docs/guides/README_INSTALL.md +208 -0
  108. package/package.json +18 -9
  109. package/scripts/claude-code-wrapper.sh +11 -0
  110. package/scripts/claude-sm-setup.sh +12 -1
  111. package/scripts/codex-wrapper.sh +11 -0
  112. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  113. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  114. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  115. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  116. package/scripts/hooks/cleanup-shell.sh +12 -1
  117. package/scripts/hooks/task-complete.sh +12 -1
  118. package/scripts/install-code-execution-hooks.sh +12 -1
  119. package/scripts/install-sweep-hook.sh +12 -0
  120. package/scripts/install.sh +11 -0
  121. package/scripts/opencode-wrapper.sh +11 -0
  122. package/scripts/portal/cloud-init.yaml +69 -0
  123. package/scripts/portal/setup.sh +69 -0
  124. package/scripts/portal/stackmemory-portal.service +34 -0
  125. package/scripts/setup-claude-integration.sh +12 -1
  126. package/scripts/smoke-init-db.sh +23 -0
  127. package/scripts/stackmemory-daemon.sh +11 -0
  128. package/scripts/verify-dist.cjs +11 -4
  129. package/dist/src/cli/commands/ralph.js +0 -1053
  130. package/dist/src/hooks/diffmem-hooks.js +0 -376
  131. package/dist/src/integrations/diffmem/client.js +0 -208
  132. package/dist/src/integrations/diffmem/config.js +0 -14
  133. package/dist/src/integrations/greptile/client.js +0 -101
  134. package/dist/src/integrations/greptile/config.js +0 -14
  135. package/dist/src/integrations/greptile/index.js +0 -11
  136. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +0 -188
  137. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  138. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  139. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  140. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  141. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  142. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -354
  143. package/dist/src/integrations/ralph/index.js +0 -17
  144. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
  145. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  146. package/dist/src/integrations/ralph/loopmax.js +0 -488
  147. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  148. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  149. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  150. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  151. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  152. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  153. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  154. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  155. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  156. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  157. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1005
  158. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  159. package/scripts/ralph-loop-implementation.js +0 -404
  160. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  161. /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
  162. /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
@@ -0,0 +1,109 @@
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
+ const SELECTORS = {
6
+ /** Main conversation/output area */
7
+ output: '[data-testid="conversation-panel"], .conversation-content, main',
8
+ /** Input textarea */
9
+ input: 'textarea[placeholder], [contenteditable="true"], .prompt-input',
10
+ /** Permission/approval buttons */
11
+ approveButton: 'button:has-text("Allow"), button:has-text("Approve"), button:has-text("Yes")',
12
+ /** Loading/working indicators */
13
+ spinner: '.loading, .spinner, [data-loading="true"]'
14
+ };
15
+ class BrowserAdapter {
16
+ constructor(page) {
17
+ this.page = page;
18
+ }
19
+ adapterType = "browser";
20
+ lastContent = "";
21
+ readScreen() {
22
+ return this.lastContent;
23
+ }
24
+ /** Async version — call before each tick in the runner */
25
+ async readScreenAsync() {
26
+ try {
27
+ const content = await this.page.locator(SELECTORS.output).textContent();
28
+ this.lastContent = content ?? "";
29
+ } catch {
30
+ this.lastContent = "";
31
+ }
32
+ return this.lastContent;
33
+ }
34
+ readScreenshot() {
35
+ return void 0;
36
+ }
37
+ async readScreenshotAsync() {
38
+ const buffer = await this.page.screenshot({ fullPage: false });
39
+ return {
40
+ base64: buffer.toString("base64"),
41
+ mediaType: "image/png"
42
+ };
43
+ }
44
+ sendInput(text, opts) {
45
+ this.sendInputAsync(text, opts).catch(() => {
46
+ });
47
+ }
48
+ async sendInputAsync(text, opts) {
49
+ try {
50
+ const inputLocator = this.page.locator(SELECTORS.input);
51
+ const count = await inputLocator.count();
52
+ if (count === 0) return;
53
+ await inputLocator.fill(text);
54
+ if (!opts?.raw) {
55
+ await this.page.keyboard.press("Enter");
56
+ }
57
+ } catch {
58
+ }
59
+ }
60
+ sendKey(key) {
61
+ this.sendKeyAsync(key).catch(() => {
62
+ });
63
+ }
64
+ async sendKeyAsync(key) {
65
+ const keyMap = {
66
+ Enter: "Enter",
67
+ "C-c": "Control+c",
68
+ Escape: "Escape",
69
+ Tab: "Tab",
70
+ y: "y",
71
+ n: "n"
72
+ };
73
+ try {
74
+ if (key === "y") {
75
+ const approveBtn = this.page.locator(SELECTORS.approveButton);
76
+ if (await approveBtn.count() > 0) {
77
+ await approveBtn.click();
78
+ return;
79
+ }
80
+ }
81
+ await this.page.keyboard.press(keyMap[key] ?? key);
82
+ } catch {
83
+ }
84
+ }
85
+ isAlive() {
86
+ try {
87
+ return !this.page.isClosed();
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
92
+ clearHistory() {
93
+ }
94
+ /** Check if a spinner/loading indicator is visible */
95
+ async isWorking() {
96
+ try {
97
+ return await this.page.locator(SELECTORS.spinner).isVisible();
98
+ } catch {
99
+ return false;
100
+ }
101
+ }
102
+ /** Navigate to Claude Code web app */
103
+ async navigate(url = "https://claude.ai/code") {
104
+ await this.page.goto(url, { waitUntil: "networkidle" });
105
+ }
106
+ }
107
+ export {
108
+ BrowserAdapter
109
+ };
@@ -0,0 +1,125 @@
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 { readFileSync, existsSync } from "fs";
7
+ import { join } from "path";
8
+ import { tmpdir } from "os";
9
+ const SCREENSHOT_PATH = join(tmpdir(), "operator-screenshot.png");
10
+ class DesktopAdapter {
11
+ constructor(appName = "Claude", llmConfig) {
12
+ this.appName = appName;
13
+ this.llmConfig = llmConfig;
14
+ }
15
+ adapterType = "desktop";
16
+ lastClassifiedText = "";
17
+ readScreen() {
18
+ this.captureWindow();
19
+ const screenshot = this.readScreenshotFile();
20
+ if (!screenshot) return this.lastClassifiedText;
21
+ try {
22
+ const result = execSync(
23
+ `node -e "
24
+ const { classifyScreenState } = require('${__dirname}/llm-decision.js');
25
+ classifyScreenState(
26
+ { base64: '${screenshot.base64.slice(0, 100)}...', mediaType: '${screenshot.mediaType}' },
27
+ { apiKey: '${this.llmConfig.apiKey}' }
28
+ ).then(r => process.stdout.write(JSON.stringify(r)));
29
+ "`,
30
+ { encoding: "utf-8", timeout: 15e3 }
31
+ );
32
+ this.lastClassifiedText = result;
33
+ } catch {
34
+ }
35
+ return this.lastClassifiedText;
36
+ }
37
+ readScreenshot() {
38
+ this.captureWindow();
39
+ return this.readScreenshotFile();
40
+ }
41
+ sendInput(text, opts) {
42
+ this.activateApp();
43
+ const escaped = text.replace(/"/g, '\\"').replace(/\\/g, "\\\\");
44
+ try {
45
+ execSync(
46
+ `osascript -e 'tell application "System Events" to keystroke "${escaped}"'`,
47
+ { stdio: "ignore", timeout: 5e3 }
48
+ );
49
+ if (!opts?.raw) {
50
+ execSync(
51
+ `osascript -e 'tell application "System Events" to key code 36'`,
52
+ { stdio: "ignore", timeout: 5e3 }
53
+ );
54
+ }
55
+ } catch {
56
+ }
57
+ }
58
+ sendKey(key) {
59
+ this.activateApp();
60
+ const keyMap = {
61
+ Enter: "key code 36",
62
+ "C-c": 'keystroke "c" using control down',
63
+ Escape: "key code 53",
64
+ Tab: "key code 48",
65
+ y: 'keystroke "y"',
66
+ n: 'keystroke "n"'
67
+ };
68
+ const action = keyMap[key] ?? `keystroke "${key}"`;
69
+ try {
70
+ execSync(`osascript -e 'tell application "System Events" to ${action}'`, {
71
+ stdio: "ignore",
72
+ timeout: 5e3
73
+ });
74
+ } catch {
75
+ }
76
+ }
77
+ isAlive() {
78
+ try {
79
+ const result = execSync(
80
+ `osascript -e 'tell application "System Events" to (name of processes) contains "${this.appName}"'`,
81
+ { encoding: "utf-8", timeout: 5e3 }
82
+ ).trim();
83
+ return result === "true";
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+ clearHistory() {
89
+ }
90
+ // ── Private ───────────────────────────────────────────
91
+ captureWindow() {
92
+ try {
93
+ execSync(`screencapture -x -o ${SCREENSHOT_PATH}`, {
94
+ stdio: "ignore",
95
+ timeout: 5e3
96
+ });
97
+ } catch {
98
+ }
99
+ }
100
+ readScreenshotFile() {
101
+ if (!existsSync(SCREENSHOT_PATH)) return void 0;
102
+ try {
103
+ const data = readFileSync(SCREENSHOT_PATH);
104
+ return {
105
+ base64: data.toString("base64"),
106
+ mediaType: "image/png"
107
+ };
108
+ } catch {
109
+ return void 0;
110
+ }
111
+ }
112
+ activateApp() {
113
+ try {
114
+ execSync(
115
+ `osascript -e 'tell application "${this.appName}" to activate'`,
116
+ { stdio: "ignore", timeout: 5e3 }
117
+ );
118
+ execSync("sleep 0.3", { stdio: "ignore" });
119
+ } catch {
120
+ }
121
+ }
122
+ }
123
+ export {
124
+ DesktopAdapter
125
+ };
@@ -0,0 +1,39 @@
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 { TmuxAdapter, ScreenshotAdapter } from "./screen-adapter.js";
6
+ import { DesktopAdapter } from "./desktop-adapter.js";
7
+ import { BrowserAdapter } from "./browser-adapter.js";
8
+ import { createAdapter } from "./adapter-factory.js";
9
+ import {
10
+ detectState,
11
+ decideAction,
12
+ detectCompletion
13
+ } from "./state-machine.js";
14
+ import {
15
+ classifyScreenState,
16
+ generateNudge,
17
+ generateRecoveryPrompt
18
+ } from "./llm-decision.js";
19
+ import { TaskQueue } from "./task-queue.js";
20
+ import { SessionManager } from "./session-manager.js";
21
+ import { OvernightRunner } from "./overnight-runner.js";
22
+ import { OperatorLogger } from "./operator-logger.js";
23
+ export {
24
+ BrowserAdapter,
25
+ DesktopAdapter,
26
+ OperatorLogger,
27
+ OvernightRunner,
28
+ ScreenshotAdapter,
29
+ SessionManager,
30
+ TaskQueue,
31
+ TmuxAdapter,
32
+ classifyScreenState,
33
+ createAdapter,
34
+ decideAction,
35
+ detectCompletion,
36
+ detectState,
37
+ generateNudge,
38
+ generateRecoveryPrompt
39
+ };
@@ -0,0 +1,137 @@
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
+ const ANTHROPIC_API_URL = "https://api.anthropic.com/v1/messages";
6
+ const CLASSIFY_SYSTEM = `You are a screen state classifier for Claude Code CLI sessions.
7
+ Given screen content (text or screenshot), classify the state as exactly one of:
8
+ IDLE, WORKING, PERMISSION_PROMPT, ERROR, COMPLETE, STUCK, RATE_LIMITED, SESSION_ENDED, UNKNOWN
9
+
10
+ Respond with ONLY a JSON object:
11
+ {"state": "<STATE>", "confidence": "high"|"medium"|"low", "detail": "<brief reason>"}
12
+
13
+ Rules:
14
+ - PERMISSION_PROMPT: Claude is asking for user approval (Y/n, Allow, Proceed?)
15
+ - RATE_LIMITED: usage limit, rate limit, 429, "try again"
16
+ - SESSION_ENDED: shell prompt visible, process exited, no Claude session
17
+ - ERROR: error message not inside a code block or file read
18
+ - IDLE: Claude's ">" prompt visible, waiting for input
19
+ - WORKING: Claude is reading files, searching, running commands
20
+ - COMPLETE: task finished, "TASK COMPLETE" visible
21
+ - STUCK: same content repeated, no progress indicators
22
+ - UNKNOWN: cannot determine`;
23
+ async function classifyScreenState(content, config) {
24
+ const userContent = [];
25
+ if (typeof content === "string") {
26
+ userContent.push({
27
+ type: "text",
28
+ text: `Screen content:
29
+ ${content.slice(-3e3)}`
30
+ });
31
+ } else {
32
+ userContent.push({
33
+ type: "image",
34
+ source: {
35
+ type: "base64",
36
+ media_type: content.mediaType,
37
+ data: content.base64
38
+ }
39
+ });
40
+ userContent.push({
41
+ type: "text",
42
+ text: "Classify the state of this Claude Code session screenshot."
43
+ });
44
+ }
45
+ try {
46
+ const response = await callHaiku(config, CLASSIFY_SYSTEM, userContent);
47
+ const parsed = JSON.parse(response);
48
+ return {
49
+ state: parsed.state,
50
+ confidence: parsed.confidence ?? "medium",
51
+ detail: parsed.detail
52
+ };
53
+ } catch {
54
+ return {
55
+ state: "UNKNOWN",
56
+ confidence: "low",
57
+ detail: "LLM classification failed"
58
+ };
59
+ }
60
+ }
61
+ const NUDGE_SYSTEM = `You are an operator for an autonomous Claude Code session.
62
+ The inner Claude session appears stuck or going in circles.
63
+
64
+ Given the recent screen content and task context, generate a SHORT nudge message
65
+ (1-3 sentences) to get Claude back on track. Be direct and specific.
66
+
67
+ Examples of good nudges:
68
+ - "The tests are failing because of a missing import. Try: import { Foo } from './foo.js'"
69
+ - "You've been editing the same file for 5 minutes. Run the tests to check progress."
70
+ - "Stop investigating \u2014 just fix the type error on line 42 and move on."
71
+
72
+ Respond with ONLY the nudge text, no JSON wrapper.`;
73
+ async function generateNudge(screenContent, taskDescription, config) {
74
+ const prompt = `Task: ${taskDescription}
75
+
76
+ Recent screen:
77
+ ${screenContent.slice(-2e3)}`;
78
+ try {
79
+ return await callHaiku(config, NUDGE_SYSTEM, [
80
+ { type: "text", text: prompt }
81
+ ]);
82
+ } catch {
83
+ return void 0;
84
+ }
85
+ }
86
+ const RECOVERY_SYSTEM = `You are an operator for an autonomous Claude Code session.
87
+ The session has encountered an error or is blocked.
88
+
89
+ Given the error context, generate a recovery prompt that Claude can use to fix the issue.
90
+ Be specific: name files, error messages, and concrete next steps.
91
+
92
+ Respond with ONLY the recovery prompt text.`;
93
+ async function generateRecoveryPrompt(screenContent, errorDetail, checkpoint, config) {
94
+ const context = [
95
+ `Error: ${errorDetail}`,
96
+ `Current task: ${checkpoint.currentTaskId ?? "none"}`,
97
+ `Restarts so far: ${checkpoint.totalRestarts}`,
98
+ `
99
+ Recent screen:
100
+ ${screenContent.slice(-2e3)}`
101
+ ].join("\n");
102
+ try {
103
+ return await callHaiku(config, RECOVERY_SYSTEM, [
104
+ { type: "text", text: context }
105
+ ]);
106
+ } catch {
107
+ return void 0;
108
+ }
109
+ }
110
+ async function callHaiku(config, system, content) {
111
+ const model = config.model ?? "claude-haiku-4-5-20251001";
112
+ const maxTokens = config.maxTokens ?? 256;
113
+ const response = await fetch(ANTHROPIC_API_URL, {
114
+ method: "POST",
115
+ headers: {
116
+ "Content-Type": "application/json",
117
+ "x-api-key": config.apiKey,
118
+ "anthropic-version": "2023-06-01"
119
+ },
120
+ body: JSON.stringify({
121
+ model,
122
+ max_tokens: maxTokens,
123
+ system,
124
+ messages: [{ role: "user", content }]
125
+ })
126
+ });
127
+ if (!response.ok) {
128
+ throw new Error(`Haiku API ${response.status}: ${await response.text()}`);
129
+ }
130
+ const data = await response.json();
131
+ return data.content[0]?.text ?? "";
132
+ }
133
+ export {
134
+ classifyScreenState,
135
+ generateNudge,
136
+ generateRecoveryPrompt
137
+ };
@@ -0,0 +1,92 @@
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 {
6
+ mkdirSync,
7
+ appendFileSync,
8
+ writeFileSync,
9
+ existsSync,
10
+ readFileSync
11
+ } from "fs";
12
+ import { join } from "path";
13
+ class OperatorLogger {
14
+ constructor(logDir, checkpointDir) {
15
+ this.checkpointDir = checkpointDir;
16
+ mkdirSync(logDir, { recursive: true });
17
+ mkdirSync(checkpointDir, { recursive: true });
18
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
19
+ this.logFile = join(logDir, `operator-${ts}.jsonl`);
20
+ this.checkpointFile = join(checkpointDir, "checkpoint.json");
21
+ }
22
+ logFile;
23
+ checkpointFile;
24
+ /** Log a tick (called each poll cycle) */
25
+ logTick(entry) {
26
+ const tick = { timestamp: Date.now(), ...entry };
27
+ this.appendLine(tick);
28
+ }
29
+ /** Log a named event */
30
+ logEvent(event, data) {
31
+ const entry = { timestamp: Date.now(), event, data };
32
+ this.appendLine(entry);
33
+ const msg = data ? `[operator] ${event}: ${JSON.stringify(data)}` : `[operator] ${event}`;
34
+ process.stderr.write(msg + "\n");
35
+ }
36
+ /** Write checkpoint file for status reads */
37
+ writeCheckpoint(checkpoint) {
38
+ writeFileSync(
39
+ this.checkpointFile,
40
+ JSON.stringify(checkpoint, null, 2),
41
+ "utf-8"
42
+ );
43
+ }
44
+ /** Read checkpoint from disk (for status command) */
45
+ static readCheckpoint(checkpointDir) {
46
+ const file = join(checkpointDir, "checkpoint.json");
47
+ if (!existsSync(file)) return void 0;
48
+ try {
49
+ return JSON.parse(readFileSync(file, "utf-8"));
50
+ } catch {
51
+ return void 0;
52
+ }
53
+ }
54
+ /** Print summary to stderr */
55
+ printSummary(summary) {
56
+ const runtime = formatDuration(summary.totalRuntimeMs);
57
+ const lines = [
58
+ "",
59
+ "=== Operator Summary ===",
60
+ `Runtime: ${runtime}`,
61
+ `Completed: ${summary.tasksCompleted}`,
62
+ `Blocked: ${summary.tasksBlocked}`,
63
+ `Remaining: ${summary.tasksRemaining}`,
64
+ `Restarts: ${summary.totalRestarts}`,
65
+ `Approvals: ${summary.totalPermissionApprovals}`,
66
+ `Rate limits: ${summary.totalRateLimitHits}`,
67
+ `Log file: ${this.logFile}`,
68
+ `Checkpoint: ${this.checkpointFile}`,
69
+ "========================",
70
+ ""
71
+ ];
72
+ process.stderr.write(lines.join("\n"));
73
+ }
74
+ getLogFile() {
75
+ return this.logFile;
76
+ }
77
+ // ── Private ───────────────────────────────────────────
78
+ appendLine(entry) {
79
+ appendFileSync(this.logFile, JSON.stringify(entry) + "\n", "utf-8");
80
+ }
81
+ }
82
+ function formatDuration(ms) {
83
+ const secs = Math.floor(ms / 1e3);
84
+ const mins = Math.floor(secs / 60);
85
+ const hrs = Math.floor(mins / 60);
86
+ if (hrs > 0) return `${hrs}h ${mins % 60}m`;
87
+ if (mins > 0) return `${mins}m ${secs % 60}s`;
88
+ return `${secs}s`;
89
+ }
90
+ export {
91
+ OperatorLogger
92
+ };