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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/connect/page.js +1 -1
- package/.next/server/app/connect/page.js.nft.json +1 -1
- package/.next/server/app/connect/page_client-reference-manifest.js +1 -1
- package/.next/server/app/demo/[scenario]/page.js +1 -1
- package/.next/server/app/demo/[scenario]/page.js.nft.json +1 -1
- package/.next/server/app/demo/[scenario]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/new/page.js +1 -1
- package/.next/server/app/new/page.js.nft.json +1 -1
- package/.next/server/app/new/page_client-reference-manifest.js +1 -1
- package/.next/server/app/new.html +1 -1
- package/.next/server/app/new.rsc +3 -3
- package/.next/server/app/new.segments/_full.segment.rsc +3 -3
- package/.next/server/app/new.segments/_head.segment.rsc +1 -1
- package/.next/server/app/new.segments/_index.segment.rsc +2 -2
- package/.next/server/app/new.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/new.segments/new/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/new.segments/new.segment.rsc +1 -1
- package/.next/server/app/onboarding/page.js +1 -1
- package/.next/server/app/onboarding/page.js.nft.json +1 -1
- package/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
- package/.next/server/app/onboarding.html +1 -1
- package/.next/server/app/onboarding.rsc +3 -3
- package/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
- package/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
- package/.next/server/app/onboarding.segments/_index.segment.rsc +2 -2
- package/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/personas/page.js +1 -1
- package/.next/server/app/personas/page.js.nft.json +1 -1
- package/.next/server/app/personas/page_client-reference-manifest.js +1 -1
- package/.next/server/app/personas.html +1 -1
- package/.next/server/app/personas.rsc +3 -3
- package/.next/server/app/personas.segments/_full.segment.rsc +3 -3
- package/.next/server/app/personas.segments/_head.segment.rsc +1 -1
- package/.next/server/app/personas.segments/_index.segment.rsc +2 -2
- package/.next/server/app/personas.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/personas.segments/personas/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/personas.segments/personas.segment.rsc +1 -1
- package/.next/server/app/runs/[runId]/page.js +2 -2
- package/.next/server/app/runs/[runId]/page.js.nft.json +1 -1
- package/.next/server/app/runs/[runId]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/runs/page.js +2 -2
- package/.next/server/app/runs/page.js.nft.json +1 -1
- package/.next/server/app/runs/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/page.js +3 -3
- package/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/permissions/page.js +1 -1
- package/.next/server/app/settings/permissions/page.js.nft.json +1 -1
- package/.next/server/app/settings/permissions/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings.html +1 -1
- package/.next/server/app/settings.rsc +3 -3
- package/.next/server/app/settings.segments/_full.segment.rsc +3 -3
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/templates/page.js +2 -2
- package/.next/server/app/templates/page.js.nft.json +1 -1
- package/.next/server/app/templates/page_client-reference-manifest.js +1 -1
- package/.next/server/app/templates.html +1 -1
- package/.next/server/app/templates.rsc +3 -3
- package/.next/server/app/templates.segments/_full.segment.rsc +3 -3
- package/.next/server/app/templates.segments/_head.segment.rsc +1 -1
- package/.next/server/app/templates.segments/_index.segment.rsc +2 -2
- package/.next/server/app/templates.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/templates.segments/templates/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/templates.segments/templates.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +1 -1
- package/.next/server/chunks/189.js +1 -0
- package/.next/server/chunks/{144.js → 21.js} +1 -1
- package/.next/server/chunks/{668.js → 313.js} +1 -1
- package/.next/server/chunks/681.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/249-2e840495c38ee022.js +25 -0
- package/.next/static/chunks/641-2908cb9553b8753a.js +1 -0
- package/.next/static/chunks/690-092c26db4082d49a.js +1 -0
- package/.next/static/chunks/app/connect/{page-a3a0af374f90ad4c.js → page-ad4409761e870bd0.js} +1 -1
- package/.next/static/chunks/app/demo/[scenario]/{page-6a0e4aec4bb96fee.js → page-39673968f543c473.js} +1 -1
- package/.next/static/chunks/app/new/{page-b96d75506030acf8.js → page-b5f609ab9413ac00.js} +1 -1
- package/.next/static/chunks/app/onboarding/{page-4be5a1d944e32672.js → page-8b0850fef487abdc.js} +1 -1
- package/.next/static/chunks/app/{page-35375a7c8b3d117a.js → page-9bdbaad592d0ce56.js} +1 -1
- package/.next/static/chunks/app/personas/{page-3884f8907107a4e6.js → page-440c6033a773100c.js} +1 -1
- package/.next/static/chunks/app/runs/[runId]/{page-b5bcf0c093389207.js → page-ffc36f12f1b63ebe.js} +1 -1
- package/.next/static/chunks/app/runs/{page-376175c1ac803558.js → page-6962ea572c9e4b74.js} +1 -1
- package/.next/static/chunks/app/settings/page-ad7180ee0d142704.js +25 -0
- package/.next/static/chunks/app/settings/permissions/{page-c90795aa9299bbe8.js → page-cd767401ac71a29c.js} +1 -1
- package/.next/static/chunks/app/templates/page-0112ab3c7ab5185d.js +1 -0
- package/.next/static/css/df4972a256406ec7.css +3 -0
- package/.next/trace +20 -20
- package/.next/trace-build +1 -1
- package/dist/daemon/cli-semaphore.js +266 -0
- package/dist/daemon/cli-semaphore.js.map +1 -0
- package/dist/daemon/routes/settings.js +39 -0
- package/dist/daemon/routes/settings.js.map +1 -1
- package/dist/daemon/runner/doer-driver.js +243 -214
- package/dist/daemon/runner/doer-driver.js.map +1 -1
- package/dist/daemon/runner/doer.js +24 -1
- package/dist/daemon/runner/doer.js.map +1 -1
- package/dist/daemon/runner/reviewer-driver.js +299 -255
- package/dist/daemon/runner/reviewer-driver.js.map +1 -1
- package/dist/daemon/runner/reviewer.js +27 -1
- package/dist/daemon/runner/reviewer.js.map +1 -1
- package/dist/daemon/runner.js +19 -0
- package/dist/daemon/runner.js.map +1 -1
- package/dist/lib/db/chats.js +28 -0
- package/dist/lib/db/chats.js.map +1 -1
- package/dist/lib/db/connection.js +6 -0
- package/dist/lib/db/connection.js.map +1 -1
- package/dist/lib/db/schema.sql +6 -0
- package/dist/lib/model-pricing.js +306 -0
- package/dist/lib/model-pricing.js.map +1 -0
- package/dist/lib/settings/concurrency.js +101 -0
- package/dist/lib/settings/concurrency.js.map +1 -0
- package/package.json +1 -1
- package/.next/server/chunks/946.js +0 -1
- package/.next/static/chunks/116-8bf7e014066cedde.js +0 -25
- package/.next/static/chunks/15-d438a2b057302bed.js +0 -1
- package/.next/static/chunks/641-60721f44faf711b9.js +0 -1
- package/.next/static/chunks/app/settings/page-1792a3e289409b2d.js +0 -25
- package/.next/static/chunks/app/templates/page-1449b0aea2e7cb68.js +0 -1
- package/.next/static/css/d2bb161eb5bee944.css +0 -3
- /package/.next/static/{jdUcCHB2b5lVrf4v8iQjl → eOeXty5cBGWg7xnmtF6ST}/_buildManifest.js +0 -0
- /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.
|
|
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}}};
|