@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,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
+ };