@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.
- package/LICENSE +131 -64
- package/README.md +107 -24
- package/bin/claude-sm +16 -1
- package/bin/claude-smd +16 -1
- package/bin/codex-smd +16 -1
- package/bin/gemini-sm +16 -1
- package/bin/hermes-sm +21 -0
- package/bin/hermes-smd +21 -0
- package/bin/opencode-sm +16 -1
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +225 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/brain.js +206 -0
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/company-os.js +184 -0
- package/dist/src/cli/commands/context.js +5 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/operator.js +127 -0
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +232 -5
- package/dist/src/cli/commands/orchestrator.js +315 -26
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/patterns.js +254 -0
- package/dist/src/cli/commands/portal.js +161 -0
- package/dist/src/cli/commands/scaffold.js +92 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +178 -11
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/commands/sync.js +253 -0
- package/dist/src/cli/commands/tasks.js +130 -1
- package/dist/src/cli/commands/vision.js +221 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +105 -39
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +116 -0
- package/dist/src/core/brain/brain-store.js +187 -0
- package/dist/src/core/brain/brain-sync.js +193 -0
- package/dist/src/core/brain/index.js +78 -0
- package/dist/src/core/brain/types.js +10 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/{integrations/diffmem → core/cache}/index.js +5 -5
- package/dist/src/core/cache/token-estimator.js +39 -0
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +79 -27
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/cross-search/index.js +10 -0
- package/dist/src/core/database/sqlite-adapter.js +14 -84
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +54 -2
- package/dist/src/core/models/provider-pricing.js +58 -4
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/patterns/index.js +22 -0
- package/dist/src/core/patterns/pattern-applier.js +39 -0
- package/dist/src/core/patterns/pattern-observer.js +157 -0
- package/dist/src/core/patterns/pattern-store.js +259 -0
- package/dist/src/core/patterns/types.js +19 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +79 -0
- package/dist/src/core/storage/cloud-sync-manager.js +116 -0
- package/dist/src/core/storage/cloud-sync.js +574 -0
- package/dist/src/core/storage/two-tier-storage.js +5 -1
- package/dist/src/core/tasks/master-tasks-template.js +43 -0
- package/dist/src/core/tasks/md-task-parser.js +138 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/vision/index.js +27 -0
- package/dist/src/core/vision/signals.js +79 -0
- package/dist/src/core/vision/types.js +22 -0
- package/dist/src/core/vision/vision-file.js +146 -0
- package/dist/src/core/vision/vision-loop.js +220 -0
- package/dist/src/core/wiki/wiki-compiler.js +103 -1
- package/dist/src/daemon/daemon-config.js +52 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/services/research-stream-service.js +320 -0
- package/dist/src/daemon/services/telemetry-service.js +192 -0
- package/dist/src/daemon/unified-daemon.js +58 -1
- package/dist/src/features/browser/cli-browser-agent.js +417 -0
- package/dist/src/features/browser/stagehand-workflows.js +578 -0
- package/dist/src/features/operator/adapter-factory.js +62 -0
- package/dist/src/features/operator/browser-adapter.js +109 -0
- package/dist/src/features/operator/desktop-adapter.js +125 -0
- package/dist/src/features/operator/index.js +39 -0
- package/dist/src/features/operator/llm-decision.js +137 -0
- package/dist/src/features/operator/operator-logger.js +92 -0
- package/dist/src/features/operator/overnight-runner.js +327 -0
- package/dist/src/features/operator/screen-adapter.js +91 -0
- package/dist/src/features/operator/session-manager.js +127 -0
- package/dist/src/features/operator/state-machine.js +227 -0
- package/dist/src/features/operator/task-queue.js +81 -0
- package/dist/src/features/portal/index.js +26 -0
- package/dist/src/features/portal/server.js +240 -0
- package/dist/src/features/portal/types.js +14 -0
- package/dist/src/features/portal/ui.js +195 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/features/tasks/task-aware-context.js +2 -1
- package/dist/src/features/tui/simple-monitor.js +0 -23
- package/dist/src/features/tui/swarm-monitor.js +8 -66
- package/dist/src/features/web/client/hooks/use-socket.js +12 -0
- package/dist/src/{core/merge/index.js → features/web/client/lib/utils.js} +8 -4
- package/dist/src/features/web/client/next-env.d.js +4 -0
- package/dist/src/features/web/client/stores/session-store.js +12 -0
- package/dist/src/features/web/server/gcp-billing.js +76 -0
- package/dist/src/features/web/server/index.js +10 -0
- package/dist/src/features/web/server/spend-calculator.js +228 -0
- package/dist/src/hooks/schemas.js +6 -1
- package/dist/src/integrations/anthropic/client.js +3 -2
- package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
- package/dist/src/integrations/claude-code/subagent-client.js +307 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/linear/webhook-retry.js +196 -0
- package/dist/src/integrations/linear/webhook-server.js +18 -22
- package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -84
- package/dist/src/integrations/mcp/server.js +542 -641
- package/dist/src/integrations/mcp/tool-alias-registry.js +297 -0
- package/dist/src/integrations/mcp/tool-definitions.js +152 -682
- package/dist/src/mcp/stackmemory-mcp-server.js +571 -231
- package/dist/src/orchestrators/multimodal/determinism.js +244 -0
- package/dist/src/orchestrators/multimodal/harness.js +149 -78
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- package/dist/src/utils/hook-installer.js +0 -8
- package/dist/src/utils/process-cleanup.js +1 -7
- package/docs/README.md +42 -0
- package/docs/guides/README_INSTALL.md +208 -0
- package/package.json +27 -9
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/scripts/claude-code-wrapper.sh +11 -0
- package/scripts/claude-sm-setup.sh +12 -1
- package/scripts/codex-wrapper.sh +11 -0
- package/scripts/git-hooks/branch-context-manager.sh +11 -0
- package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
- package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
- package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
- package/scripts/hooks/cleanup-shell.sh +12 -1
- package/scripts/hooks/task-complete.sh +12 -1
- package/scripts/install-code-execution-hooks.sh +12 -1
- package/scripts/install-sweep-hook.sh +12 -0
- package/scripts/install.sh +11 -0
- package/scripts/opencode-wrapper.sh +11 -0
- package/scripts/portal/cloud-init.yaml +69 -0
- package/scripts/portal/setup.sh +69 -0
- package/scripts/portal/stackmemory-portal.service +34 -0
- package/scripts/setup-claude-integration.sh +12 -1
- package/scripts/smoke-init-db.sh +23 -0
- package/scripts/stackmemory-daemon.sh +11 -0
- package/scripts/verify-dist.cjs +11 -4
- package/dist/src/cli/commands/ralph.js +0 -1053
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/hooks/diffmem-hooks.js +0 -376
- package/dist/src/integrations/diffmem/client.js +0 -208
- package/dist/src/integrations/diffmem/config.js +0 -14
- package/dist/src/integrations/greptile/client.js +0 -101
- package/dist/src/integrations/greptile/config.js +0 -14
- package/dist/src/integrations/greptile/index.js +0 -11
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
- package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
- package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -391
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -435
- package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
- package/dist/src/integrations/ralph/loopmax.js +0 -488
- package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
- package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
- package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
- package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
- package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
- package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1007
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/core/{merge → cache}/types.js +0 -0
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile/types.js → core/trace/trace-event.js} +0 -0
- /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
|
+
};
|