@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,417 @@
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 { spawn } from "child_process";
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
+ import { join } from "path";
8
+ import { homedir } from "os";
9
+ import { createHash } from "crypto";
10
+ import { logger } from "../../core/monitoring/logger.js";
11
+ const DEFAULT_CACHE_DIR = join(
12
+ homedir(),
13
+ ".stackmemory",
14
+ "workflows",
15
+ "cli-cache"
16
+ );
17
+ const DEFAULT_CLI_TIMEOUT = 6e4;
18
+ const DEFAULT_MAX_SNAPSHOT = 15e3;
19
+ class CliBrowserAgent {
20
+ config;
21
+ cache = /* @__PURE__ */ new Map();
22
+ page = null;
23
+ // Playwright Page
24
+ browser = null;
25
+ // Playwright Browser
26
+ constructor(config) {
27
+ this.config = {
28
+ provider: config.provider,
29
+ headless: config.headless ?? true,
30
+ cacheDir: config.cacheDir ?? DEFAULT_CACHE_DIR,
31
+ cliTimeout: config.cliTimeout ?? DEFAULT_CLI_TIMEOUT,
32
+ maxSnapshotSize: config.maxSnapshotSize ?? DEFAULT_MAX_SNAPSHOT
33
+ };
34
+ this.loadCache();
35
+ }
36
+ /** Initialize — launch browser */
37
+ async init() {
38
+ const pw = await import("playwright");
39
+ this.browser = await pw.chromium.launch({ headless: this.config.headless });
40
+ this.page = await this.browser.newPage();
41
+ logger.info("CliBrowserAgent initialized", {
42
+ provider: this.config.provider
43
+ });
44
+ }
45
+ /** Navigate to URL */
46
+ async goto(url) {
47
+ await this.page.goto(url, { waitUntil: "domcontentloaded" });
48
+ }
49
+ /** Current page URL */
50
+ url() {
51
+ return this.page?.url() || "";
52
+ }
53
+ /**
54
+ * Extract structured data from the current page.
55
+ * Checks cache first → on miss, sends DOM snapshot to CLI for interpretation.
56
+ */
57
+ async extract(instruction) {
58
+ const start = Date.now();
59
+ const cacheKey = this.buildCacheKey("extract", instruction);
60
+ const cached = this.cache.get(cacheKey);
61
+ if (cached) {
62
+ cached.hits++;
63
+ this.saveCache();
64
+ return {
65
+ data: cached.result,
66
+ fromCache: true,
67
+ cliTokens: 0,
68
+ duration: Date.now() - start
69
+ };
70
+ }
71
+ const snapshot = await this.getA11ySnapshot();
72
+ const prompt = buildExtractPrompt(instruction, snapshot, this.url());
73
+ const cliResult = await this.callCli(prompt);
74
+ let data;
75
+ try {
76
+ data = JSON.parse(cliResult.text);
77
+ } catch {
78
+ const jsonMatch = cliResult.text.match(/```(?:json)?\s*([\s\S]*?)```/);
79
+ if (jsonMatch) {
80
+ data = JSON.parse(jsonMatch[1].trim());
81
+ } else {
82
+ data = { raw: cliResult.text };
83
+ }
84
+ }
85
+ this.cache.set(cacheKey, {
86
+ instruction,
87
+ urlPattern: extractUrlPattern(this.url()),
88
+ result: data,
89
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
90
+ hits: 0
91
+ });
92
+ this.saveCache();
93
+ return {
94
+ data,
95
+ fromCache: false,
96
+ cliTokens: cliResult.tokens,
97
+ duration: Date.now() - start
98
+ };
99
+ }
100
+ /**
101
+ * Perform a natural language action on the page.
102
+ * Checks cache for matching selectors → on miss, asks CLI to identify the right element.
103
+ */
104
+ async act(instruction) {
105
+ const start = Date.now();
106
+ const cacheKey = this.buildCacheKey("act", instruction);
107
+ const cached = this.cache.get(cacheKey);
108
+ if (cached?.actions?.length) {
109
+ try {
110
+ await this.executeActions(cached.actions);
111
+ cached.hits++;
112
+ this.saveCache();
113
+ return {
114
+ success: true,
115
+ fromCache: true,
116
+ cliTokens: 0,
117
+ duration: Date.now() - start,
118
+ actions: cached.actions
119
+ };
120
+ } catch {
121
+ logger.info("Cached action failed, self-healing via CLI", {
122
+ instruction
123
+ });
124
+ }
125
+ }
126
+ const snapshot = await this.getA11ySnapshot();
127
+ const prompt = buildActPrompt(instruction, snapshot, this.url());
128
+ const cliResult = await this.callCli(prompt);
129
+ let actions;
130
+ try {
131
+ const parsed = JSON.parse(cliResult.text);
132
+ actions = Array.isArray(parsed) ? parsed : parsed.actions || [parsed];
133
+ } catch {
134
+ const jsonMatch = cliResult.text.match(/```(?:json)?\s*([\s\S]*?)```/);
135
+ if (jsonMatch) {
136
+ const parsed = JSON.parse(jsonMatch[1].trim());
137
+ actions = Array.isArray(parsed) ? parsed : parsed.actions || [parsed];
138
+ } else {
139
+ return {
140
+ success: false,
141
+ fromCache: false,
142
+ cliTokens: cliResult.tokens,
143
+ duration: Date.now() - start,
144
+ actions: [],
145
+ error: `Failed to parse CLI response: ${cliResult.text.slice(0, 200)}`
146
+ };
147
+ }
148
+ }
149
+ try {
150
+ await this.executeActions(actions);
151
+ } catch (e) {
152
+ return {
153
+ success: false,
154
+ fromCache: false,
155
+ cliTokens: cliResult.tokens,
156
+ duration: Date.now() - start,
157
+ actions,
158
+ error: e.message
159
+ };
160
+ }
161
+ this.cache.set(cacheKey, {
162
+ instruction,
163
+ urlPattern: extractUrlPattern(this.url()),
164
+ result: { success: true },
165
+ actions,
166
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
167
+ hits: 0
168
+ });
169
+ this.saveCache();
170
+ return {
171
+ success: true,
172
+ fromCache: false,
173
+ cliTokens: cliResult.tokens,
174
+ duration: Date.now() - start,
175
+ actions
176
+ };
177
+ }
178
+ /** Get the Playwright page for direct Playwright code */
179
+ getPage() {
180
+ return this.page;
181
+ }
182
+ /** Close browser */
183
+ async close() {
184
+ if (this.browser) {
185
+ await this.browser.close();
186
+ this.browser = null;
187
+ this.page = null;
188
+ }
189
+ }
190
+ // ─── Internal ─────────────────────────────────────────────
191
+ /** Get page content snapshot — prefer visible text (fast + small) */
192
+ async getA11ySnapshot() {
193
+ const text = await this.page.evaluate(
194
+ (max) => document.body.innerText.slice(0, max),
195
+ this.config.maxSnapshotSize
196
+ );
197
+ return text;
198
+ }
199
+ /**
200
+ * Call LLM for interpretation.
201
+ * Uses CLI wrapper (claude/codex) or falls back to direct API.
202
+ * CLI wrappers use subscription (no per-token cost).
203
+ */
204
+ async callCli(prompt) {
205
+ try {
206
+ return await this.callCliSubprocess(prompt);
207
+ } catch (cliError) {
208
+ logger.warn("CLI wrapper failed, trying direct API fallback", {
209
+ error: cliError.message
210
+ });
211
+ }
212
+ if (process.env.ANTHROPIC_API_KEY) {
213
+ return this.callAnthropicDirect(prompt);
214
+ }
215
+ throw new Error(
216
+ "No LLM backend available. Need claude CLI or ANTHROPIC_API_KEY."
217
+ );
218
+ }
219
+ /** Call via CLI subprocess */
220
+ callCliSubprocess(prompt) {
221
+ return new Promise((resolve, reject) => {
222
+ const { cmd, args } = this.config.provider === "claude" ? { cmd: "claude", args: ["--print", "--model", "sonnet"] } : { cmd: "codex", args: ["-q"] };
223
+ const proc = spawn(cmd, args, {
224
+ cwd: process.cwd(),
225
+ stdio: ["pipe", "pipe", "pipe"],
226
+ timeout: this.config.cliTimeout,
227
+ env: {
228
+ ...process.env,
229
+ DISABLE_HOOKS: "1",
230
+ STACKMEMORY_DESIRE_PATHS: "0",
231
+ // Skip all Claude Code hooks
232
+ CLAUDE_CODE_SKIP_HOOKS: "1"
233
+ }
234
+ });
235
+ proc.stdin.write(prompt);
236
+ proc.stdin.end();
237
+ let stdout = "";
238
+ let stderr = "";
239
+ proc.stdout.on("data", (d) => {
240
+ stdout += d.toString();
241
+ });
242
+ proc.stderr.on("data", (d) => {
243
+ stderr += d.toString();
244
+ });
245
+ proc.on("close", (code) => {
246
+ const text = stdout.trim();
247
+ if (!text) {
248
+ reject(
249
+ new Error(
250
+ `CLI exited ${code} with no output: ${stderr.slice(0, 300)}`
251
+ )
252
+ );
253
+ return;
254
+ }
255
+ let finalText = text;
256
+ if (this.config.provider === "codex") {
257
+ try {
258
+ const parsed = JSON.parse(text);
259
+ finalText = parsed.message || parsed.output || text;
260
+ } catch {
261
+ }
262
+ }
263
+ const tokens = Math.ceil((prompt.length + finalText.length) / 4);
264
+ resolve({ text: finalText, tokens });
265
+ });
266
+ proc.on("error", (err) => {
267
+ reject(new Error(`Failed to spawn ${cmd}: ${err.message}`));
268
+ });
269
+ });
270
+ }
271
+ /** Direct Anthropic API call (fallback when CLI hooks interfere) */
272
+ async callAnthropicDirect(prompt) {
273
+ const resp = await fetch("https://api.anthropic.com/v1/messages", {
274
+ method: "POST",
275
+ headers: {
276
+ "Content-Type": "application/json",
277
+ "x-api-key": process.env.ANTHROPIC_API_KEY,
278
+ "anthropic-version": "2023-06-01"
279
+ },
280
+ body: JSON.stringify({
281
+ model: "claude-sonnet-4-20250514",
282
+ max_tokens: 2048,
283
+ messages: [{ role: "user", content: prompt }]
284
+ })
285
+ });
286
+ if (!resp.ok) {
287
+ throw new Error(`Anthropic API ${resp.status}: ${await resp.text()}`);
288
+ }
289
+ const data = await resp.json();
290
+ const text = data.content?.[0]?.text || "";
291
+ const tokens = (data.usage?.input_tokens || 0) + (data.usage?.output_tokens || 0);
292
+ return { text, tokens };
293
+ }
294
+ /** Execute Playwright actions from CLI response */
295
+ async executeActions(actions) {
296
+ for (const action of actions) {
297
+ const el = this.page.locator(action.selector).first();
298
+ await el.waitFor({ timeout: 5e3 });
299
+ switch (action.action) {
300
+ case "click":
301
+ await el.click();
302
+ break;
303
+ case "fill":
304
+ await el.fill(action.value || "");
305
+ break;
306
+ case "select":
307
+ await el.selectOption(action.value || "");
308
+ break;
309
+ case "check":
310
+ await el.check();
311
+ break;
312
+ case "press":
313
+ await el.press(action.value || "Enter");
314
+ break;
315
+ }
316
+ await this.page.waitForTimeout(200);
317
+ }
318
+ }
319
+ /** Build cache key from instruction + URL pattern */
320
+ buildCacheKey(type, instruction) {
321
+ const urlPattern = extractUrlPattern(this.url());
322
+ const input = `${type}:${urlPattern}:${instruction}`;
323
+ return createHash("sha256").update(input).digest("hex").slice(0, 16);
324
+ }
325
+ /** Load cache from disk */
326
+ loadCache() {
327
+ const cacheFile = join(this.config.cacheDir, "cache.json");
328
+ if (!existsSync(cacheFile)) return;
329
+ try {
330
+ const data = JSON.parse(readFileSync(cacheFile, "utf-8"));
331
+ for (const [key, entry] of Object.entries(data)) {
332
+ this.cache.set(key, entry);
333
+ }
334
+ } catch {
335
+ }
336
+ }
337
+ /** Save cache to disk */
338
+ saveCache() {
339
+ ensureDir(this.config.cacheDir);
340
+ const cacheFile = join(this.config.cacheDir, "cache.json");
341
+ const data = {};
342
+ for (const [key, entry] of this.cache) {
343
+ data[key] = entry;
344
+ }
345
+ writeFileSync(cacheFile, JSON.stringify(data, null, 2));
346
+ }
347
+ }
348
+ function buildExtractPrompt(instruction, snapshot, url) {
349
+ return `You are a browser data extraction assistant. Given the accessibility tree of a web page, extract the requested data as JSON.
350
+
351
+ Page URL: ${url}
352
+
353
+ Accessibility tree:
354
+ ${snapshot}
355
+
356
+ Instruction: ${instruction}
357
+
358
+ Respond with ONLY a JSON object containing the extracted data. No explanation, no markdown, just JSON.`;
359
+ }
360
+ function buildActPrompt(instruction, snapshot, url) {
361
+ return `You are a browser automation assistant. Given the accessibility tree of a web page, determine which element(s) to interact with.
362
+
363
+ Page URL: ${url}
364
+
365
+ Accessibility tree:
366
+ ${snapshot}
367
+
368
+ Instruction: ${instruction}
369
+
370
+ Respond with ONLY a JSON array of actions. Each action has:
371
+ - "selector": CSS selector or text selector (prefer [role], [aria-label], text= selectors)
372
+ - "action": "click" | "fill" | "select" | "check" | "press"
373
+ - "value": (optional) text to type or option to select
374
+ - "description": brief description of what this does
375
+
376
+ Example: [{"selector": "button:has-text('Submit')", "action": "click", "description": "Click submit button"}]
377
+
378
+ No explanation, no markdown, just JSON array.`;
379
+ }
380
+ function ensureDir(dir) {
381
+ if (!existsSync(dir)) {
382
+ mkdirSync(dir, { recursive: true });
383
+ }
384
+ }
385
+ function extractUrlPattern(url) {
386
+ try {
387
+ const u = new URL(url);
388
+ return `${u.host}${u.pathname}`;
389
+ } catch {
390
+ return url;
391
+ }
392
+ }
393
+ function formatA11yTree(node, indent, maxSize) {
394
+ if (!node) return "(empty page)";
395
+ let result = "";
396
+ const role = node.role || "";
397
+ const name = node.name || "";
398
+ const value = node.value || "";
399
+ if (role && role !== "none" && role !== "generic") {
400
+ result += `${indent}[${role}] ${name}`;
401
+ if (value) result += ` = "${value}"`;
402
+ result += "\n";
403
+ }
404
+ if (result.length > maxSize) {
405
+ return result.slice(0, maxSize) + "\n... (truncated)";
406
+ }
407
+ if (node.children) {
408
+ for (const child of node.children) {
409
+ result += formatA11yTree(child, indent + " ", maxSize - result.length);
410
+ if (result.length > maxSize) break;
411
+ }
412
+ }
413
+ return result;
414
+ }
415
+ export {
416
+ CliBrowserAgent
417
+ };