chorus-codes 0.8.24 → 0.8.26

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 (154) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +1 -1
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  6. package/.next/server/app/_global-error.html +1 -1
  7. package/.next/server/app/_global-error.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/connect/page.js +1 -1
  24. package/.next/server/app/connect/page.js.nft.json +1 -1
  25. package/.next/server/app/connect/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app/demo/[scenario]/page.js +1 -1
  27. package/.next/server/app/demo/[scenario]/page.js.nft.json +1 -1
  28. package/.next/server/app/demo/[scenario]/page_client-reference-manifest.js +1 -1
  29. package/.next/server/app/new/page.js +1 -1
  30. package/.next/server/app/new/page.js.nft.json +1 -1
  31. package/.next/server/app/new/page_client-reference-manifest.js +1 -1
  32. package/.next/server/app/new.html +1 -1
  33. package/.next/server/app/new.rsc +3 -3
  34. package/.next/server/app/new.segments/_full.segment.rsc +3 -3
  35. package/.next/server/app/new.segments/_head.segment.rsc +1 -1
  36. package/.next/server/app/new.segments/_index.segment.rsc +2 -2
  37. package/.next/server/app/new.segments/_tree.segment.rsc +2 -2
  38. package/.next/server/app/new.segments/new/__PAGE__.segment.rsc +2 -2
  39. package/.next/server/app/new.segments/new.segment.rsc +1 -1
  40. package/.next/server/app/onboarding/page.js +1 -1
  41. package/.next/server/app/onboarding/page.js.nft.json +1 -1
  42. package/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  43. package/.next/server/app/onboarding.html +1 -1
  44. package/.next/server/app/onboarding.rsc +3 -3
  45. package/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
  46. package/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  47. package/.next/server/app/onboarding.segments/_index.segment.rsc +2 -2
  48. package/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
  49. package/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +2 -2
  50. package/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  51. package/.next/server/app/page.js +2 -2
  52. package/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/server/app/personas/page.js +1 -1
  55. package/.next/server/app/personas/page.js.nft.json +1 -1
  56. package/.next/server/app/personas/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/personas.html +1 -1
  58. package/.next/server/app/personas.rsc +3 -3
  59. package/.next/server/app/personas.segments/_full.segment.rsc +3 -3
  60. package/.next/server/app/personas.segments/_head.segment.rsc +1 -1
  61. package/.next/server/app/personas.segments/_index.segment.rsc +2 -2
  62. package/.next/server/app/personas.segments/_tree.segment.rsc +2 -2
  63. package/.next/server/app/personas.segments/personas/__PAGE__.segment.rsc +2 -2
  64. package/.next/server/app/personas.segments/personas.segment.rsc +1 -1
  65. package/.next/server/app/runs/[runId]/page.js +2 -2
  66. package/.next/server/app/runs/[runId]/page.js.nft.json +1 -1
  67. package/.next/server/app/runs/[runId]/page_client-reference-manifest.js +1 -1
  68. package/.next/server/app/runs/page.js +2 -2
  69. package/.next/server/app/runs/page.js.nft.json +1 -1
  70. package/.next/server/app/runs/page_client-reference-manifest.js +1 -1
  71. package/.next/server/app/settings/page.js +3 -3
  72. package/.next/server/app/settings/page.js.nft.json +1 -1
  73. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  74. package/.next/server/app/settings/permissions/page.js +1 -1
  75. package/.next/server/app/settings/permissions/page.js.nft.json +1 -1
  76. package/.next/server/app/settings/permissions/page_client-reference-manifest.js +1 -1
  77. package/.next/server/app/settings.html +1 -1
  78. package/.next/server/app/settings.rsc +3 -3
  79. package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
  80. package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
  81. package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
  82. package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
  83. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
  84. package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
  85. package/.next/server/app/templates/page.js +2 -2
  86. package/.next/server/app/templates/page.js.nft.json +1 -1
  87. package/.next/server/app/templates/page_client-reference-manifest.js +1 -1
  88. package/.next/server/app/templates.html +1 -1
  89. package/.next/server/app/templates.rsc +3 -3
  90. package/.next/server/app/templates.segments/_full.segment.rsc +3 -3
  91. package/.next/server/app/templates.segments/_head.segment.rsc +1 -1
  92. package/.next/server/app/templates.segments/_index.segment.rsc +2 -2
  93. package/.next/server/app/templates.segments/_tree.segment.rsc +2 -2
  94. package/.next/server/app/templates.segments/templates/__PAGE__.segment.rsc +2 -2
  95. package/.next/server/app/templates.segments/templates.segment.rsc +1 -1
  96. package/.next/server/app-paths-manifest.json +1 -1
  97. package/.next/server/chunks/189.js +1 -0
  98. package/.next/server/chunks/{144.js → 21.js} +1 -1
  99. package/.next/server/chunks/{668.js → 313.js} +1 -1
  100. package/.next/server/chunks/681.js +1 -1
  101. package/.next/server/middleware-build-manifest.js +1 -1
  102. package/.next/server/pages/404.html +1 -1
  103. package/.next/server/pages/500.html +1 -1
  104. package/.next/server/server-reference-manifest.json +1 -1
  105. package/.next/static/chunks/249-2e840495c38ee022.js +25 -0
  106. package/.next/static/chunks/641-2908cb9553b8753a.js +1 -0
  107. package/.next/static/chunks/690-092c26db4082d49a.js +1 -0
  108. package/.next/static/chunks/app/connect/{page-a3a0af374f90ad4c.js → page-ad4409761e870bd0.js} +1 -1
  109. package/.next/static/chunks/app/demo/[scenario]/{page-6a0e4aec4bb96fee.js → page-39673968f543c473.js} +1 -1
  110. package/.next/static/chunks/app/new/{page-b96d75506030acf8.js → page-b5f609ab9413ac00.js} +1 -1
  111. package/.next/static/chunks/app/onboarding/{page-4be5a1d944e32672.js → page-8b0850fef487abdc.js} +1 -1
  112. package/.next/static/chunks/app/{page-35375a7c8b3d117a.js → page-9bdbaad592d0ce56.js} +1 -1
  113. package/.next/static/chunks/app/personas/{page-3884f8907107a4e6.js → page-440c6033a773100c.js} +1 -1
  114. package/.next/static/chunks/app/runs/[runId]/{page-b5bcf0c093389207.js → page-ffc36f12f1b63ebe.js} +1 -1
  115. package/.next/static/chunks/app/runs/{page-376175c1ac803558.js → page-6962ea572c9e4b74.js} +1 -1
  116. package/.next/static/chunks/app/settings/page-ad7180ee0d142704.js +25 -0
  117. package/.next/static/chunks/app/settings/permissions/{page-c90795aa9299bbe8.js → page-cd767401ac71a29c.js} +1 -1
  118. package/.next/static/chunks/app/templates/page-0112ab3c7ab5185d.js +1 -0
  119. package/.next/static/css/df4972a256406ec7.css +3 -0
  120. package/.next/trace +20 -20
  121. package/.next/trace-build +1 -1
  122. package/dist/daemon/cli-semaphore.js +266 -0
  123. package/dist/daemon/cli-semaphore.js.map +1 -0
  124. package/dist/daemon/routes/settings.js +39 -0
  125. package/dist/daemon/routes/settings.js.map +1 -1
  126. package/dist/daemon/runner/doer-driver.js +243 -214
  127. package/dist/daemon/runner/doer-driver.js.map +1 -1
  128. package/dist/daemon/runner/doer.js +24 -1
  129. package/dist/daemon/runner/doer.js.map +1 -1
  130. package/dist/daemon/runner/reviewer-driver.js +299 -255
  131. package/dist/daemon/runner/reviewer-driver.js.map +1 -1
  132. package/dist/daemon/runner/reviewer.js +27 -1
  133. package/dist/daemon/runner/reviewer.js.map +1 -1
  134. package/dist/daemon/runner.js +19 -0
  135. package/dist/daemon/runner.js.map +1 -1
  136. package/dist/lib/db/chats.js +28 -0
  137. package/dist/lib/db/chats.js.map +1 -1
  138. package/dist/lib/db/connection.js +6 -0
  139. package/dist/lib/db/connection.js.map +1 -1
  140. package/dist/lib/db/schema.sql +6 -0
  141. package/dist/lib/model-pricing.js +306 -0
  142. package/dist/lib/model-pricing.js.map +1 -0
  143. package/dist/lib/settings/concurrency.js +101 -0
  144. package/dist/lib/settings/concurrency.js.map +1 -0
  145. package/package.json +1 -1
  146. package/.next/server/chunks/946.js +0 -1
  147. package/.next/static/chunks/116-8bf7e014066cedde.js +0 -25
  148. package/.next/static/chunks/15-d438a2b057302bed.js +0 -1
  149. package/.next/static/chunks/641-60721f44faf711b9.js +0 -1
  150. package/.next/static/chunks/app/settings/page-1792a3e289409b2d.js +0 -25
  151. package/.next/static/chunks/app/templates/page-1449b0aea2e7cb68.js +0 -1
  152. package/.next/static/css/d2bb161eb5bee944.css +0 -3
  153. /package/.next/static/{jdUcCHB2b5lVrf4v8iQjl → eOeXty5cBGWg7xnmtF6ST}/_buildManifest.js +0 -0
  154. /package/.next/static/{jdUcCHB2b5lVrf4v8iQjl → eOeXty5cBGWg7xnmtF6ST}/_ssgManifest.js +0 -0
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports._testing = void 0;
37
+ exports.getModelPricing = getModelPricing;
38
+ exports.synthesizeCostUsd = synthesizeCostUsd;
39
+ /**
40
+ * Model-pricing lookup. Backs the home page's "plan equivalent" spend
41
+ * column — for CLI shims that don't self-report cost (gemini-cli emits
42
+ * tokens only; codex `exec` emits nothing), we synthesize cost from
43
+ * tokens × $/token using OpenRouter's public model catalog.
44
+ *
45
+ * OpenRouter exposes prices in $ per token (not per Mtok) at
46
+ * `GET https://openrouter.ai/api/v1/models` — no auth required for the
47
+ * catalog. We fetch once at first need, cache in memory, persist a 24h
48
+ * disk copy at `~/.chorus/model-pricing.json` so a daemon restart reuses
49
+ * the snapshot instead of refetching cold.
50
+ *
51
+ * Model-id matching is dash/dot-insensitive: chorus uses `claude-opus-4-7`
52
+ * while OpenRouter uses `anthropic/claude-opus-4.7`. We normalize both
53
+ * sides to "lowercased, dots → dashes, vendor prefix stripped" before
54
+ * the lookup so the same logical model resolves regardless of cosmetic
55
+ * id drift.
56
+ */
57
+ const fs = __importStar(require("fs"));
58
+ const os = __importStar(require("os"));
59
+ const path = __importStar(require("path"));
60
+ const CACHE_PATH = path.join(os.homedir(), '.chorus', 'model-pricing.json');
61
+ const DISK_TTL_MS = 24 * 60 * 60 * 1000;
62
+ const MEMORY_TTL_MS = 24 * 60 * 60 * 1000;
63
+ const FETCH_TIMEOUT_MS = 8_000;
64
+ const FAILURE_COOLDOWN_MS = 60_000;
65
+ const OPENROUTER_MODELS_URL = 'https://openrouter.ai/api/v1/models';
66
+ let memoryCache = null;
67
+ let inflightFetch = null;
68
+ // Negative cache: last failed-fetch timestamp. Suppresses re-fetch
69
+ // attempts for FAILURE_COOLDOWN_MS so an offline daemon doesn't pay an
70
+ // 8s timeout on every reviewer/doer that lacks costUsd. opencode-cli-2
71
+ // + cli-3 + openrouter all flagged this as a hot-path latency bug.
72
+ let lastFetchFailureAt = null;
73
+ /**
74
+ * Lowercase + replace dots with dashes. Used on both lookup key and
75
+ * catalog ids so `claude-opus-4-7` matches `anthropic/claude-opus-4.7`.
76
+ */
77
+ function normalize(id) {
78
+ return id.toLowerCase().replace(/\./g, '-');
79
+ }
80
+ /**
81
+ * Strip a leading `openrouter:` voice-id prefix the cockpit uses for
82
+ * OpenRouter voices — the actual model id is everything after the colon.
83
+ * Gateway prefixes (`opencode-go/`, etc.) are NOT stripped here; instead
84
+ * `getModelPricing` falls back to a bare-id lookup when the full
85
+ * normalized form misses. This handles `opencode-go/kimi-k2.6` →
86
+ * bare `kimi-k2-6` → catalog hit on `moonshotai/kimi-k2.6`.
87
+ */
88
+ function stripVoicePrefix(id) {
89
+ if (id.startsWith('openrouter:'))
90
+ return id.slice('openrouter:'.length);
91
+ return id;
92
+ }
93
+ async function fetchOpenRouterCatalog() {
94
+ try {
95
+ const ac = new AbortController();
96
+ const timer = setTimeout(() => ac.abort(), FETCH_TIMEOUT_MS);
97
+ const res = await fetch(OPENROUTER_MODELS_URL, { signal: ac.signal });
98
+ clearTimeout(timer);
99
+ if (!res.ok)
100
+ return null;
101
+ const body = (await res.json());
102
+ if (!Array.isArray(body.data))
103
+ return null;
104
+ const prices = {};
105
+ for (const raw of body.data) {
106
+ if (typeof raw.id !== 'string' || !raw.id)
107
+ continue;
108
+ const promptStr = raw.pricing?.prompt;
109
+ const completionStr = raw.pricing?.completion;
110
+ const inputCost = typeof promptStr === 'string' ? Number.parseFloat(promptStr) : NaN;
111
+ const outputCost = typeof completionStr === 'string'
112
+ ? Number.parseFloat(completionStr)
113
+ : NaN;
114
+ if (!Number.isFinite(inputCost) || !Number.isFinite(outputCost))
115
+ continue;
116
+ const fullId = raw.id; // e.g. "anthropic/claude-opus-4.7"
117
+ const normalizedFull = normalize(fullId);
118
+ const bareId = fullId.includes('/') ? fullId.split('/').pop() : fullId;
119
+ const normalizedBare = bareId ? normalize(bareId) : null;
120
+ const price = {
121
+ inputCostPerToken: inputCost,
122
+ outputCostPerToken: outputCost,
123
+ };
124
+ // Index both forms. Bare form lets a chorus call with model id
125
+ // `gemini-2.5-pro` (no vendor prefix) match `google/gemini-2.5-pro`;
126
+ // prefixed form lets a call with `x-ai/grok-4` match exactly.
127
+ // Bare collisions across vendors are possible (theoretically two
128
+ // vendors could ship a model with the same suffix); first-wins is
129
+ // fine for chorus's lineage-disambiguated calls.
130
+ if (normalizedBare && !(normalizedBare in prices)) {
131
+ prices[normalizedBare] = price;
132
+ }
133
+ prices[normalizedFull] = price;
134
+ }
135
+ return { fetchedAt: Date.now(), prices };
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }
141
+ function loadDiskCache() {
142
+ try {
143
+ if (!fs.existsSync(CACHE_PATH))
144
+ return null;
145
+ const raw = fs.readFileSync(CACHE_PATH, 'utf-8');
146
+ const parsed = JSON.parse(raw);
147
+ if (typeof parsed.fetchedAt !== 'number' ||
148
+ !parsed.prices ||
149
+ typeof parsed.prices !== 'object') {
150
+ return null;
151
+ }
152
+ if (Date.now() - parsed.fetchedAt > DISK_TTL_MS)
153
+ return null;
154
+ return parsed;
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ }
160
+ function persistDiskCache(snapshot) {
161
+ try {
162
+ const dir = path.dirname(CACHE_PATH);
163
+ if (!fs.existsSync(dir))
164
+ fs.mkdirSync(dir, { recursive: true });
165
+ // Atomic write: render to a temp file then rename. POSIX rename is
166
+ // atomic on the same filesystem, so a crash mid-write leaves either
167
+ // the old file or the fully-written new file — never a truncated one.
168
+ // Reviewers (codex, opencode-cli-2/3/4, openrouter) all flagged the
169
+ // direct writeFileSync as a corruption risk on crash.
170
+ const tmpPath = `${CACHE_PATH}.tmp.${process.pid}`;
171
+ fs.writeFileSync(tmpPath, JSON.stringify(snapshot), { encoding: 'utf-8' });
172
+ fs.renameSync(tmpPath, CACHE_PATH);
173
+ }
174
+ catch {
175
+ /* best-effort — pricing cache is informational */
176
+ }
177
+ }
178
+ /**
179
+ * Ensure the in-memory cache is populated and fresh. Loads the disk
180
+ * copy if it exists and is within DISK_TTL_MS, otherwise fetches from
181
+ * OpenRouter. Concurrent callers share an inflight promise so a hot
182
+ * daemon boot doesn't fan out N fetches. A failed fetch parks the
183
+ * cooldown so the daemon doesn't re-issue 8s-timeout calls back-to-back.
184
+ *
185
+ * Memory TTL (MEMORY_TTL_MS) was added after opencode-cli-4 flagged
186
+ * "memory cache never expires" — a long-running daemon would otherwise
187
+ * serve week-old pricing despite the disk file expiring on schedule.
188
+ */
189
+ async function ensureCache() {
190
+ const now = Date.now();
191
+ if (memoryCache && now - memoryCache.fetchedAt < MEMORY_TTL_MS) {
192
+ return memoryCache;
193
+ }
194
+ // Memory expired — fall through to disk/fetch. Don't clear `memoryCache`
195
+ // yet; a failed refresh below should still be able to serve a stale
196
+ // value rather than nothing (graceful degradation when the network is
197
+ // out and nothing newer is available).
198
+ const disk = loadDiskCache();
199
+ if (disk) {
200
+ memoryCache = disk;
201
+ return memoryCache;
202
+ }
203
+ // Negative cache: if a recent fetch failed, don't retry within the
204
+ // cooldown window. Returns the stale memory cache (if any) so callers
205
+ // get a best-effort answer instead of paying another 8s timeout.
206
+ if (lastFetchFailureAt !== null &&
207
+ now - lastFetchFailureAt < FAILURE_COOLDOWN_MS) {
208
+ return memoryCache;
209
+ }
210
+ if (inflightFetch)
211
+ return inflightFetch;
212
+ inflightFetch = (async () => {
213
+ try {
214
+ const fetched = await fetchOpenRouterCatalog();
215
+ if (fetched) {
216
+ memoryCache = fetched;
217
+ lastFetchFailureAt = null;
218
+ persistDiskCache(fetched);
219
+ }
220
+ else {
221
+ lastFetchFailureAt = Date.now();
222
+ }
223
+ return memoryCache;
224
+ }
225
+ finally {
226
+ inflightFetch = null;
227
+ }
228
+ })();
229
+ return inflightFetch;
230
+ }
231
+ /**
232
+ * Look up pricing for a given chorus model id. Returns null when the
233
+ * model isn't in the OpenRouter catalog or the catalog isn't reachable
234
+ * (caller falls back to "no shadow cost recorded" — degraded gracefully).
235
+ *
236
+ * Lookup strategy (each step skipped if previous matched):
237
+ * 1. Full normalized id — `claude-opus-4-7` → catalog `claude-opus-4-7`,
238
+ * `x-ai/grok-4` → catalog `x-ai/grok-4`.
239
+ * 2. Bare suffix after the last `/` — handles gateway-prefixed ids
240
+ * like `opencode-go/kimi-k2.6` whose prefix isn't a known vendor;
241
+ * the bare `kimi-k2-6` key matches OpenRouter's `moonshotai/kimi-k2.6`.
242
+ * First-wins on bare-id collisions across vendors is acknowledged
243
+ * in fetchOpenRouterCatalog.
244
+ */
245
+ async function getModelPricing(modelId) {
246
+ if (!modelId)
247
+ return null;
248
+ const cache = await ensureCache();
249
+ if (!cache)
250
+ return null;
251
+ const normalized = normalize(stripVoicePrefix(modelId));
252
+ const direct = cache.prices[normalized];
253
+ if (direct)
254
+ return direct;
255
+ if (normalized.includes('/')) {
256
+ const bare = normalized.slice(normalized.lastIndexOf('/') + 1);
257
+ return cache.prices[bare] ?? null;
258
+ }
259
+ return null;
260
+ }
261
+ /**
262
+ * Convert (inputTokens, outputTokens, cachedInputTokens, model) → USD
263
+ * cost. Returns undefined when pricing is unavailable so callers can
264
+ * leave costUsd unset rather than reporting a fake $0.
265
+ *
266
+ * Contract: `inputTokens` and `cachedInputTokens` are MUTUALLY EXCLUSIVE
267
+ * partitions of total input — Claude Code emits them this way (the
268
+ * `usage.input_tokens` field is non-cached only; cached lives in
269
+ * `cache_read_input_tokens`). We sum both and price at the full input
270
+ * rate. OpenRouter's catalog doesn't expose per-model cached pricing
271
+ * reliably, so cached-as-full-price is intentional: it over-estimates
272
+ * the plan-equivalent spend by 0–10% on cache-heavy reviews, which is
273
+ * the right direction for "what your subscription saves you" framing
274
+ * (never under-promise the saving). Reviewers caught this — earlier
275
+ * draft accepted cachedInputTokens but never read it, leaving the
276
+ * contract ambiguous.
277
+ */
278
+ async function synthesizeCostUsd(modelId, usage) {
279
+ if (!modelId)
280
+ return undefined;
281
+ const inputTokens = usage.inputTokens ?? 0;
282
+ const cachedInputTokens = usage.cachedInputTokens ?? 0;
283
+ const outputTokens = usage.outputTokens ?? 0;
284
+ const totalInput = inputTokens + cachedInputTokens;
285
+ if (totalInput <= 0 && outputTokens <= 0)
286
+ return undefined;
287
+ const price = await getModelPricing(modelId);
288
+ if (!price)
289
+ return undefined;
290
+ const cost = totalInput * price.inputCostPerToken +
291
+ outputTokens * price.outputCostPerToken;
292
+ return Number.isFinite(cost) && cost >= 0 ? cost : undefined;
293
+ }
294
+ // Test seam — exercised from tests/model-pricing.test.ts.
295
+ exports._testing = {
296
+ normalize,
297
+ stripVoicePrefix,
298
+ setMemoryCache: (snapshot) => {
299
+ memoryCache = snapshot;
300
+ inflightFetch = null;
301
+ },
302
+ getMemoryCache: () => memoryCache,
303
+ CACHE_PATH,
304
+ DISK_TTL_MS,
305
+ };
306
+ //# sourceMappingURL=model-pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-pricing.js","sourceRoot":"","sources":["../../src/lib/model-pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * Typed accessor for chorus's concurrency settings — caps on parallel
4
+ * CLI subprocesses, daemon-wide.
5
+ *
6
+ * Two knobs:
7
+ *
8
+ * - `maxParallelCli` (1..10, default 3): GLOBAL cap on the total
9
+ * number of local-CLI shim subprocesses (reviewers + doer combined)
10
+ * in flight across the whole daemon. HTTP-dispatched shims
11
+ * (openrouter and friends) don't count — they're network calls and
12
+ * consume zero local CPU/RAM.
13
+ *
14
+ * - `perCli` (1..5, defaults below): per-binary cap, also daemon-wide.
15
+ * Lets the user say "max 2 opencode" even when 4+ chats are in
16
+ * flight — opencode subprocesses are ~450 MB each and a 4-stack hits
17
+ * swap. Composes with the global cap as `min(global, perCli)`: a
18
+ * reviewer slot must acquire BOTH a global slot AND a per-CLI slot
19
+ * before spawning, whichever is tighter is the queue.
20
+ *
21
+ * Why a single setting object instead of two: keeps the YAML/Form view
22
+ * tidy, and lets `mm.update_settings` apply both atomically. Empty
23
+ * `perCli` values fall through to defaults so the user only stores
24
+ * deltas from default.
25
+ *
26
+ * The daemon reads this dynamically each acquire (not per chat or per
27
+ * boot) so settings changes take effect on the next reviewer to start —
28
+ * no daemon restart needed.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports._defaults = exports.ConcurrencySchema = exports.CLI_LINEAGES = void 0;
32
+ exports.resolvePerCliCap = resolvePerCliCap;
33
+ exports.getConcurrency = getConcurrency;
34
+ exports.setConcurrency = setConcurrency;
35
+ const zod_1 = require("zod");
36
+ const db_1 = require("../db");
37
+ /** CLIs we cap individually. Mirrors the keys in `cli-detect.ts`. */
38
+ exports.CLI_LINEAGES = [
39
+ 'claude-code',
40
+ 'codex-cli',
41
+ 'gemini-cli',
42
+ 'opencode-cli',
43
+ 'kimi-cli',
44
+ ];
45
+ /**
46
+ * Defaults reflect what we learned during the May 2026 OOM incident:
47
+ * opencode is heaviest (~450 MB / proc), gemini parser leaks listeners
48
+ * under churn so we keep it tight, claude/codex are lighter and can run
49
+ * 3-wide. Adjustable in /settings.
50
+ */
51
+ const DEFAULT_PER_CLI = {
52
+ 'claude-code': 3,
53
+ 'codex-cli': 3,
54
+ 'gemini-cli': 2,
55
+ 'opencode-cli': 2,
56
+ 'kimi-cli': 2,
57
+ };
58
+ const DEFAULT_MAX_PARALLEL_CLI = 3;
59
+ const PER_CLI_KEY_SCHEMA = zod_1.z.enum(exports.CLI_LINEAGES);
60
+ exports.ConcurrencySchema = zod_1.z.object({
61
+ maxParallelCli: zod_1.z.number().int().min(1).max(10).default(DEFAULT_MAX_PARALLEL_CLI),
62
+ perCli: zod_1.z
63
+ .record(PER_CLI_KEY_SCHEMA, zod_1.z.number().int().min(1).max(5))
64
+ .default({}),
65
+ });
66
+ const SETTINGS_KEY = 'concurrency';
67
+ /**
68
+ * Resolve the per-CLI cap for a given lineage with default fallback.
69
+ * Centralized so callers don't sprinkle `?? DEFAULT_PER_CLI[k]` checks.
70
+ */
71
+ function resolvePerCliCap(config, lineage) {
72
+ return config.perCli[lineage] ?? DEFAULT_PER_CLI[lineage];
73
+ }
74
+ async function getConcurrency() {
75
+ const raw = await db_1.settings.get(SETTINGS_KEY);
76
+ if (raw === null) {
77
+ return exports.ConcurrencySchema.parse({});
78
+ }
79
+ // safeParse so a hand-edited bogus value never crashes the runner —
80
+ // fall back to defaults, the cockpit will surface the broken state on
81
+ // next save anyway.
82
+ const result = exports.ConcurrencySchema.safeParse(raw);
83
+ if (!result.success) {
84
+ return exports.ConcurrencySchema.parse({});
85
+ }
86
+ return result.data;
87
+ }
88
+ async function setConcurrency(config) {
89
+ const validated = exports.ConcurrencySchema.parse(config);
90
+ await db_1.settings.set(SETTINGS_KEY, validated);
91
+ }
92
+ /**
93
+ * Defaults exposed for the cockpit so the form can pre-populate
94
+ * placeholder values matching what the daemon would use if a row is
95
+ * left blank.
96
+ */
97
+ exports._defaults = {
98
+ maxParallelCli: DEFAULT_MAX_PARALLEL_CLI,
99
+ perCli: DEFAULT_PER_CLI,
100
+ };
101
+ //# sourceMappingURL=concurrency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"concurrency.js","sourceRoot":"","sources":["../../../src/lib/settings/concurrency.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;;;AAgDH,4CAKC;AAED,wCAaC;AAED,wCAGC;AAvED,6BAAwB;AACxB,8BAAiC;AAEjC,qEAAqE;AACxD,QAAA,YAAY,GAAG;IAC1B,aAAa;IACb,WAAW;IACX,YAAY;IACZ,cAAc;IACd,UAAU;CACF,CAAC;AAGX;;;;;GAKG;AACH,MAAM,eAAe,GAAkC;IACrD,aAAa,EAAE,CAAC;IAChB,WAAW,EAAE,CAAC;IACd,YAAY,EAAE,CAAC;IACf,cAAc,EAAE,CAAC;IACjB,UAAU,EAAE,CAAC;CACd,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,CAAC;AAEnC,MAAM,kBAAkB,GAAG,OAAC,CAAC,IAAI,CAAC,oBAAY,CAAC,CAAC;AAEnC,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,cAAc,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC;IACjF,MAAM,EAAE,OAAC;SACN,MAAM,CAAC,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;SAC1D,OAAO,CAAC,EAAE,CAAC;CACf,CAAC,CAAC;AAIH,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC;;;GAGG;AACH,SAAgB,gBAAgB,CAC9B,MAAyB,EACzB,OAAsB;IAEtB,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAEM,KAAK,UAAU,cAAc;IAClC,MAAM,GAAG,GAAG,MAAM,aAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,OAAO,yBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,oEAAoE;IACpE,sEAAsE;IACtE,oBAAoB;IACpB,MAAM,MAAM,GAAG,yBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,yBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC;AAEM,KAAK,UAAU,cAAc,CAAC,MAAyB;IAC5D,MAAM,SAAS,GAAG,yBAAiB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,aAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACU,QAAA,SAAS,GAAG;IACvB,cAAc,EAAE,wBAAwB;IACxC,MAAM,EAAE,eAAe;CACxB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chorus-codes",
3
- "version": "0.8.24",
3
+ "version": "0.8.26",
4
4
  "description": "Driver-agnostic multi-LLM peer review for code decisions. Bring your own CLI; Chorus convenes 2-4 other LLMs to review the work before you ship.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "99x Agency",
@@ -1 +0,0 @@
1
- "use strict";exports.id=946,exports.ids=[946],exports.modules={23154:(a,b,c)=>{c.d(b,{MobileTopBar:()=>d});let d=(0,c(96600).registerClientReference)(function(){throw Error("Attempted to call MobileTopBar() from the server but MobileTopBar is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/ubuntu/dev/chorus/src/components/mobile-top-bar.tsx","MobileTopBar")},30676:(a,b,c)=>{c.d(b,{G:()=>g});var d=c(87796),e=c(80434),f=c(23154);function g({children:a}){return(0,d.jsxs)("div",{className:"flex h-screen w-screen overflow-hidden bg-background text-foreground",children:[(0,d.jsx)(e.AppSidebar,{}),(0,d.jsxs)("div",{className:"flex flex-1 flex-col overflow-hidden",children:[(0,d.jsx)(f.MobileTopBar,{}),(0,d.jsx)("main",{className:"flex-1 overflow-y-auto",children:a})]})]})}},54197:(a,b,c)=>{c.d(b,{$:()=>g,T:()=>h});let d="/api/v1";async function e(){let a=process.env.PORT;return a?`http://127.0.0.1:${a}/api/daemon`:"http://127.0.0.1:5050/api/daemon"}async function f(){return await e()}class g extends Error{constructor(a,b,c,d){super(c),this.code=a,this.statusCode=b,this.details=d,this.name="DaemonError"}}async function h(a,b={}){let c=await f(),e=a.startsWith("/")?a:`/${a}`,i=e===d||e.startsWith(`${d}/`)?e:`${d}${e}`,j=`${c.replace(/\/$/,"")}${i}`;try{let a=await fetch(j,{...b,headers:{"Content-Type":"application/json",...b.headers}}),c=await a.json().catch(()=>({ok:!1,error:{code:"parse_error",message:"Failed to parse response"}}));if(!c.ok){if("string"==typeof c.error&&"string"==typeof c.message)throw new g(c.error,a.status,c.message);throw new g(c.error?.code||"unknown",a.status,c.error?.message||`Daemon returned ${a.status}`,c.error?.details)}return c.data}catch(a){if(a instanceof g)throw a;if(a instanceof TypeError&&a.message.includes("fetch"))throw new g("connection_failed",0,"Failed to connect to Chorus daemon. Is it running?");throw new g("unknown",0,a instanceof Error?a.message:"Unknown error")}}},79645:(a,b,c)=>{c.d(b,{$v:()=>d.$,PB:()=>g,mt:()=>p.mt,GT:()=>r,$F:()=>o,Ih:()=>f,_$:()=>q,AQ:()=>p.AQ,eG:()=>n});var d=c(54197);function e(a){let b;if(a.attached_files)try{let c=JSON.parse(a.attached_files);Array.isArray(c)&&(b=c)}catch{}return{id:a.id,slug:a.slug??void 0,work:a.work,templateId:a.template_id,status:a.status,currentPhaseIdx:a.current_phase_idx??0,yolo:!!a.yolo,attachedFiles:b,repoPath:a.repo_path??void 0,prUrl:a.pr_url??void 0,shipError:a.ship_error??void 0,artifact:a.artifact??void 0,verdict:a.verdict??void 0,createdAt:a.created_at,updatedAt:a.updated_at,finishedAt:a.finished_at??void 0}}async function f(a){let b=new URLSearchParams;a?.limit&&b.append("limit",a.limit.toString()),a?.offset&&b.append("offset",a.offset.toString()),a?.status&&b.append("status",a.status);let c=b.toString();return(await (0,d.T)(`/chats${c?`?${c}`:""}`)).items.map(e)}async function g(a){return e(await (0,d.T)(`/chats/${a}`))}var h=c(59144);let i=new Set(["anthropic","openai","google","opencode","moonshot","any","xai"]),j={anthropic:"claude",openai:"codex",google:"gemini",opencode:"opencode",moonshot:"kimi",any:"claude",xai:"opencode"};function k(a){return a?j[a]??"claude":"claude"}function l(a){let b=a.kind??"review";return{id:a.id??"phase",name:a.title??a.name??a.id??"Phase",description:a.description??"",kind:b,gate:"auto",doer:{lineage:k(a.doer?.lineage),models:a.doer?.models??[],...a.doer?.persona?{persona:a.doer.persona}:{}},reviewer:{require:a.reviewer?.require??1,crossLineage:a.reviewer?.crossLineage??!0,candidates:(a.reviewer?.candidates??[]).map(a=>k(a.lineage)).filter(a=>i.has(Object.keys(j).find(b=>j[b]===a)??"")),candidatesWithModels:(a.reviewer?.candidates??[]).map(a=>({lineage:k(a.lineage),models:a.models??[],...a.persona?{persona:a.persona}:{}}))},inputs:{include:a.inputs?.include??[],exclude:a.inputs?.exclude??[]},iterate:{max:a.iterate?.maxRounds??2,onMax:"ask-user"},blindSpots:[],execution:"parallel",builtin:!0,..."review_only"===b?{artifact:{label:a.artifact?.label??"Artifact to review",hint:a.artifact?.hint??"Paste a unified diff, a markdown draft, code, or any text blob.",maxBytes:a.artifact?.maxBytes??1048576}}:{}}}function m(a){let b={};try{b=h.parse(a.yaml)??{}}catch{}let c="majority";"number"==typeof b.agreementThreshold?c=b.agreementThreshold>=.99?"unanimous":b.agreementThreshold>=.5?"majority":"any":"string"==typeof b.agreementThreshold&&(c=b.agreementThreshold);let d=(b.phases??[]).map(l);return{id:a.id,name:b.name??a.id,description:b.description??"",category:function(a,b){if(a.category)return a.category;let c=b.toLowerCase();return c.includes("bug")||c.includes("debug")||c.includes("diagnose")?"debug":c.includes("plan")||c.includes("architect")?"plan":c.includes("decide")||c.includes("decision")?"decide":"review"}(b,a.id),phases:d,agreementThreshold:c,onThresholdMet:"auto-finalize"===b.onThresholdMet?"auto-finalize":"ask-user",maxRounds:b.maxRounds??3,driver:"external",driverHandoff:!1,verificationGate:"auto",costCapUsd:0,yoloDefault:b.yoloDefault??!1,estimatedBaselineTokens:b.estimatedBaselineTokens,onError:"ask-user",notify:"dashboard-only",yaml:a.yaml,authorHandle:b.author??"chorus",forks:0,popularity:0,source:a.source,isComplete:"number"==typeof a.is_complete?0!==a.is_complete:!1!==a.is_complete}}async function n(){return(await (0,d.T)("/templates")).items.map(m)}async function o(a){return m(await (0,d.T)(`/templates/${a}`))}var p=c(81147);async function q(){return(await (0,d.T)("/orchestrators")).items}async function r(){return(0,d.T)("/stats")}},80434:(a,b,c)=>{c.d(b,{AppSidebar:()=>e});var d=c(96600);(0,d.registerClientReference)(function(){throw Error("Attempted to call SidebarBody() from the server but SidebarBody is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/ubuntu/dev/chorus/src/components/app-sidebar.tsx","SidebarBody");let e=(0,d.registerClientReference)(function(){throw Error("Attempted to call AppSidebar() from the server but AppSidebar is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/ubuntu/dev/chorus/src/components/app-sidebar.tsx","AppSidebar")},81147:(a,b,c)=>{c.d(b,{AQ:()=>g,VE:()=>e,mt:()=>f});var d=c(54197);async function e(){return(0,d.T)("/settings/permissions")}async function f(){return(0,d.T)("/settings")}async function g(){return(await (0,d.T)("/secrets")).items}}};