@stackmemoryai/stackmemory 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/LICENSE +131 -64
  2. package/README.md +3 -1
  3. package/bin/claude-sm +16 -1
  4. package/bin/claude-smd +16 -1
  5. package/bin/codex-smd +16 -1
  6. package/bin/gemini-sm +16 -1
  7. package/bin/hermes-sm +21 -0
  8. package/bin/hermes-smd +21 -0
  9. package/bin/opencode-sm +16 -1
  10. package/dist/src/cli/codex-sm.js +51 -11
  11. package/dist/src/cli/commands/brain.js +206 -0
  12. package/dist/src/cli/commands/company-os.js +184 -0
  13. package/dist/src/cli/commands/context.js +5 -0
  14. package/dist/src/cli/commands/operator.js +127 -0
  15. package/dist/src/cli/commands/orchestrate.js +2 -0
  16. package/dist/src/cli/commands/orchestrator.js +3 -2
  17. package/dist/src/cli/commands/patterns.js +254 -0
  18. package/dist/src/cli/commands/portal.js +161 -0
  19. package/dist/src/cli/commands/scaffold.js +92 -0
  20. package/dist/src/cli/commands/setup.js +1 -4
  21. package/dist/src/cli/commands/sync.js +253 -0
  22. package/dist/src/cli/commands/tasks.js +130 -1
  23. package/dist/src/cli/commands/vision.js +221 -0
  24. package/dist/src/cli/hermes-sm.js +224 -0
  25. package/dist/src/cli/index.js +15 -10
  26. package/dist/src/cli/utils/real-cli-bin.js +72 -0
  27. package/dist/src/core/brain/brain-store.js +187 -0
  28. package/dist/src/core/brain/brain-sync.js +193 -0
  29. package/dist/src/core/brain/index.js +78 -0
  30. package/dist/src/core/brain/types.js +10 -0
  31. package/dist/src/core/cache/token-estimator.js +24 -1
  32. package/dist/src/core/config/feature-flags.js +2 -6
  33. package/dist/src/core/context/frame-database.js +44 -0
  34. package/dist/src/core/context/recursive-context-manager.js +1 -1
  35. package/dist/src/core/context/rehydration.js +2 -1
  36. package/dist/src/core/database/sqlite-adapter.js +14 -1
  37. package/dist/src/core/models/model-router.js +33 -1
  38. package/dist/src/core/models/provider-pricing.js +58 -4
  39. package/dist/src/core/patterns/index.js +22 -0
  40. package/dist/src/core/patterns/pattern-applier.js +39 -0
  41. package/dist/src/core/patterns/pattern-observer.js +157 -0
  42. package/dist/src/core/patterns/pattern-store.js +259 -0
  43. package/dist/src/core/patterns/types.js +19 -0
  44. package/dist/src/core/retrieval/llm-context-retrieval.js +5 -4
  45. package/dist/src/core/retrieval/unified-context-assembler.js +11 -66
  46. package/dist/src/core/skill-packs/types.js +14 -1
  47. package/dist/src/core/storage/cloud-sync-manager.js +116 -0
  48. package/dist/src/core/storage/cloud-sync.js +574 -0
  49. package/dist/src/core/storage/two-tier-storage.js +5 -1
  50. package/dist/src/core/tasks/master-tasks-template.js +43 -0
  51. package/dist/src/core/tasks/md-task-parser.js +138 -0
  52. package/dist/src/core/vision/index.js +27 -0
  53. package/dist/src/core/vision/signals.js +79 -0
  54. package/dist/src/core/vision/types.js +22 -0
  55. package/dist/src/core/vision/vision-file.js +146 -0
  56. package/dist/src/core/vision/vision-loop.js +220 -0
  57. package/dist/src/core/wiki/wiki-compiler.js +103 -1
  58. package/dist/src/daemon/daemon-config.js +45 -0
  59. package/dist/src/daemon/services/desire-path-service.js +566 -0
  60. package/dist/src/daemon/services/research-stream-service.js +320 -0
  61. package/dist/src/daemon/services/telemetry-service.js +192 -0
  62. package/dist/src/daemon/unified-daemon.js +28 -1
  63. package/dist/src/features/browser/cli-browser-agent.js +417 -0
  64. package/dist/src/features/browser/stagehand-workflows.js +578 -0
  65. package/dist/src/features/operator/adapter-factory.js +62 -0
  66. package/dist/src/features/operator/browser-adapter.js +109 -0
  67. package/dist/src/features/operator/desktop-adapter.js +125 -0
  68. package/dist/src/features/operator/index.js +39 -0
  69. package/dist/src/features/operator/llm-decision.js +137 -0
  70. package/dist/src/features/operator/operator-logger.js +92 -0
  71. package/dist/src/features/operator/overnight-runner.js +327 -0
  72. package/dist/src/features/operator/screen-adapter.js +91 -0
  73. package/dist/src/features/operator/session-manager.js +127 -0
  74. package/dist/src/features/operator/state-machine.js +227 -0
  75. package/dist/src/features/operator/task-queue.js +81 -0
  76. package/dist/src/features/portal/index.js +26 -0
  77. package/dist/src/features/portal/server.js +240 -0
  78. package/dist/src/features/portal/types.js +14 -0
  79. package/dist/src/features/portal/ui.js +195 -0
  80. package/dist/src/features/tasks/task-aware-context.js +2 -1
  81. package/dist/src/features/tui/simple-monitor.js +0 -23
  82. package/dist/src/features/tui/swarm-monitor.js +8 -66
  83. package/dist/src/{integrations/diffmem/index.js → features/web/client/hooks/use-socket.js} +6 -5
  84. package/dist/src/features/web/client/lib/utils.js +12 -0
  85. package/dist/src/features/web/client/stores/session-store.js +12 -0
  86. package/dist/src/features/web/server/gcp-billing.js +76 -0
  87. package/dist/src/features/web/server/index.js +10 -0
  88. package/dist/src/features/web/server/spend-calculator.js +228 -0
  89. package/dist/src/hooks/schemas.js +4 -1
  90. package/dist/src/integrations/anthropic/client.js +3 -2
  91. package/dist/src/integrations/claude-code/agent-bridge.js +0 -3
  92. package/dist/src/integrations/claude-code/subagent-client.js +218 -11
  93. package/dist/src/integrations/claude-code/task-coordinator.js +2 -1
  94. package/dist/src/integrations/linear/webhook-retry.js +196 -0
  95. package/dist/src/integrations/linear/webhook-server.js +18 -22
  96. package/dist/src/integrations/mcp/handlers/cloud-sync-handlers.js +101 -0
  97. package/dist/src/integrations/mcp/handlers/index.js +27 -52
  98. package/dist/src/integrations/mcp/server.js +122 -335
  99. package/dist/src/integrations/mcp/tool-alias-registry.js +0 -73
  100. package/dist/src/integrations/mcp/tool-definitions.js +111 -510
  101. package/dist/src/mcp/stackmemory-mcp-server.js +404 -379
  102. package/dist/src/orchestrators/multimodal/determinism.js +2 -1
  103. package/dist/src/orchestrators/multimodal/harness.js +2 -1
  104. package/dist/src/skills/recursive-agent-orchestrator.js +2 -4
  105. package/dist/src/utils/process-cleanup.js +1 -7
  106. package/docs/README.md +42 -0
  107. package/docs/guides/README_INSTALL.md +208 -0
  108. package/package.json +18 -9
  109. package/scripts/claude-code-wrapper.sh +11 -0
  110. package/scripts/claude-sm-setup.sh +12 -1
  111. package/scripts/codex-wrapper.sh +11 -0
  112. package/scripts/git-hooks/branch-context-manager.sh +11 -0
  113. package/scripts/git-hooks/post-checkout-stackmemory.sh +11 -0
  114. package/scripts/git-hooks/post-commit-stackmemory.sh +11 -0
  115. package/scripts/git-hooks/pre-commit-stackmemory.sh +11 -0
  116. package/scripts/hooks/cleanup-shell.sh +12 -1
  117. package/scripts/hooks/task-complete.sh +12 -1
  118. package/scripts/install-code-execution-hooks.sh +12 -1
  119. package/scripts/install-sweep-hook.sh +12 -0
  120. package/scripts/install.sh +11 -0
  121. package/scripts/opencode-wrapper.sh +11 -0
  122. package/scripts/portal/cloud-init.yaml +69 -0
  123. package/scripts/portal/setup.sh +69 -0
  124. package/scripts/portal/stackmemory-portal.service +34 -0
  125. package/scripts/setup-claude-integration.sh +12 -1
  126. package/scripts/smoke-init-db.sh +23 -0
  127. package/scripts/stackmemory-daemon.sh +11 -0
  128. package/scripts/verify-dist.cjs +11 -4
  129. package/dist/src/cli/commands/ralph.js +0 -1053
  130. package/dist/src/hooks/diffmem-hooks.js +0 -376
  131. package/dist/src/integrations/diffmem/client.js +0 -208
  132. package/dist/src/integrations/diffmem/config.js +0 -14
  133. package/dist/src/integrations/greptile/client.js +0 -101
  134. package/dist/src/integrations/greptile/config.js +0 -14
  135. package/dist/src/integrations/greptile/index.js +0 -11
  136. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +0 -188
  137. package/dist/src/integrations/mcp/handlers/diffmem-handlers.js +0 -455
  138. package/dist/src/integrations/mcp/handlers/greptile-handlers.js +0 -456
  139. package/dist/src/integrations/mcp/handlers/provider-handlers.js +0 -227
  140. package/dist/src/integrations/ralph/bridge/ralph-stackmemory-bridge.js +0 -863
  141. package/dist/src/integrations/ralph/context/context-budget-manager.js +0 -308
  142. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +0 -354
  143. package/dist/src/integrations/ralph/index.js +0 -17
  144. package/dist/src/integrations/ralph/learning/pattern-learner.js +0 -416
  145. package/dist/src/integrations/ralph/lifecycle/iteration-lifecycle.js +0 -448
  146. package/dist/src/integrations/ralph/loopmax.js +0 -488
  147. package/dist/src/integrations/ralph/monitoring/swarm-dashboard.js +0 -293
  148. package/dist/src/integrations/ralph/monitoring/swarm-registry.js +0 -107
  149. package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +0 -508
  150. package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +0 -407
  151. package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +0 -495
  152. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +0 -387
  153. package/dist/src/integrations/ralph/performance/performance-optimizer.js +0 -357
  154. package/dist/src/integrations/ralph/recovery/crash-recovery.js +0 -461
  155. package/dist/src/integrations/ralph/state/state-reconciler.js +0 -420
  156. package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +0 -444
  157. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -1005
  158. package/dist/src/integrations/ralph/visualization/ralph-debugger.js +0 -635
  159. package/scripts/ralph-loop-implementation.js +0 -404
  160. /package/dist/src/{integrations/diffmem/types.js → core/storage/cloud-sync-types.js} +0 -0
  161. /package/dist/src/{integrations/greptile → features/operator}/types.js +0 -0
  162. /package/dist/src/{integrations/ralph/types.js → features/web/client/next-env.d.js} +0 -0
@@ -0,0 +1,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: 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";
@@ -44,8 +45,12 @@ class ClaudeCodeSubagentClient {
44
45
  }
45
46
  /**
46
47
  * Execute a subagent task.
47
- * When multiProvider is enabled, routes cheap tasks to external providers.
48
- * 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)
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: this.estimateTokens(fullPrompt + result.text)
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: this.estimateTokens(JSON.stringify(result))
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 Math.ceil((prompt.length + response.length) / 4);
351
+ return estimateTokens(prompt + response);
351
352
  }
352
353
  /**
353
354
  * Calculate actual task cost