@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,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,10 +15,13 @@ 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",
21
23
  "openrouter",
24
+ "moonshot",
22
25
  "anthropic-batch",
23
26
  "custom"
24
27
  ]);
@@ -40,7 +43,8 @@ const ModelRouterConfigSchema = z.object({
40
43
  review: ModelProviderSchema.optional(),
41
44
  linting: ModelProviderSchema.optional(),
42
45
  context: ModelProviderSchema.optional(),
43
- testing: ModelProviderSchema.optional()
46
+ testing: ModelProviderSchema.optional(),
47
+ design: ModelProviderSchema.optional()
44
48
  }).optional().default({}),
45
49
  fallback: z.object({
46
50
  enabled: z.boolean(),
@@ -59,6 +63,7 @@ const ModelRouterConfigSchema = z.object({
59
63
  cerebras: ModelConfigSchema.optional(),
60
64
  deepinfra: ModelConfigSchema.optional(),
61
65
  openrouter: ModelConfigSchema.optional(),
66
+ moonshot: ModelConfigSchema.optional(),
62
67
  "anthropic-batch": ModelConfigSchema.optional(),
63
68
  custom: ModelConfigSchema.optional()
64
69
  }).optional().default({}),
@@ -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: Math.ceil(request.prompt.length / 4),
224
- outputTokens: Math.ceil(content.length / 4)
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";
@@ -17,6 +18,17 @@ import {
17
18
  createProvider
18
19
  } from "../../core/extensions/provider-adapter.js";
19
20
  import { AnthropicBatchClient } from "../anthropic/batch-client.js";
21
+ const QUOTA_ERROR_PATTERNS = [
22
+ /rate.?limit/i,
23
+ /quota.?exceeded/i,
24
+ /too many requests/i,
25
+ /429/,
26
+ /capacity/i,
27
+ /billing/i,
28
+ /usage.?limit/i,
29
+ /plan.?limit/i,
30
+ /max.*requests/i
31
+ ];
20
32
  class ClaudeCodeSubagentClient {
21
33
  tempDir;
22
34
  mockMode;
@@ -33,8 +45,12 @@ class ClaudeCodeSubagentClient {
33
45
  }
34
46
  /**
35
47
  * Execute a subagent task.
36
- * When multiProvider is enabled, routes cheap tasks to external providers.
37
- * Falls back to Claude Code CLI path when disabled or for complex tasks.
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)
38
54
  */
39
55
  async executeSubagent(request) {
40
56
  const startTime = Date.now();
@@ -47,6 +63,43 @@ class ClaudeCodeSubagentClient {
47
63
  if (this.mockMode) {
48
64
  return this.getMockResponse(request, startTime, subagentId);
49
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
+ }
50
103
  if (isFeatureEnabled("multiProvider")) {
51
104
  const taskType = request.type;
52
105
  const complexity = scoreComplexity(request.task, request.context);
@@ -108,6 +161,12 @@ class ClaudeCodeSubagentClient {
108
161
  tokens: result.usage.inputTokens + result.usage.outputTokens
109
162
  };
110
163
  } catch (error) {
164
+ if (optimal.provider === "anthropic" && this.isQuotaError(error.message)) {
165
+ logger.warn("Anthropic API quota hit, overflowing to Kimi", {
166
+ error: error.message
167
+ });
168
+ return this.executeKimiOverflow(request, startTime, subagentId);
169
+ }
111
170
  logger.warn(`Direct API failed for ${optimal.provider}, falling back`, {
112
171
  error: error.message
113
172
  });
@@ -173,9 +232,16 @@ Context (JSON): ${JSON.stringify(request.context)}`;
173
232
  output: result.text,
174
233
  duration: Date.now() - startTime,
175
234
  subagentType: request.type,
176
- tokens: this.estimateTokens(fullPrompt + result.text)
235
+ tokens: estimateTokens(fullPrompt + result.text)
177
236
  };
178
237
  } catch (error) {
238
+ if (this.isQuotaError(error.message)) {
239
+ logger.warn("Claude quota/rate limit hit, overflowing to Kimi", {
240
+ subagentId,
241
+ error: error.message
242
+ });
243
+ return this.executeKimiOverflow(request, startTime, subagentId);
244
+ }
179
245
  logger.error(`Subagent CLI execution failed: ${request.type}`, {
180
246
  error,
181
247
  subagentId
@@ -210,6 +276,195 @@ Context (JSON): ${JSON.stringify(request.context)}`;
210
276
  }
211
277
  });
212
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
+ }
403
+ /**
404
+ * Check if an error message indicates quota/rate limit exhaustion
405
+ */
406
+ isQuotaError(message) {
407
+ return QUOTA_ERROR_PATTERNS.some((pattern) => pattern.test(message));
408
+ }
409
+ /**
410
+ * Execute via Kimi/Moonshot API as overflow when Claude quota is exhausted.
411
+ * Uses OpenAI-compatible API at api.moonshot.ai/v1.
412
+ */
413
+ async executeKimiOverflow(request, startTime, subagentId) {
414
+ const apiKey = process.env["MOONSHOT_API_KEY"] || "";
415
+ if (!apiKey) {
416
+ logger.warn("No MOONSHOT_API_KEY set, cannot overflow to Kimi");
417
+ return {
418
+ success: false,
419
+ result: null,
420
+ error: "Claude quota exceeded and no MOONSHOT_API_KEY configured",
421
+ duration: Date.now() - startTime,
422
+ subagentType: request.type
423
+ };
424
+ }
425
+ try {
426
+ const adapter = createProvider("moonshot", {
427
+ apiKey,
428
+ baseUrl: "https://api.moonshot.ai/v1"
429
+ });
430
+ const prompt = this.buildSubagentPrompt(request);
431
+ const result = await adapter.complete(
432
+ [{ role: "user", content: prompt }],
433
+ { model: "kimi-k2.6", maxTokens: 8192 }
434
+ );
435
+ const text = result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
436
+ let parsed;
437
+ try {
438
+ parsed = JSON.parse(text);
439
+ } catch {
440
+ parsed = { rawOutput: text };
441
+ }
442
+ logger.info("Kimi overflow completed", {
443
+ subagentId,
444
+ tokens: result.usage.inputTokens + result.usage.outputTokens
445
+ });
446
+ return {
447
+ success: true,
448
+ result: parsed,
449
+ output: text,
450
+ duration: Date.now() - startTime,
451
+ subagentType: request.type,
452
+ tokens: result.usage.inputTokens + result.usage.outputTokens
453
+ };
454
+ } catch (kimiError) {
455
+ logger.error("Kimi overflow also failed", {
456
+ subagentId,
457
+ error: kimiError.message
458
+ });
459
+ return {
460
+ success: false,
461
+ result: null,
462
+ error: `Claude quota exceeded, Kimi fallback failed: ${kimiError.message}`,
463
+ duration: Date.now() - startTime,
464
+ subagentType: request.type
465
+ };
466
+ }
467
+ }
213
468
  /**
214
469
  * Build subagent prompt based on type
215
470
  */
@@ -319,6 +574,23 @@ Context (JSON): ${JSON.stringify(request.context)}`;
319
574
  Search parameters are in the context file.
320
575
 
321
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.`,
322
594
  publish: `You are a Publishing Subagent handling releases and deployments.
323
595
 
324
596
  Task: ${request.task}
@@ -504,6 +776,15 @@ function greetUser(name: string): string {
504
776
  changelog: "Initial release",
505
777
  published: false,
506
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
+ ]
507
788
  }
508
789
  };
509
790
  const result = mockResponses[request.type] || {};
@@ -513,15 +794,9 @@ function greetUser(name: string): string {
513
794
  output: `Mock ${request.type} subagent completed successfully`,
514
795
  duration: Date.now() - startTime,
515
796
  subagentType: request.type,
516
- tokens: this.estimateTokens(JSON.stringify(result))
797
+ tokens: estimateTokens(JSON.stringify(result))
517
798
  };
518
799
  }
519
- /**
520
- * Estimate token usage
521
- */
522
- estimateTokens(text) {
523
- return Math.ceil(text.length / 4);
524
- }
525
800
  /**
526
801
  * Cleanup temporary files
527
802
  */
@@ -541,6 +816,27 @@ function greetUser(name: string): string {
541
816
  }
542
817
  }
543
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
+ }
544
840
  /**
545
841
  * Get active subagent statistics
546
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 Math.ceil((prompt.length + response.length) / 4);
351
+ return estimateTokens(prompt + response);
351
352
  }
352
353
  /**
353
354
  * Calculate actual task cost