@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.
- package/LICENSE +131 -64
- package/README.md +3 -1
- 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/codex-sm.js +51 -11
- package/dist/src/cli/commands/brain.js +206 -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/operator.js +127 -0
- package/dist/src/cli/commands/orchestrate.js +2 -0
- package/dist/src/cli/commands/orchestrator.js +3 -2
- 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/setup.js +1 -4
- 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/hermes-sm.js +224 -0
- package/dist/src/cli/index.js +15 -10
- package/dist/src/cli/utils/real-cli-bin.js +72 -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/token-estimator.js +24 -1
- package/dist/src/core/config/feature-flags.js +2 -6
- package/dist/src/core/context/frame-database.js +44 -0
- package/dist/src/core/context/recursive-context-manager.js +1 -1
- package/dist/src/core/context/rehydration.js +2 -1
- package/dist/src/core/database/sqlite-adapter.js +14 -1
- package/dist/src/core/models/model-router.js +33 -1
- package/dist/src/core/models/provider-pricing.js +58 -4
- 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/retrieval/llm-context-retrieval.js +5 -4
- package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
- package/dist/src/core/skill-packs/types.js +14 -1
- 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/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 +45 -0
- package/dist/src/daemon/services/desire-path-service.js +566 -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 +28 -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/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/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
- package/dist/src/features/web/client/lib/utils.js +12 -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 +4 -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 +218 -11
- package/dist/src/integrations/claude-code/task-coordinator.js +2 -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 +27 -52
- package/dist/src/integrations/mcp/server.js +122 -335
- package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
- package/dist/src/integrations/mcp/tool-definitions.js +111 -510
- package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
- package/dist/src/orchestrators/multimodal/determinism.js +2 -1
- package/dist/src/orchestrators/multimodal/harness.js +2 -1
- package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
- 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 +18 -9
- 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/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/cross-search-handlers.js +0 -188
- 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/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 -354
- package/dist/src/integrations/ralph/index.js +0 -17
- package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
- 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 -1005
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
- package/scripts/ralph-loop-implementation.js +0 -404
- /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
- /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
- /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
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 Database from "better-sqlite3";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import {
|
|
10
|
+
calculateCost,
|
|
11
|
+
effectiveSpendMultiplier,
|
|
12
|
+
formatCost
|
|
13
|
+
} from "../../../core/models/provider-pricing.js";
|
|
14
|
+
import { getGcpSpend, getGcpSpendEnvFallback } from "./gcp-billing.js";
|
|
15
|
+
const DEFAULT_CONDUCTOR_MODEL = "anthropic/claude-sonnet-4-20250514";
|
|
16
|
+
const DEFAULT_RETRIEVAL_MODEL = "anthropic/claude-haiku-4-5-20251001";
|
|
17
|
+
function resolvePricingKey(model, fallback = DEFAULT_CONDUCTOR_MODEL) {
|
|
18
|
+
if (!model) return fallback;
|
|
19
|
+
const normalized = model.toLowerCase().trim();
|
|
20
|
+
if (normalized.includes("claude-opus-4")) return "anthropic/claude-opus-4-7";
|
|
21
|
+
if (normalized.includes("claude-sonnet-4-5"))
|
|
22
|
+
return "anthropic/claude-sonnet-4-5-20250929";
|
|
23
|
+
if (normalized.includes("claude-sonnet-4"))
|
|
24
|
+
return "anthropic/claude-sonnet-4-20250514";
|
|
25
|
+
if (normalized.includes("claude-haiku"))
|
|
26
|
+
return "anthropic/claude-haiku-4-5-20251001";
|
|
27
|
+
if (normalized.includes("claude-3-5-haiku"))
|
|
28
|
+
return "anthropic/claude-haiku-4-5-20251001";
|
|
29
|
+
if (normalized.includes("gpt-4o")) return "openai/gpt-4o";
|
|
30
|
+
if (normalized.includes("meta-llama/llama-4-scout"))
|
|
31
|
+
return "openrouter/meta-llama/llama-4-scout";
|
|
32
|
+
if (normalized.includes("llama-4-scout-17b"))
|
|
33
|
+
return "cerebras/llama-4-scout-17b-16e-instruct";
|
|
34
|
+
if (normalized.includes("glm-4")) return "deepinfra/THUDM/glm-4-9b-chat";
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
function extractModelFromEventJson(eventJson) {
|
|
38
|
+
if (!eventJson) return void 0;
|
|
39
|
+
try {
|
|
40
|
+
const event = JSON.parse(eventJson);
|
|
41
|
+
const model = event.model ?? event.message?.model ?? event.body?.model ?? event.params?.model;
|
|
42
|
+
return typeof model === "string" ? model : void 0;
|
|
43
|
+
} catch {
|
|
44
|
+
return void 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getConductorTracesDbPath() {
|
|
48
|
+
return join(homedir(), ".stackmemory", "conductor", "traces.db");
|
|
49
|
+
}
|
|
50
|
+
function getProjectContextDbPath() {
|
|
51
|
+
return join(process.cwd(), ".stackmemory", "context.db");
|
|
52
|
+
}
|
|
53
|
+
function readConductorUsage() {
|
|
54
|
+
const dbPath = getConductorTracesDbPath();
|
|
55
|
+
if (!existsSync(dbPath)) return [];
|
|
56
|
+
let db;
|
|
57
|
+
try {
|
|
58
|
+
db = new Database(dbPath, { readonly: true });
|
|
59
|
+
const rows = db.prepare(
|
|
60
|
+
`
|
|
61
|
+
SELECT
|
|
62
|
+
input_tokens as inputTokens,
|
|
63
|
+
output_tokens as outputTokens,
|
|
64
|
+
timestamp,
|
|
65
|
+
event_json as eventJson
|
|
66
|
+
FROM conductor_traces
|
|
67
|
+
WHERE input_tokens > 0 OR output_tokens > 0
|
|
68
|
+
ORDER BY timestamp DESC
|
|
69
|
+
`
|
|
70
|
+
).all();
|
|
71
|
+
return rows.map((r) => ({
|
|
72
|
+
source: "conductor",
|
|
73
|
+
modelKey: resolvePricingKey(
|
|
74
|
+
extractModelFromEventJson(r.eventJson),
|
|
75
|
+
DEFAULT_CONDUCTOR_MODEL
|
|
76
|
+
),
|
|
77
|
+
inputTokens: r.inputTokens || 0,
|
|
78
|
+
outputTokens: r.outputTokens || 0,
|
|
79
|
+
timestamp: r.timestamp
|
|
80
|
+
}));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.warn("Failed to read conductor traces for spend estimate:", error);
|
|
83
|
+
return [];
|
|
84
|
+
} finally {
|
|
85
|
+
db?.close();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function readRetrievalUsage() {
|
|
89
|
+
const dbPath = getProjectContextDbPath();
|
|
90
|
+
if (!existsSync(dbPath)) return [];
|
|
91
|
+
let db;
|
|
92
|
+
try {
|
|
93
|
+
db = new Database(dbPath, { readonly: true });
|
|
94
|
+
const rows = db.prepare(
|
|
95
|
+
`
|
|
96
|
+
SELECT
|
|
97
|
+
tokens_used as tokensUsed,
|
|
98
|
+
timestamp
|
|
99
|
+
FROM retrieval_audit
|
|
100
|
+
WHERE tokens_used > 0
|
|
101
|
+
ORDER BY timestamp DESC
|
|
102
|
+
`
|
|
103
|
+
).all();
|
|
104
|
+
return rows.map((r) => ({
|
|
105
|
+
source: "retrieval",
|
|
106
|
+
modelKey: DEFAULT_RETRIEVAL_MODEL,
|
|
107
|
+
// Estimate: 80% output, 20% input for the analysis call
|
|
108
|
+
inputTokens: Math.round((r.tokensUsed || 0) * 0.2),
|
|
109
|
+
outputTokens: Math.round((r.tokensUsed || 0) * 0.8),
|
|
110
|
+
timestamp: r.timestamp
|
|
111
|
+
}));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.warn("Failed to read retrieval audit for spend estimate:", error);
|
|
114
|
+
return [];
|
|
115
|
+
} finally {
|
|
116
|
+
db?.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function dateKey(timestamp) {
|
|
120
|
+
const d = new Date(timestamp);
|
|
121
|
+
return d.toISOString().slice(0, 10);
|
|
122
|
+
}
|
|
123
|
+
function calculateSpendSummary(rows) {
|
|
124
|
+
const now = /* @__PURE__ */ new Date();
|
|
125
|
+
const multiplier = effectiveSpendMultiplier(now);
|
|
126
|
+
const bySource = {};
|
|
127
|
+
const byModel = {};
|
|
128
|
+
const byDayMap = {};
|
|
129
|
+
let totalInputTokens = 0;
|
|
130
|
+
let totalOutputTokens = 0;
|
|
131
|
+
let totalListCost = 0;
|
|
132
|
+
let totalEffectiveCost = 0;
|
|
133
|
+
for (const row of rows) {
|
|
134
|
+
const cost = calculateCost(
|
|
135
|
+
row.modelKey.split("/")[0],
|
|
136
|
+
row.modelKey.split("/").slice(1).join("/"),
|
|
137
|
+
row.inputTokens,
|
|
138
|
+
row.outputTokens
|
|
139
|
+
);
|
|
140
|
+
const listCost = cost?.totalCost ?? 0;
|
|
141
|
+
const effective = listCost * multiplier;
|
|
142
|
+
totalInputTokens += row.inputTokens;
|
|
143
|
+
totalOutputTokens += row.outputTokens;
|
|
144
|
+
totalListCost += listCost;
|
|
145
|
+
totalEffectiveCost += effective;
|
|
146
|
+
const src = bySource[row.source] ?? {
|
|
147
|
+
source: row.source,
|
|
148
|
+
inputTokens: 0,
|
|
149
|
+
outputTokens: 0,
|
|
150
|
+
totalTokens: 0,
|
|
151
|
+
listCostUsd: 0,
|
|
152
|
+
effectiveCostUsd: 0
|
|
153
|
+
};
|
|
154
|
+
src.inputTokens += row.inputTokens;
|
|
155
|
+
src.outputTokens += row.outputTokens;
|
|
156
|
+
src.totalTokens += row.inputTokens + row.outputTokens;
|
|
157
|
+
src.listCostUsd += listCost;
|
|
158
|
+
src.effectiveCostUsd += effective;
|
|
159
|
+
bySource[row.source] = src;
|
|
160
|
+
const model = byModel[row.modelKey] ?? {
|
|
161
|
+
modelKey: row.modelKey,
|
|
162
|
+
displayName: row.modelKey.split("/").slice(1).join("/"),
|
|
163
|
+
inputTokens: 0,
|
|
164
|
+
outputTokens: 0,
|
|
165
|
+
totalTokens: 0,
|
|
166
|
+
listCostUsd: 0,
|
|
167
|
+
effectiveCostUsd: 0
|
|
168
|
+
};
|
|
169
|
+
model.inputTokens += row.inputTokens;
|
|
170
|
+
model.outputTokens += row.outputTokens;
|
|
171
|
+
model.totalTokens += row.inputTokens + row.outputTokens;
|
|
172
|
+
model.listCostUsd += listCost;
|
|
173
|
+
model.effectiveCostUsd += effective;
|
|
174
|
+
byModel[row.modelKey] = model;
|
|
175
|
+
const day = dateKey(row.timestamp);
|
|
176
|
+
const dayEntry = byDayMap[day] ?? {
|
|
177
|
+
date: day,
|
|
178
|
+
inputTokens: 0,
|
|
179
|
+
outputTokens: 0,
|
|
180
|
+
totalTokens: 0,
|
|
181
|
+
listCostUsd: 0,
|
|
182
|
+
effectiveCostUsd: 0
|
|
183
|
+
};
|
|
184
|
+
dayEntry.inputTokens += row.inputTokens;
|
|
185
|
+
dayEntry.outputTokens += row.outputTokens;
|
|
186
|
+
dayEntry.totalTokens += row.inputTokens + row.outputTokens;
|
|
187
|
+
dayEntry.listCostUsd += listCost;
|
|
188
|
+
dayEntry.effectiveCostUsd += effective;
|
|
189
|
+
byDayMap[day] = dayEntry;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
totalInputTokens,
|
|
193
|
+
totalOutputTokens,
|
|
194
|
+
totalTokens: totalInputTokens + totalOutputTokens,
|
|
195
|
+
listCostUsd: totalListCost,
|
|
196
|
+
effectiveCostUsd: totalEffectiveCost,
|
|
197
|
+
discountMultiplier: multiplier,
|
|
198
|
+
formattedListCost: formatCost(totalListCost),
|
|
199
|
+
formattedEffectiveCost: formatCost(totalEffectiveCost),
|
|
200
|
+
bySource,
|
|
201
|
+
byModel,
|
|
202
|
+
byDay: Object.values(byDayMap).sort((a, b) => a.date.localeCompare(b.date))
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async function getSpendSummary() {
|
|
206
|
+
const rows = [...readConductorUsage(), ...readRetrievalUsage()];
|
|
207
|
+
const summary = calculateSpendSummary(rows);
|
|
208
|
+
const gcp = await getGcpSpend();
|
|
209
|
+
if (gcp) {
|
|
210
|
+
summary.gcpSpendUsd = gcp.totalCostUsd;
|
|
211
|
+
summary.gcpSpendFormatted = formatCost(gcp.totalCostUsd);
|
|
212
|
+
summary.gcpSource = "bigquery";
|
|
213
|
+
summary.gcpTable = gcp.table;
|
|
214
|
+
summary.gcpDaily = gcp.daily;
|
|
215
|
+
} else {
|
|
216
|
+
const envSpend = getGcpSpendEnvFallback();
|
|
217
|
+
if (envSpend !== void 0) {
|
|
218
|
+
summary.gcpSpendUsd = envSpend;
|
|
219
|
+
summary.gcpSpendFormatted = formatCost(envSpend);
|
|
220
|
+
summary.gcpSource = "env";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return summary;
|
|
224
|
+
}
|
|
225
|
+
export {
|
|
226
|
+
calculateSpendSummary,
|
|
227
|
+
getSpendSummary
|
|
228
|
+
};
|
|
@@ -15,6 +15,8 @@ const ModelProviderSchema = z.enum([
|
|
|
15
15
|
"anthropic",
|
|
16
16
|
"qwen",
|
|
17
17
|
"openai",
|
|
18
|
+
"xai",
|
|
19
|
+
"deepseek",
|
|
18
20
|
"ollama",
|
|
19
21
|
"cerebras",
|
|
20
22
|
"deepinfra",
|
|
@@ -41,7 +43,8 @@ const ModelRouterConfigSchema = z.object({
|
|
|
41
43
|
review: ModelProviderSchema.optional(),
|
|
42
44
|
linting: ModelProviderSchema.optional(),
|
|
43
45
|
context: ModelProviderSchema.optional(),
|
|
44
|
-
testing: ModelProviderSchema.optional()
|
|
46
|
+
testing: ModelProviderSchema.optional(),
|
|
47
|
+
design: ModelProviderSchema.optional()
|
|
45
48
|
}).optional().default({}),
|
|
46
49
|
fallback: z.object({
|
|
47
50
|
enabled: z.boolean(),
|
|
@@ -3,6 +3,7 @@ import { dirname as __pathDirname } from 'path';
|
|
|
3
3
|
const __filename = __fileURLToPath(import.meta.url);
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../../core/monitoring/logger.js";
|
|
6
|
+
import { estimateTokens } from "../../core/cache/token-estimator.js";
|
|
6
7
|
import { STRUCTURED_RESPONSE_SUFFIX } from "../../orchestrators/multimodal/constants.js";
|
|
7
8
|
class AnthropicClient {
|
|
8
9
|
apiKey;
|
|
@@ -220,8 +221,8 @@ describe('validateInput', () => {
|
|
|
220
221
|
stopReason: "stop_sequence",
|
|
221
222
|
model: request.model,
|
|
222
223
|
usage: {
|
|
223
|
-
inputTokens:
|
|
224
|
-
outputTokens:
|
|
224
|
+
inputTokens: estimateTokens(request.prompt),
|
|
225
|
+
outputTokens: estimateTokens(content)
|
|
225
226
|
}
|
|
226
227
|
};
|
|
227
228
|
}
|
|
@@ -4,9 +4,6 @@ const __filename = __fileURLToPath(import.meta.url);
|
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
import { logger } from "../../core/monitoring/logger.js";
|
|
7
|
-
import {
|
|
8
|
-
OracleWorkerCoordinator
|
|
9
|
-
} from "../ralph/patterns/oracle-worker-pattern.js";
|
|
10
7
|
import { ClaudeCodeTaskCoordinator } from "./task-coordinator.js";
|
|
11
8
|
const CLAUDE_CODE_AGENTS = {
|
|
12
9
|
// Oracle-level agents (strategic, high-level)
|
|
@@ -3,8 +3,9 @@ import { dirname as __pathDirname } from 'path';
|
|
|
3
3
|
const __filename = __fileURLToPath(import.meta.url);
|
|
4
4
|
const __dirname = __pathDirname(__filename);
|
|
5
5
|
import { logger } from "../../core/monitoring/logger.js";
|
|
6
|
+
import { estimateTokens } from "../../core/cache/token-estimator.js";
|
|
6
7
|
import { STRUCTURED_RESPONSE_SUFFIX } from "../../orchestrators/multimodal/constants.js";
|
|
7
|
-
import { spawn } from "child_process";
|
|
8
|
+
import { spawn, execSync } from "child_process";
|
|
8
9
|
import * as fs from "fs";
|
|
9
10
|
import * as path from "path";
|
|
10
11
|
import * as os from "os";
|
|
@@ -44,8 +45,12 @@ class ClaudeCodeSubagentClient {
|
|
|
44
45
|
}
|
|
45
46
|
/**
|
|
46
47
|
* Execute a subagent task.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
48
|
+
*
|
|
49
|
+
* Routing priority (subscription-first to minimize API costs):
|
|
50
|
+
* 1. Codex CLI — subagents included in ChatGPT Plus/Pro subscription
|
|
51
|
+
* 2. Grok Build CLI — included in Grok Build subscription ($300/mo)
|
|
52
|
+
* 3. External API providers — when multiProvider enabled (DeepSeek, xAI, etc.)
|
|
53
|
+
* 4. Claude Code CLI — falls back here (uses API tokens, not subscription)
|
|
49
54
|
*/
|
|
50
55
|
async executeSubagent(request) {
|
|
51
56
|
const startTime = Date.now();
|
|
@@ -58,6 +63,43 @@ class ClaudeCodeSubagentClient {
|
|
|
58
63
|
if (this.mockMode) {
|
|
59
64
|
return this.getMockResponse(request, startTime, subagentId);
|
|
60
65
|
}
|
|
66
|
+
if (request.type === "design" || request.forceProvider === "claude") {
|
|
67
|
+
return this.executeSubagentViaCLI(request, startTime, subagentId);
|
|
68
|
+
}
|
|
69
|
+
if (request.forceProvider === "codex" && this.isCodexAvailable()) {
|
|
70
|
+
return this.executeSubagentViaCodex(request, startTime, subagentId);
|
|
71
|
+
}
|
|
72
|
+
if (request.forceProvider === "grok" && this.isGrokBuildAvailable()) {
|
|
73
|
+
return this.executeSubagentViaGrokBuild(request, startTime, subagentId);
|
|
74
|
+
}
|
|
75
|
+
if (this.isCodexAvailable()) {
|
|
76
|
+
try {
|
|
77
|
+
return await this.executeSubagentViaCodex(
|
|
78
|
+
request,
|
|
79
|
+
startTime,
|
|
80
|
+
subagentId
|
|
81
|
+
);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
logger.warn("Codex subagent failed, trying next provider", {
|
|
84
|
+
subagentId,
|
|
85
|
+
error: error.message
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (this.isGrokBuildAvailable()) {
|
|
90
|
+
try {
|
|
91
|
+
return await this.executeSubagentViaGrokBuild(
|
|
92
|
+
request,
|
|
93
|
+
startTime,
|
|
94
|
+
subagentId
|
|
95
|
+
);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
logger.warn("Grok Build subagent failed, trying next provider", {
|
|
98
|
+
subagentId,
|
|
99
|
+
error: error.message
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
61
103
|
if (isFeatureEnabled("multiProvider")) {
|
|
62
104
|
const taskType = request.type;
|
|
63
105
|
const complexity = scoreComplexity(request.task, request.context);
|
|
@@ -190,7 +232,7 @@ Context (JSON): ${JSON.stringify(request.context)}`;
|
|
|
190
232
|
output: result.text,
|
|
191
233
|
duration: Date.now() - startTime,
|
|
192
234
|
subagentType: request.type,
|
|
193
|
-
tokens:
|
|
235
|
+
tokens: estimateTokens(fullPrompt + result.text)
|
|
194
236
|
};
|
|
195
237
|
} catch (error) {
|
|
196
238
|
if (this.isQuotaError(error.message)) {
|
|
@@ -234,6 +276,130 @@ Context (JSON): ${JSON.stringify(request.context)}`;
|
|
|
234
276
|
}
|
|
235
277
|
});
|
|
236
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Check if Codex CLI is available (installed + authenticated via ChatGPT subscription)
|
|
281
|
+
*/
|
|
282
|
+
isCodexAvailable() {
|
|
283
|
+
try {
|
|
284
|
+
const result = execSync("which codex 2>/dev/null", {
|
|
285
|
+
encoding: "utf-8",
|
|
286
|
+
timeout: 3e3
|
|
287
|
+
}).trim();
|
|
288
|
+
return !!result;
|
|
289
|
+
} catch {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Check if Grok Build CLI is available
|
|
295
|
+
*/
|
|
296
|
+
isGrokBuildAvailable() {
|
|
297
|
+
try {
|
|
298
|
+
const result = execSync("which grok 2>/dev/null", {
|
|
299
|
+
encoding: "utf-8",
|
|
300
|
+
timeout: 3e3
|
|
301
|
+
}).trim();
|
|
302
|
+
return !!result;
|
|
303
|
+
} catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Execute subagent via Codex CLI.
|
|
309
|
+
* Subagents run against ChatGPT subscription — no API cost.
|
|
310
|
+
*/
|
|
311
|
+
async executeSubagentViaCodex(request, startTime, subagentId) {
|
|
312
|
+
const prompt = this.buildSubagentPrompt(request);
|
|
313
|
+
logger.info("Routing subagent to Codex CLI (subscription-free)", {
|
|
314
|
+
subagentId,
|
|
315
|
+
type: request.type
|
|
316
|
+
});
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
318
|
+
const args = ["-q", "--json", "-a", "full-auto", prompt];
|
|
319
|
+
const proc = spawn("codex", args, {
|
|
320
|
+
cwd: process.cwd(),
|
|
321
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
322
|
+
timeout: request.timeout ?? 3e5
|
|
323
|
+
});
|
|
324
|
+
let stdout = "";
|
|
325
|
+
let stderr = "";
|
|
326
|
+
proc.stdout?.on("data", (d) => {
|
|
327
|
+
stdout += d.toString();
|
|
328
|
+
});
|
|
329
|
+
proc.stderr?.on("data", (d) => {
|
|
330
|
+
stderr += d.toString();
|
|
331
|
+
});
|
|
332
|
+
proc.on("close", (code) => {
|
|
333
|
+
if (code !== 0) {
|
|
334
|
+
reject(new Error(`Codex exited ${code}: ${stderr.slice(0, 500)}`));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
let parsed;
|
|
338
|
+
try {
|
|
339
|
+
parsed = JSON.parse(stdout);
|
|
340
|
+
} catch {
|
|
341
|
+
parsed = { rawOutput: stdout };
|
|
342
|
+
}
|
|
343
|
+
resolve({
|
|
344
|
+
success: true,
|
|
345
|
+
result: parsed,
|
|
346
|
+
output: stdout,
|
|
347
|
+
duration: Date.now() - startTime,
|
|
348
|
+
subagentType: `codex:${request.type}`
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
proc.on("error", reject);
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Execute subagent via Grok Build CLI.
|
|
356
|
+
* Runs against Grok Build subscription — no per-token API cost.
|
|
357
|
+
*/
|
|
358
|
+
async executeSubagentViaGrokBuild(request, startTime, subagentId) {
|
|
359
|
+
const prompt = this.buildSubagentPrompt(request);
|
|
360
|
+
logger.info("Routing subagent to Grok Build CLI (subscription-free)", {
|
|
361
|
+
subagentId,
|
|
362
|
+
type: request.type
|
|
363
|
+
});
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
const args = ["run", "--quiet", "--json", prompt];
|
|
366
|
+
const proc = spawn("grok", args, {
|
|
367
|
+
cwd: process.cwd(),
|
|
368
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
369
|
+
timeout: request.timeout ?? 3e5
|
|
370
|
+
});
|
|
371
|
+
let stdout = "";
|
|
372
|
+
let stderr = "";
|
|
373
|
+
proc.stdout?.on("data", (d) => {
|
|
374
|
+
stdout += d.toString();
|
|
375
|
+
});
|
|
376
|
+
proc.stderr?.on("data", (d) => {
|
|
377
|
+
stderr += d.toString();
|
|
378
|
+
});
|
|
379
|
+
proc.on("close", (code) => {
|
|
380
|
+
if (code !== 0) {
|
|
381
|
+
reject(
|
|
382
|
+
new Error(`Grok Build exited ${code}: ${stderr.slice(0, 500)}`)
|
|
383
|
+
);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
let parsed;
|
|
387
|
+
try {
|
|
388
|
+
parsed = JSON.parse(stdout);
|
|
389
|
+
} catch {
|
|
390
|
+
parsed = { rawOutput: stdout };
|
|
391
|
+
}
|
|
392
|
+
resolve({
|
|
393
|
+
success: true,
|
|
394
|
+
result: parsed,
|
|
395
|
+
output: stdout,
|
|
396
|
+
duration: Date.now() - startTime,
|
|
397
|
+
subagentType: `grok-build:${request.type}`
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
proc.on("error", reject);
|
|
401
|
+
});
|
|
402
|
+
}
|
|
237
403
|
/**
|
|
238
404
|
* Check if an error message indicates quota/rate limit exhaustion
|
|
239
405
|
*/
|
|
@@ -408,6 +574,23 @@ Context (JSON): ${JSON.stringify(request.context)}`;
|
|
|
408
574
|
Search parameters are in the context file.
|
|
409
575
|
|
|
410
576
|
Output relevant context snippets.`,
|
|
577
|
+
design: `You are a Frontend Design Subagent. You make creative, opinionated UI/UX decisions while implementing exactly what's scoped.
|
|
578
|
+
|
|
579
|
+
Task: ${request.task}
|
|
580
|
+
|
|
581
|
+
Instructions:
|
|
582
|
+
1. Make strong design decisions \u2014 pick colors, spacing, layout, typography, interactions
|
|
583
|
+
2. Write production-ready code (React/TSX + Tailwind preferred, adapt to project stack)
|
|
584
|
+
3. Favor distinctive, polished UI over generic/boilerplate aesthetics
|
|
585
|
+
4. Ensure responsive design and accessibility basics (contrast, focus, semantic HTML)
|
|
586
|
+
5. Include hover states, transitions, and micro-interactions where appropriate
|
|
587
|
+
6. Match existing design system if one exists in the project, otherwise create cohesive choices
|
|
588
|
+
|
|
589
|
+
The prompt is well-scoped but you own the UI/UX decisions. Don't ask \u2014 decide.
|
|
590
|
+
|
|
591
|
+
Context and requirements are in the provided file.
|
|
592
|
+
|
|
593
|
+
Output the implementation code with brief notes on design choices made.`,
|
|
411
594
|
publish: `You are a Publishing Subagent handling releases and deployments.
|
|
412
595
|
|
|
413
596
|
Task: ${request.task}
|
|
@@ -593,6 +776,15 @@ function greetUser(name: string): string {
|
|
|
593
776
|
changelog: "Initial release",
|
|
594
777
|
published: false,
|
|
595
778
|
reason: "Mock mode - no actual publishing"
|
|
779
|
+
},
|
|
780
|
+
design: {
|
|
781
|
+
implementation: '<div className="flex flex-col gap-4 p-6">...</div>',
|
|
782
|
+
files_modified: ["src/components/Example.tsx"],
|
|
783
|
+
design_choices: [
|
|
784
|
+
"Dark mode default",
|
|
785
|
+
"Space Grotesk typography",
|
|
786
|
+
"8px grid"
|
|
787
|
+
]
|
|
596
788
|
}
|
|
597
789
|
};
|
|
598
790
|
const result = mockResponses[request.type] || {};
|
|
@@ -602,15 +794,9 @@ function greetUser(name: string): string {
|
|
|
602
794
|
output: `Mock ${request.type} subagent completed successfully`,
|
|
603
795
|
duration: Date.now() - startTime,
|
|
604
796
|
subagentType: request.type,
|
|
605
|
-
tokens:
|
|
797
|
+
tokens: estimateTokens(JSON.stringify(result))
|
|
606
798
|
};
|
|
607
799
|
}
|
|
608
|
-
/**
|
|
609
|
-
* Estimate token usage
|
|
610
|
-
*/
|
|
611
|
-
estimateTokens(text) {
|
|
612
|
-
return Math.ceil(text.length / 4);
|
|
613
|
-
}
|
|
614
800
|
/**
|
|
615
801
|
* Cleanup temporary files
|
|
616
802
|
*/
|
|
@@ -630,6 +816,27 @@ function greetUser(name: string): string {
|
|
|
630
816
|
}
|
|
631
817
|
}
|
|
632
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Delegate a design/frontend task to Claude.
|
|
821
|
+
* Always routes to Claude CLI regardless of subscription-first ordering,
|
|
822
|
+
* because Claude is the design specialist.
|
|
823
|
+
*
|
|
824
|
+
* Usage from any wrapper (codex-sm, opencode-sm, etc.):
|
|
825
|
+
* const client = new ClaudeCodeSubagentClient();
|
|
826
|
+
* const result = await client.delegateDesign(
|
|
827
|
+
* 'Build a settings page with dark/light toggle, user avatar, and notification preferences',
|
|
828
|
+
* { stack: 'react+tailwind', designSystem: 'nothing' }
|
|
829
|
+
* );
|
|
830
|
+
*/
|
|
831
|
+
async delegateDesign(task, context = {}, options) {
|
|
832
|
+
return this.executeSubagent({
|
|
833
|
+
type: "design",
|
|
834
|
+
task,
|
|
835
|
+
context,
|
|
836
|
+
forceProvider: "claude",
|
|
837
|
+
...options
|
|
838
|
+
});
|
|
839
|
+
}
|
|
633
840
|
/**
|
|
634
841
|
* Get active subagent statistics
|
|
635
842
|
*/
|
|
@@ -5,6 +5,7 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { v4 as uuidv4 } from "uuid";
|
|
6
6
|
import { spawn } from "child_process";
|
|
7
7
|
import { logger } from "../../core/monitoring/logger.js";
|
|
8
|
+
import { estimateTokens } from "../../core/cache/token-estimator.js";
|
|
8
9
|
class ClaudeCodeTaskCoordinator {
|
|
9
10
|
activeTasks = /* @__PURE__ */ new Map();
|
|
10
11
|
completedTasks = [];
|
|
@@ -347,7 +348,7 @@ class ClaudeCodeTaskCoordinator {
|
|
|
347
348
|
* Estimate token usage
|
|
348
349
|
*/
|
|
349
350
|
estimateTokenUsage(prompt, response) {
|
|
350
|
-
return
|
|
351
|
+
return estimateTokens(prompt + response);
|
|
351
352
|
}
|
|
352
353
|
/**
|
|
353
354
|
* Calculate actual task cost
|