@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,578 @@
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
+ existsSync,
7
+ mkdirSync,
8
+ appendFileSync,
9
+ readFileSync,
10
+ writeFileSync
11
+ } from "fs";
12
+ import { join } from "path";
13
+ import { homedir } from "os";
14
+ import { randomUUID, createHash } from "crypto";
15
+ import { logger } from "../../core/monitoring/logger.js";
16
+ const SM_DIR = join(homedir(), ".stackmemory");
17
+ const DP_DIR = join(SM_DIR, "desire-paths");
18
+ const WORKFLOW_DIR = join(SM_DIR, "workflows");
19
+ const STREAM_FILE = join(DP_DIR, "action-stream.jsonl");
20
+ const WORKFLOW_CACHE_FILE = join(WORKFLOW_DIR, "cached-workflows.json");
21
+ const BENCHMARK_FILE = join(WORKFLOW_DIR, "benchmarks.jsonl");
22
+ class StagehandWorkflowCapture {
23
+ stagehand;
24
+ // Stagehand instance (peer dep, not typed)
25
+ sessionId;
26
+ recording = false;
27
+ currentWorkflow = null;
28
+ steps = [];
29
+ constructor(stagehandInstance, sessionId) {
30
+ this.stagehand = stagehandInstance;
31
+ this.sessionId = sessionId || `wf-${Date.now()}-${randomUUID().slice(0, 8)}`;
32
+ ensureDir(DP_DIR);
33
+ ensureDir(WORKFLOW_DIR);
34
+ }
35
+ /** Start recording a workflow */
36
+ startCapture(name) {
37
+ this.recording = true;
38
+ this.steps = [];
39
+ const page = this.stagehand.context?.pages?.()?.[0];
40
+ const startUrl = page?.url?.() || "about:blank";
41
+ this.currentWorkflow = {
42
+ id: randomUUID(),
43
+ name,
44
+ startUrl,
45
+ steps: [],
46
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
47
+ totalDuration: 0,
48
+ totalTokens: 0,
49
+ sessionId: this.sessionId
50
+ };
51
+ logger.info("Workflow capture started", { name, startUrl });
52
+ }
53
+ /** Navigate to a URL */
54
+ async navigate(url) {
55
+ const start = Date.now();
56
+ const page = this.stagehand.context.pages()[0];
57
+ await page.goto(url);
58
+ const dur = Date.now() - start;
59
+ this.recordStep({
60
+ type: "navigate",
61
+ instruction: url,
62
+ url,
63
+ duration: dur,
64
+ tokens: 0,
65
+ cached: false
66
+ });
67
+ }
68
+ /** Execute a Stagehand act() — natural language browser action */
69
+ async act(instruction, options) {
70
+ const start = Date.now();
71
+ const result = await this.stagehand.act(instruction, options);
72
+ const dur = Date.now() - start;
73
+ const page = this.stagehand.context.pages()[0];
74
+ const url = page?.url?.() || "";
75
+ const tokens = estimateActTokens(instruction);
76
+ this.recordStep({
77
+ type: "act",
78
+ instruction,
79
+ url,
80
+ duration: dur,
81
+ tokens,
82
+ cached: false,
83
+ // TODO: detect from Stagehand cache hits
84
+ result
85
+ });
86
+ return result;
87
+ }
88
+ /** Execute a Stagehand extract() — structured data extraction */
89
+ async extract(instruction, schema, options) {
90
+ const start = Date.now();
91
+ const result = await this.stagehand.extract(instruction, schema, options);
92
+ const dur = Date.now() - start;
93
+ const page = this.stagehand.context.pages()[0];
94
+ const url = page?.url?.() || "";
95
+ const tokens = estimateActTokens(instruction);
96
+ this.recordStep({
97
+ type: "extract",
98
+ instruction,
99
+ url,
100
+ duration: dur,
101
+ tokens,
102
+ cached: false,
103
+ result
104
+ });
105
+ return result;
106
+ }
107
+ /** Execute a Stagehand observe() — discover available actions */
108
+ async observe(instruction) {
109
+ const start = Date.now();
110
+ const result = await this.stagehand.observe(instruction);
111
+ const dur = Date.now() - start;
112
+ const page = this.stagehand.context.pages()[0];
113
+ const url = page?.url?.() || "";
114
+ this.recordStep({
115
+ type: "observe",
116
+ instruction,
117
+ url,
118
+ duration: dur,
119
+ tokens: estimateActTokens(instruction),
120
+ cached: false,
121
+ result
122
+ });
123
+ return result;
124
+ }
125
+ /** Stop recording and return the captured workflow */
126
+ stopCapture() {
127
+ if (!this.currentWorkflow) return null;
128
+ this.recording = false;
129
+ this.currentWorkflow.steps = [...this.steps];
130
+ this.currentWorkflow.totalDuration = this.steps.reduce(
131
+ (sum, s) => sum + s.duration,
132
+ 0
133
+ );
134
+ this.currentWorkflow.totalTokens = this.steps.reduce(
135
+ (sum, s) => sum + s.tokens,
136
+ 0
137
+ );
138
+ saveWorkflow(this.currentWorkflow);
139
+ const desirePathPattern = workflowToDesirePathPattern(this.currentWorkflow);
140
+ appendDesirePathPattern(desirePathPattern);
141
+ logger.info("Workflow capture stopped", {
142
+ id: this.currentWorkflow.id,
143
+ steps: this.steps.length,
144
+ duration: this.currentWorkflow.totalDuration
145
+ });
146
+ const wf = this.currentWorkflow;
147
+ this.currentWorkflow = null;
148
+ this.steps = [];
149
+ return wf;
150
+ }
151
+ /** Record a step + emit to action-stream */
152
+ recordStep(step) {
153
+ if (this.recording) {
154
+ this.steps.push(step);
155
+ }
156
+ const entry = {
157
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
158
+ sid: this.sessionId,
159
+ tool: `stagehand:${step.type}`,
160
+ target: sanitizeInstruction(step.instruction),
161
+ dur: step.duration,
162
+ meta: {
163
+ url: step.url,
164
+ cached: step.cached,
165
+ tokens: step.tokens,
166
+ success: !step.result?.error
167
+ }
168
+ };
169
+ emitToActionStream(entry);
170
+ }
171
+ /** Get the underlying Stagehand instance for direct access */
172
+ get instance() {
173
+ return this.stagehand;
174
+ }
175
+ }
176
+ class WorkflowCache {
177
+ workflows = /* @__PURE__ */ new Map();
178
+ constructor() {
179
+ this.load();
180
+ }
181
+ /** Load cached workflows from disk */
182
+ load() {
183
+ if (!existsSync(WORKFLOW_CACHE_FILE)) return;
184
+ try {
185
+ const data = JSON.parse(readFileSync(WORKFLOW_CACHE_FILE, "utf-8"));
186
+ if (Array.isArray(data)) {
187
+ for (const entry of data) {
188
+ this.workflows.set(entry.id, entry);
189
+ }
190
+ }
191
+ } catch {
192
+ }
193
+ }
194
+ /** Save to disk */
195
+ save() {
196
+ ensureDir(WORKFLOW_DIR);
197
+ writeFileSync(
198
+ WORKFLOW_CACHE_FILE,
199
+ JSON.stringify([...this.workflows.values()], null, 2)
200
+ );
201
+ }
202
+ /** Store a captured workflow as a cached entry */
203
+ cacheWorkflow(workflow) {
204
+ const entry = {
205
+ version: 1,
206
+ id: workflow.id,
207
+ name: workflow.name,
208
+ startUrl: workflow.startUrl,
209
+ steps: workflow.steps.map((s) => ({
210
+ type: s.type,
211
+ instruction: s.instruction,
212
+ url: s.url
213
+ })),
214
+ capturedAt: workflow.capturedAt,
215
+ replayCount: 0,
216
+ avgDuration: workflow.totalDuration,
217
+ successRate: 1
218
+ };
219
+ this.workflows.set(entry.id, entry);
220
+ this.save();
221
+ return entry;
222
+ }
223
+ /** Find a cached workflow by name (fuzzy match) */
224
+ findByName(query) {
225
+ const lower = query.toLowerCase();
226
+ for (const entry of this.workflows.values()) {
227
+ if (entry.name.toLowerCase().includes(lower)) return entry;
228
+ }
229
+ return void 0;
230
+ }
231
+ /** Find workflows matching a URL pattern */
232
+ findByUrl(url) {
233
+ const host = extractHost(url);
234
+ return [...this.workflows.values()].filter(
235
+ (w) => extractHost(w.startUrl) === host
236
+ );
237
+ }
238
+ /** List all cached workflows */
239
+ list() {
240
+ return [...this.workflows.values()].sort(
241
+ (a, b) => b.replayCount - a.replayCount
242
+ );
243
+ }
244
+ /** Update replay stats after a replay attempt */
245
+ recordReplay(id, success, duration) {
246
+ const entry = this.workflows.get(id);
247
+ if (!entry) return;
248
+ entry.replayCount++;
249
+ entry.lastReplayAt = (/* @__PURE__ */ new Date()).toISOString();
250
+ entry.avgDuration = (entry.avgDuration * (entry.replayCount - 1) + duration) / entry.replayCount;
251
+ entry.successRate = (entry.successRate * (entry.replayCount - 1) + (success ? 1 : 0)) / entry.replayCount;
252
+ this.save();
253
+ }
254
+ get(id) {
255
+ return this.workflows.get(id);
256
+ }
257
+ }
258
+ class WorkflowReplayer {
259
+ stagehand;
260
+ cache;
261
+ constructor(stagehandInstance, cache) {
262
+ this.stagehand = stagehandInstance;
263
+ this.cache = cache || new WorkflowCache();
264
+ }
265
+ /** Replay a workflow by ID */
266
+ async replay(workflowId, mode = "hybrid", variables) {
267
+ const entry = this.cache.get(workflowId);
268
+ if (!entry) {
269
+ return {
270
+ success: false,
271
+ error: "Workflow not found",
272
+ duration: 0,
273
+ steps: 0,
274
+ selfHealed: false
275
+ };
276
+ }
277
+ return this.replayEntry(entry, mode, variables);
278
+ }
279
+ /** Replay by name (fuzzy match) */
280
+ async replayByName(name, mode = "hybrid", variables) {
281
+ const entry = this.cache.findByName(name);
282
+ if (!entry) {
283
+ return {
284
+ success: false,
285
+ error: `No workflow matching "${name}"`,
286
+ duration: 0,
287
+ steps: 0,
288
+ selfHealed: false
289
+ };
290
+ }
291
+ return this.replayEntry(entry, mode, variables);
292
+ }
293
+ async replayEntry(entry, mode, variables) {
294
+ const start = Date.now();
295
+ let selfHealed = false;
296
+ let stepsCompleted = 0;
297
+ try {
298
+ const page = this.stagehand.context.pages()[0];
299
+ let startUrl = entry.startUrl;
300
+ if (variables) {
301
+ startUrl = substituteVars(startUrl, variables);
302
+ }
303
+ await page.goto(startUrl);
304
+ for (const step of entry.steps) {
305
+ let instruction = step.instruction;
306
+ if (variables) {
307
+ instruction = substituteVars(instruction, variables);
308
+ }
309
+ if (step.type === "navigate" && step.url) {
310
+ const url = variables ? substituteVars(step.url, variables) : step.url;
311
+ await page.goto(url);
312
+ } else if (step.type === "act") {
313
+ if (mode === "cached" && step.actions?.length) {
314
+ try {
315
+ await replayActions(page, step.actions);
316
+ } catch {
317
+ if (mode === "cached")
318
+ throw new Error("Cache miss \u2014 selector changed");
319
+ selfHealed = true;
320
+ await this.stagehand.act(instruction);
321
+ }
322
+ } else {
323
+ await this.stagehand.act(instruction);
324
+ }
325
+ } else if (step.type === "extract") {
326
+ await this.stagehand.extract(
327
+ instruction,
328
+ step.schema ? JSON.parse(step.schema) : void 0
329
+ );
330
+ } else if (step.type === "observe") {
331
+ await this.stagehand.observe(instruction);
332
+ }
333
+ stepsCompleted++;
334
+ }
335
+ const duration = Date.now() - start;
336
+ this.cache.recordReplay(entry.id, true, duration);
337
+ return {
338
+ success: true,
339
+ duration,
340
+ steps: stepsCompleted,
341
+ selfHealed
342
+ };
343
+ } catch (error) {
344
+ const duration = Date.now() - start;
345
+ this.cache.recordReplay(entry.id, false, duration);
346
+ return {
347
+ success: false,
348
+ error: error.message,
349
+ duration,
350
+ steps: stepsCompleted,
351
+ selfHealed
352
+ };
353
+ }
354
+ }
355
+ }
356
+ class WorkflowBenchmark {
357
+ results = [];
358
+ /** Run a benchmark with Stagehand AI mode */
359
+ async benchmarkStagehandAI(name, stagehand, steps) {
360
+ const start = Date.now();
361
+ let success = true;
362
+ let error;
363
+ let totalTokens = 0;
364
+ try {
365
+ const page = stagehand.context.pages()[0];
366
+ for (const step of steps) {
367
+ if (step.type === "navigate") {
368
+ await page.goto(step.instruction);
369
+ } else if (step.type === "act") {
370
+ await stagehand.act(step.instruction);
371
+ totalTokens += estimateActTokens(step.instruction);
372
+ } else if (step.type === "extract") {
373
+ await stagehand.extract(step.instruction, step.schema);
374
+ totalTokens += estimateActTokens(step.instruction);
375
+ }
376
+ }
377
+ } catch (e) {
378
+ success = false;
379
+ error = e.message;
380
+ }
381
+ const result = {
382
+ workflow: name,
383
+ approach: "stagehand-ai",
384
+ duration: Date.now() - start,
385
+ tokens: totalTokens,
386
+ success,
387
+ selfHealed: false,
388
+ steps: steps.length,
389
+ error
390
+ };
391
+ this.results.push(result);
392
+ return result;
393
+ }
394
+ /** Run a benchmark with cached replay */
395
+ async benchmarkStagehandCached(name, replayer, workflowId, variables) {
396
+ const replayResult = await replayer.replay(workflowId, "cached", variables);
397
+ const result = {
398
+ workflow: name,
399
+ approach: "stagehand-cached",
400
+ duration: replayResult.duration,
401
+ tokens: 0,
402
+ // Cached = no AI tokens
403
+ success: replayResult.success,
404
+ selfHealed: replayResult.selfHealed,
405
+ steps: replayResult.steps,
406
+ error: replayResult.error
407
+ };
408
+ this.results.push(result);
409
+ return result;
410
+ }
411
+ /** Run a benchmark with a Playwright code function */
412
+ async benchmarkPlaywright(name, fn, stepCount) {
413
+ const start = Date.now();
414
+ let success = true;
415
+ let error;
416
+ try {
417
+ await fn();
418
+ } catch (e) {
419
+ success = false;
420
+ error = e.message;
421
+ }
422
+ const result = {
423
+ workflow: name,
424
+ approach: "playwright-code",
425
+ duration: Date.now() - start,
426
+ tokens: 0,
427
+ success,
428
+ selfHealed: false,
429
+ steps: stepCount,
430
+ error
431
+ };
432
+ this.results.push(result);
433
+ return result;
434
+ }
435
+ /** Get all results */
436
+ getResults() {
437
+ return [...this.results];
438
+ }
439
+ /** Print a comparison table */
440
+ formatTable() {
441
+ const grouped = /* @__PURE__ */ new Map();
442
+ for (const r of this.results) {
443
+ const list = grouped.get(r.workflow) || [];
444
+ list.push(r);
445
+ grouped.set(r.workflow, list);
446
+ }
447
+ const lines = [];
448
+ lines.push(
449
+ "| Workflow | Approach | Duration | Tokens | Success | Self-Healed |"
450
+ );
451
+ lines.push(
452
+ "|----------|----------|----------|--------|---------|-------------|"
453
+ );
454
+ for (const [name, results] of grouped) {
455
+ for (const r of results) {
456
+ lines.push(
457
+ `| ${name} | ${r.approach} | ${r.duration}ms | ${r.tokens} | ${r.success ? "Y" : "N"} | ${r.selfHealed ? "Y" : "N"} |`
458
+ );
459
+ }
460
+ }
461
+ return lines.join("\n");
462
+ }
463
+ /** Save results to JSONL */
464
+ save() {
465
+ ensureDir(WORKFLOW_DIR);
466
+ for (const result of this.results) {
467
+ appendFileSync(BENCHMARK_FILE, JSON.stringify(result) + "\n");
468
+ }
469
+ }
470
+ }
471
+ function ensureDir(dir) {
472
+ if (!existsSync(dir)) {
473
+ mkdirSync(dir, { recursive: true });
474
+ }
475
+ }
476
+ function sanitizeInstruction(instruction) {
477
+ return instruction.slice(0, 100).replace(/[\n\r]/g, " ");
478
+ }
479
+ function estimateActTokens(instruction) {
480
+ return Math.ceil(instruction.length / 4) + 2e3;
481
+ }
482
+ function extractHost(url) {
483
+ try {
484
+ return new URL(url).host;
485
+ } catch {
486
+ return url;
487
+ }
488
+ }
489
+ function substituteVars(template, vars) {
490
+ let result = template;
491
+ for (const [key, value] of Object.entries(vars)) {
492
+ result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
493
+ }
494
+ return result;
495
+ }
496
+ function emitToActionStream(entry) {
497
+ try {
498
+ ensureDir(DP_DIR);
499
+ appendFileSync(STREAM_FILE, JSON.stringify(entry) + "\n");
500
+ } catch {
501
+ }
502
+ }
503
+ function workflowToDesirePathPattern(workflow) {
504
+ return {
505
+ id: workflow.id,
506
+ sequence: workflow.steps.map(
507
+ (s) => `stagehand:${s.type}:${sanitizeInstruction(s.instruction)}`
508
+ ),
509
+ frequency: 1,
510
+ sessions: 1,
511
+ score: workflow.steps.length,
512
+ // longer = more valuable
513
+ first_seen: workflow.capturedAt,
514
+ last_seen: workflow.capturedAt,
515
+ source: "stagehand"
516
+ };
517
+ }
518
+ function appendDesirePathPattern(pattern) {
519
+ const patternsFile = join(DP_DIR, "patterns.json");
520
+ let patterns = [];
521
+ if (existsSync(patternsFile)) {
522
+ try {
523
+ patterns = JSON.parse(readFileSync(patternsFile, "utf-8"));
524
+ } catch {
525
+ patterns = [];
526
+ }
527
+ }
528
+ const seqHash = createHash("sha256").update(pattern.sequence.join("|")).digest("hex").slice(0, 16);
529
+ const existing = patterns.find((p) => {
530
+ const h = createHash("sha256").update((p.sequence || []).join("|")).digest("hex").slice(0, 16);
531
+ return h === seqHash;
532
+ });
533
+ if (existing) {
534
+ existing.frequency = (existing.frequency || 0) + 1;
535
+ existing.sessions = (existing.sessions || 0) + 1;
536
+ existing.score = existing.frequency * existing.sessions;
537
+ existing.last_seen = pattern.last_seen;
538
+ } else {
539
+ patterns.push(pattern);
540
+ }
541
+ ensureDir(DP_DIR);
542
+ writeFileSync(patternsFile, JSON.stringify(patterns, null, 2));
543
+ }
544
+ async function replayActions(page, actions) {
545
+ for (const action of actions) {
546
+ if (!action.selector) continue;
547
+ const element = await page.locator(action.selector);
548
+ switch (action.action) {
549
+ case "click":
550
+ await element.click();
551
+ break;
552
+ case "fill":
553
+ case "type":
554
+ if (action.text) await element.fill(action.text);
555
+ break;
556
+ case "selectOption":
557
+ if (action.args?.[0]) await element.selectOption(action.args[0]);
558
+ break;
559
+ default:
560
+ if (action.action && typeof element[action.action] === "function") {
561
+ await element[action.action](...action.args || []);
562
+ }
563
+ }
564
+ }
565
+ }
566
+ function saveWorkflow(workflow) {
567
+ const file = join(WORKFLOW_DIR, `${workflow.id}.json`);
568
+ ensureDir(WORKFLOW_DIR);
569
+ writeFileSync(file, JSON.stringify(workflow, null, 2));
570
+ const cache = new WorkflowCache();
571
+ cache.cacheWorkflow(workflow);
572
+ }
573
+ export {
574
+ StagehandWorkflowCapture,
575
+ WorkflowBenchmark,
576
+ WorkflowCache,
577
+ WorkflowReplayer
578
+ };
@@ -0,0 +1,62 @@
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 { TmuxAdapter } from "./screen-adapter.js";
7
+ function createAdapter(config) {
8
+ const mode = config.mode === "auto" ? detectMode(config) : config.mode;
9
+ switch (mode) {
10
+ case "tmux":
11
+ return new TmuxAdapter(config.sessionName, "0");
12
+ case "desktop": {
13
+ if (!config.llmConfig?.apiKey) {
14
+ throw new Error(
15
+ "Desktop mode requires ANTHROPIC_API_KEY for screenshot interpretation.\nSet it via: export ANTHROPIC_API_KEY=sk-ant-..."
16
+ );
17
+ }
18
+ const { DesktopAdapter } = require("./desktop-adapter.js");
19
+ return new DesktopAdapter(config.appName ?? "Claude", config.llmConfig);
20
+ }
21
+ case "browser":
22
+ throw new Error(
23
+ "Browser mode requires a Playwright page instance.\nUse BrowserAdapter directly with an initialized page,\nor use: stackmemory operator start --mode browser --url https://claude.ai/code"
24
+ );
25
+ default:
26
+ return new TmuxAdapter(config.sessionName, "0");
27
+ }
28
+ }
29
+ function detectMode(config) {
30
+ if (isTmuxAvailable()) {
31
+ return "tmux";
32
+ }
33
+ if (process.platform === "darwin" && isClaudeDesktopRunning(config.appName)) {
34
+ return "desktop";
35
+ }
36
+ process.stderr.write(
37
+ "[operator] Warning: tmux not found and no desktop app detected.\n Install tmux: brew install tmux\n Or use --mode desktop/browser\n"
38
+ );
39
+ return "tmux";
40
+ }
41
+ function isTmuxAvailable() {
42
+ try {
43
+ execSync("which tmux", { stdio: "ignore" });
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+ function isClaudeDesktopRunning(appName = "Claude") {
50
+ try {
51
+ const result = execSync(
52
+ `osascript -e 'tell application "System Events" to (name of processes) contains "${appName}"'`,
53
+ { encoding: "utf-8", timeout: 5e3 }
54
+ ).trim();
55
+ return result === "true";
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+ export {
61
+ createAdapter
62
+ };