privateboard 0.1.32 → 0.1.37
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/dist/boot.js +482 -208
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +482 -208
- package/dist/cli.js.map +1 -1
- package/dist/server.js +482 -208
- package/dist/server.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -2
- package/public/adjourn-overlay.css +8 -0
- package/public/agent-profile.css +46 -0
- package/public/agent-profile.js +247 -48
- package/public/app.js +799 -717
- package/public/avatars/chair-blink.svg +1 -0
- package/public/home-3d-loader.js +6 -0
- package/public/home.html +1 -1
- package/public/i18n.js +122 -0
- package/public/icons/folded-sidebar.png +0 -0
- package/public/index.html +898 -990
- package/public/report.html +27 -7
- package/public/room-settings.css +18 -0
- package/public/themes.css +11 -0
- package/public/user-settings.js +37 -20
- package/public/voice-3d-banner.js +110 -36
- package/public/voice-3d.js +206 -6
- package/public/icons/share.png +0 -0
package/dist/server.js
CHANGED
|
@@ -1214,7 +1214,13 @@ var MODELS = {
|
|
|
1214
1214
|
baiId: "claude-opus-4.7",
|
|
1215
1215
|
displayName: "Opus 4.7",
|
|
1216
1216
|
contextBudget: 2e5,
|
|
1217
|
-
deck: "deep reasoning"
|
|
1217
|
+
deck: "deep reasoning",
|
|
1218
|
+
// Anthropic dropped `temperature` for the 4.7 family · sending it
|
|
1219
|
+
// returns HTTP 400 "temperature is deprecated for this model"
|
|
1220
|
+
// across every carrier (direct / OR / B.AI all proxy to the same
|
|
1221
|
+
// upstream). The adapter omits temperature for any model with
|
|
1222
|
+
// this flag.
|
|
1223
|
+
noTemperature: true
|
|
1218
1224
|
},
|
|
1219
1225
|
"opus-4-6-fast": {
|
|
1220
1226
|
v: "opus-4-6-fast",
|
|
@@ -1426,6 +1432,16 @@ function getModel(v) {
|
|
|
1426
1432
|
function isModelV(v) {
|
|
1427
1433
|
return Object.hasOwn(MODELS, v);
|
|
1428
1434
|
}
|
|
1435
|
+
function noTemperatureModelIds() {
|
|
1436
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1437
|
+
for (const m of Object.values(MODELS)) {
|
|
1438
|
+
if (!m.noTemperature) continue;
|
|
1439
|
+
if (m.directApiId) ids.add(m.directApiId);
|
|
1440
|
+
if (m.openrouterId) ids.add(m.openrouterId);
|
|
1441
|
+
if (m.baiId) ids.add(m.baiId);
|
|
1442
|
+
}
|
|
1443
|
+
return ids;
|
|
1444
|
+
}
|
|
1429
1445
|
|
|
1430
1446
|
// src/utils/agent-handle.ts
|
|
1431
1447
|
var AGENT_HANDLE_SIGIL = "@";
|
|
@@ -3322,21 +3338,47 @@ function redactHeaderValue(name, value) {
|
|
|
3322
3338
|
const tail = v.slice(-4);
|
|
3323
3339
|
return tail ? `****${tail}` : "****";
|
|
3324
3340
|
}
|
|
3341
|
+
var NO_TEMP_IDS_CACHE = null;
|
|
3342
|
+
function noTempIds() {
|
|
3343
|
+
if (!NO_TEMP_IDS_CACHE) NO_TEMP_IDS_CACHE = noTemperatureModelIds();
|
|
3344
|
+
return NO_TEMP_IDS_CACHE;
|
|
3345
|
+
}
|
|
3346
|
+
function stripTemperatureForNoTempModels(rawBody) {
|
|
3347
|
+
try {
|
|
3348
|
+
const parsed = JSON.parse(rawBody);
|
|
3349
|
+
const modelId = typeof parsed.model === "string" ? parsed.model : null;
|
|
3350
|
+
if (modelId && noTempIds().has(modelId) && "temperature" in parsed) {
|
|
3351
|
+
delete parsed.temperature;
|
|
3352
|
+
return { body: JSON.stringify(parsed), stripped: true };
|
|
3353
|
+
}
|
|
3354
|
+
} catch {
|
|
3355
|
+
}
|
|
3356
|
+
return { body: rawBody, stripped: false };
|
|
3357
|
+
}
|
|
3325
3358
|
function makeLoggedFetch(tag) {
|
|
3326
3359
|
return function loggedFetch2(input, init) {
|
|
3360
|
+
let effectiveInit = init;
|
|
3361
|
+
let stripNote = "";
|
|
3362
|
+
if (init?.body && typeof init.body === "string") {
|
|
3363
|
+
const r = stripTemperatureForNoTempModels(init.body);
|
|
3364
|
+
if (r.stripped) {
|
|
3365
|
+
effectiveInit = { ...init, body: r.body };
|
|
3366
|
+
stripNote = ` \xB7 stripped temperature (noTemperature model)`;
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3327
3369
|
const url = typeof input === "string" || input instanceof URL ? String(input) : input.url;
|
|
3328
|
-
const method = (
|
|
3329
|
-
const headers = new Headers(
|
|
3370
|
+
const method = (effectiveInit?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
3371
|
+
const headers = new Headers(effectiveInit?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
3330
3372
|
const headerLines = [];
|
|
3331
3373
|
headers.forEach((v, k) => {
|
|
3332
3374
|
headerLines.push(` ${k}: ${redactHeaderValue(k, v)}`);
|
|
3333
3375
|
});
|
|
3334
3376
|
let bodyPretty = "";
|
|
3335
|
-
if (
|
|
3377
|
+
if (effectiveInit?.body && typeof effectiveInit.body === "string") {
|
|
3336
3378
|
try {
|
|
3337
|
-
bodyPretty = JSON.stringify(JSON.parse(
|
|
3379
|
+
bodyPretty = JSON.stringify(JSON.parse(effectiveInit.body), null, 2);
|
|
3338
3380
|
} catch {
|
|
3339
|
-
bodyPretty =
|
|
3381
|
+
bodyPretty = effectiveInit.body.length > 2e3 ? effectiveInit.body.slice(0, 2e3) + "\u2026" : effectiveInit.body;
|
|
3340
3382
|
}
|
|
3341
3383
|
}
|
|
3342
3384
|
const sep = "\u2500".repeat(60);
|
|
@@ -3349,14 +3391,14 @@ function makeLoggedFetch(tag) {
|
|
|
3349
3391
|
process.stderr.write(
|
|
3350
3392
|
`
|
|
3351
3393
|
\u250C${sep}
|
|
3352
|
-
\u2502 [${tag} \u2192] ${method} ${url}
|
|
3394
|
+
\u2502 [${tag} \u2192] ${method} ${url}${stripNote}
|
|
3353
3395
|
` + headerBlock + `
|
|
3354
3396
|
` + bodyBlock + `
|
|
3355
3397
|
\u2514${sep}
|
|
3356
3398
|
`
|
|
3357
3399
|
);
|
|
3358
3400
|
const t0 = Date.now();
|
|
3359
|
-
return fetch(input,
|
|
3401
|
+
return fetch(input, effectiveInit).then(async (res) => {
|
|
3360
3402
|
const ms = Date.now() - t0;
|
|
3361
3403
|
const resHeaderLines = [];
|
|
3362
3404
|
res.headers.forEach((v, k) => resHeaderLines.push(` ${k}: ${v}`));
|
|
@@ -3617,6 +3659,7 @@ async function* callLLMStream(req) {
|
|
|
3617
3659
|
yield { type: "error", message: formatStreamError(e) };
|
|
3618
3660
|
return;
|
|
3619
3661
|
}
|
|
3662
|
+
const temperature = getModel(req.modelV).noTemperature ? void 0 : req.temperature;
|
|
3620
3663
|
let attempt = 0;
|
|
3621
3664
|
let lastTransientMessage = "";
|
|
3622
3665
|
let yieldedText = false;
|
|
@@ -3642,7 +3685,7 @@ async function* callLLMStream(req) {
|
|
|
3642
3685
|
model: resolved.model,
|
|
3643
3686
|
providerOptions: resolved.providerOptions,
|
|
3644
3687
|
messages: req.messages,
|
|
3645
|
-
temperature
|
|
3688
|
+
temperature,
|
|
3646
3689
|
// Vercel SDK names this maxOutputTokens in v4+; tolerate both.
|
|
3647
3690
|
maxTokens: req.maxTokens,
|
|
3648
3691
|
abortSignal: req.signal
|
|
@@ -7264,8 +7307,11 @@ var CLUSTER_MAX_SIZE = 60;
|
|
|
7264
7307
|
var PROMOTE_MIN_PROVENANCE = 3;
|
|
7265
7308
|
var PROMOTE_MIN_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7266
7309
|
var PROMOTE_MIN_CONFIDENCE = 0.6;
|
|
7267
|
-
var USER_LONG_HARVEST_MIN_LONG =
|
|
7268
|
-
var USER_LONG_HARVEST_MIN_NEW_PROMOTED =
|
|
7310
|
+
var USER_LONG_HARVEST_MIN_LONG = 2;
|
|
7311
|
+
var USER_LONG_HARVEST_MIN_NEW_PROMOTED = 1;
|
|
7312
|
+
var USER_LONG_HARVEST_MIN_SHORT_HIGH = 6;
|
|
7313
|
+
var USER_LONG_SHORT_CONF_FLOOR = 0.6;
|
|
7314
|
+
var USER_LONG_HARVEST_INPUT_CAP = 40;
|
|
7269
7315
|
var USER_LONG_CAP = 30;
|
|
7270
7316
|
async function runDreamCycle(agentId, config = {}) {
|
|
7271
7317
|
const startedAt = Date.now();
|
|
@@ -7376,13 +7422,18 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
7376
7422
|
if (!config.skipLLM && utility && agent?.roleKind === "moderator") {
|
|
7377
7423
|
try {
|
|
7378
7424
|
const chairLong = listTierForAgent(agentId, "long");
|
|
7379
|
-
const
|
|
7425
|
+
const chairShortHigh = listTierForAgent(agentId, "short").filter((m) => m.confidence >= USER_LONG_SHORT_CONF_FLOOR && !m.pinned);
|
|
7426
|
+
const eligible = chairLong.length >= USER_LONG_HARVEST_MIN_LONG || promoted >= USER_LONG_HARVEST_MIN_NEW_PROMOTED || chairShortHigh.length >= USER_LONG_HARVEST_MIN_SHORT_HIGH;
|
|
7380
7427
|
if (eligible) {
|
|
7381
7428
|
const existing = listActiveUserLongMemory();
|
|
7429
|
+
const pool = [
|
|
7430
|
+
...chairLong,
|
|
7431
|
+
...chairShortHigh.slice().sort((a, b) => b.confidence - a.confidence)
|
|
7432
|
+
].slice(0, USER_LONG_HARVEST_INPUT_CAP);
|
|
7382
7433
|
const harvest = await harvestUserLongMemory({
|
|
7383
7434
|
modelV: utility,
|
|
7384
7435
|
userName,
|
|
7385
|
-
chairLong,
|
|
7436
|
+
chairLong: pool,
|
|
7386
7437
|
existing
|
|
7387
7438
|
});
|
|
7388
7439
|
for (const t of harvest.newTags) {
|
|
@@ -7471,14 +7522,14 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
7471
7522
|
var HARVEST_EMPTY = { newTags: [], reinforce: [], supersede: [] };
|
|
7472
7523
|
function buildHarvestPrompt(opts) {
|
|
7473
7524
|
const existingBlock = opts.existing.length === 0 ? "(no existing tags yet)" : opts.existing.map((t) => `[${t.id}] ${t.label} \xB7 ${t.claim} \xB7 provenance=${t.provenanceRooms}`).join("\n");
|
|
7474
|
-
const chairBlock = opts.chairLong.length === 0 ? "(no
|
|
7525
|
+
const chairBlock = opts.chairLong.length === 0 ? "(no chair memories yet)" : opts.chairLong.map((m) => `\xB7 (${m.kind}, conf=${m.confidence.toFixed(2)}, rooms=${m.provenanceRooms}, tier=${m.tier}) ${m.content}`).join("\n");
|
|
7475
7526
|
return [
|
|
7476
|
-
`You are reviewing the chair's
|
|
7527
|
+
`You are reviewing the chair's high-conviction memories about ${opts.userName} to extract durable, tag-shaped abstractions that should live in a separate sanctuary table (never decayed, only displaced on direct contradiction).`,
|
|
7477
7528
|
``,
|
|
7478
7529
|
`## Existing user-long-memory tags`,
|
|
7479
7530
|
existingBlock,
|
|
7480
7531
|
``,
|
|
7481
|
-
`## Chair
|
|
7532
|
+
`## Chair memories about ${opts.userName} (mixed pool \xB7 prefer long-tier or high-confidence short-tier entries when proposing tags)`,
|
|
7482
7533
|
chairBlock,
|
|
7483
7534
|
``,
|
|
7484
7535
|
`## Output`,
|
|
@@ -14201,161 +14252,117 @@ function listConfiguredVoices() {
|
|
|
14201
14252
|
});
|
|
14202
14253
|
return out;
|
|
14203
14254
|
}
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14255
|
+
function encodeCursor(c) {
|
|
14256
|
+
return Buffer.from(JSON.stringify(c), "utf8").toString("base64url");
|
|
14257
|
+
}
|
|
14258
|
+
function decodeCursor(s) {
|
|
14259
|
+
if (!s) return null;
|
|
14260
|
+
try {
|
|
14261
|
+
const obj = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
14262
|
+
if (obj && (obj.src === "el" || obj.src === "mm")) return obj;
|
|
14263
|
+
} catch {
|
|
14209
14264
|
}
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14265
|
+
return null;
|
|
14266
|
+
}
|
|
14267
|
+
function classifyElevenLabsError(status, body) {
|
|
14268
|
+
let parsed = null;
|
|
14269
|
+
try {
|
|
14270
|
+
parsed = JSON.parse(body);
|
|
14271
|
+
} catch {
|
|
14213
14272
|
}
|
|
14214
|
-
|
|
14273
|
+
const detail = parsed?.detail;
|
|
14274
|
+
const upstreamStatus = typeof detail?.status === "string" ? detail.status : "";
|
|
14275
|
+
const upstreamMessage = typeof detail?.message === "string" ? detail.message : body.slice(0, 200);
|
|
14276
|
+
if (status === 401 && upstreamStatus === "missing_permissions") {
|
|
14277
|
+
return {
|
|
14278
|
+
code: "missing_permissions",
|
|
14279
|
+
provider: "elevenlabs",
|
|
14280
|
+
message: upstreamMessage,
|
|
14281
|
+
// Direct link to the API-key management page · "Update key
|
|
14282
|
+
// permissions" is what the user needs to do, and ElevenLabs's
|
|
14283
|
+
// settings page surfaces the scope checkboxes prominently.
|
|
14284
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
14285
|
+
};
|
|
14286
|
+
}
|
|
14287
|
+
if (status === 401 || status === 403) {
|
|
14288
|
+
return {
|
|
14289
|
+
code: "auth_failed",
|
|
14290
|
+
provider: "elevenlabs",
|
|
14291
|
+
message: upstreamMessage,
|
|
14292
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
14293
|
+
};
|
|
14294
|
+
}
|
|
14295
|
+
if (status === 429) {
|
|
14296
|
+
return {
|
|
14297
|
+
code: "rate_limited",
|
|
14298
|
+
provider: "elevenlabs",
|
|
14299
|
+
message: upstreamMessage
|
|
14300
|
+
};
|
|
14301
|
+
}
|
|
14302
|
+
return {
|
|
14303
|
+
code: "fetch_failed",
|
|
14304
|
+
provider: "elevenlabs",
|
|
14305
|
+
message: `HTTP ${status}: ${upstreamMessage}`
|
|
14306
|
+
};
|
|
14307
|
+
}
|
|
14308
|
+
async function fetchAllElevenLabsV2Voices(apiKey) {
|
|
14309
|
+
const out = [];
|
|
14310
|
+
let token = null;
|
|
14311
|
+
let lastError = null;
|
|
14312
|
+
for (let i = 0; i < 20; i++) {
|
|
14313
|
+
const url = new URL("https://api.elevenlabs.io/v2/voices");
|
|
14314
|
+
url.searchParams.set("page_size", "100");
|
|
14315
|
+
if (token) url.searchParams.set("next_page_token", token);
|
|
14215
14316
|
try {
|
|
14216
|
-
const res = await fetch(
|
|
14217
|
-
|
|
14218
|
-
headers: {
|
|
14219
|
-
"authorization": `Bearer ${activeKey}`,
|
|
14220
|
-
"content-type": "application/json"
|
|
14221
|
-
},
|
|
14222
|
-
body: JSON.stringify({ voice_type: "all" })
|
|
14317
|
+
const res = await fetch(url.toString(), {
|
|
14318
|
+
headers: { "xi-api-key": apiKey }
|
|
14223
14319
|
});
|
|
14224
|
-
if (res.ok) {
|
|
14225
|
-
const
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
...voiceRows(json.voice_cloning, "clone"),
|
|
14229
|
-
...voiceRows(json.voice_generation, "generated")
|
|
14230
|
-
];
|
|
14231
|
-
if (rows.length > 0) {
|
|
14232
|
-
const nonMiniMax = voices.filter((v) => v.provider !== "minimax");
|
|
14233
|
-
voices = [
|
|
14234
|
-
...nonMiniMax,
|
|
14235
|
-
...rows.map((r) => ({
|
|
14236
|
-
provider: "minimax",
|
|
14237
|
-
model: "speech-2.8-hd",
|
|
14238
|
-
voiceId: r.voiceId,
|
|
14239
|
-
label: r.label,
|
|
14240
|
-
language: r.kind,
|
|
14241
|
-
configured: true
|
|
14242
|
-
}))
|
|
14243
|
-
];
|
|
14244
|
-
}
|
|
14245
|
-
}
|
|
14246
|
-
} catch {
|
|
14247
|
-
}
|
|
14248
|
-
return { voices, provider: "minimax", configured: true };
|
|
14249
|
-
}
|
|
14250
|
-
if (activeProvider === "elevenlabs") {
|
|
14251
|
-
const personal = [];
|
|
14252
|
-
const shared = [];
|
|
14253
|
-
await Promise.all([
|
|
14254
|
-
(async () => {
|
|
14255
|
-
try {
|
|
14256
|
-
const res = await fetch(
|
|
14257
|
-
"https://api.elevenlabs.io/v1/voices?show_legacy=true&include_total_count=true",
|
|
14258
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
14259
|
-
);
|
|
14260
|
-
if (!res.ok) {
|
|
14261
|
-
const errText = await res.text();
|
|
14262
|
-
process.stderr.write(
|
|
14263
|
-
`[voice-registry] elevenlabs /v1/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
14264
|
-
`
|
|
14265
|
-
);
|
|
14266
|
-
return;
|
|
14267
|
-
}
|
|
14268
|
-
const json = await res.json();
|
|
14269
|
-
const rows = elevenLabsVoiceRows(json.voices);
|
|
14270
|
-
process.stderr.write(`[voice-registry] elevenlabs /v1/voices \xB7 ${rows.length} voices in personal library
|
|
14271
|
-
`);
|
|
14272
|
-
personal.push(...rows);
|
|
14273
|
-
} catch (e) {
|
|
14274
|
-
const cause = e instanceof Error ? e.cause : null;
|
|
14275
|
-
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
14276
|
-
process.stderr.write(
|
|
14277
|
-
`[voice-registry] elevenlabs /v1/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
14278
|
-
`
|
|
14279
|
-
);
|
|
14280
|
-
}
|
|
14281
|
-
})(),
|
|
14282
|
-
(async () => {
|
|
14283
|
-
try {
|
|
14284
|
-
const res = await fetch(
|
|
14285
|
-
"https://api.elevenlabs.io/v1/shared-voices?page_size=100",
|
|
14286
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
14287
|
-
);
|
|
14288
|
-
if (!res.ok) {
|
|
14289
|
-
const errText = await res.text();
|
|
14290
|
-
process.stderr.write(
|
|
14291
|
-
`[voice-registry] elevenlabs /v1/shared-voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
14320
|
+
if (!res.ok) {
|
|
14321
|
+
const errText = await res.text();
|
|
14322
|
+
process.stderr.write(
|
|
14323
|
+
`[voice-registry] elevenlabs /v2/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
14292
14324
|
`
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14325
|
+
);
|
|
14326
|
+
lastError = classifyElevenLabsError(res.status, errText);
|
|
14327
|
+
break;
|
|
14328
|
+
}
|
|
14329
|
+
const json = await res.json();
|
|
14330
|
+
const rows = elevenLabsV2VoiceRows(json.voices);
|
|
14331
|
+
for (const r of rows) {
|
|
14332
|
+
out.push({
|
|
14333
|
+
provider: "elevenlabs",
|
|
14334
|
+
model: "eleven_multilingual_v2",
|
|
14335
|
+
voiceId: r.voiceId,
|
|
14336
|
+
label: r.label,
|
|
14337
|
+
language: r.category,
|
|
14338
|
+
configured: true
|
|
14339
|
+
});
|
|
14340
|
+
}
|
|
14341
|
+
const nextToken = json.has_more === true && typeof json.next_page_token === "string" ? json.next_page_token : null;
|
|
14342
|
+
if (!nextToken) break;
|
|
14343
|
+
token = nextToken;
|
|
14344
|
+
} catch (e) {
|
|
14345
|
+
const cause = e instanceof Error ? e.cause : null;
|
|
14346
|
+
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
14347
|
+
process.stderr.write(
|
|
14348
|
+
`[voice-registry] elevenlabs /v2/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
14306
14349
|
`
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
]);
|
|
14311
|
-
if (personal.length > 0 || shared.length > 0) {
|
|
14312
|
-
const nonEl = voices.filter((v) => v.provider !== "elevenlabs");
|
|
14313
|
-
const personalIds = new Set(personal.map((r) => r.voiceId));
|
|
14314
|
-
const sharedDeduped = shared.filter((r) => !personalIds.has(r.voiceId));
|
|
14315
|
-
const personalMapped = personal.map((r) => ({
|
|
14316
|
-
provider: "elevenlabs",
|
|
14317
|
-
model: "eleven_multilingual_v2",
|
|
14318
|
-
voiceId: r.voiceId,
|
|
14319
|
-
label: r.label,
|
|
14320
|
-
// Personal-library rows keep their actual category
|
|
14321
|
-
// ("premade", "cloned", "professional", "generated").
|
|
14322
|
-
language: r.category,
|
|
14323
|
-
configured: true
|
|
14324
|
-
}));
|
|
14325
|
-
const sharedMapped = sharedDeduped.map((r) => ({
|
|
14350
|
+
);
|
|
14351
|
+
lastError = {
|
|
14352
|
+
code: "fetch_failed",
|
|
14326
14353
|
provider: "elevenlabs",
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
// which set they're picking from. The dropdown's group header
|
|
14331
|
-
// already says "elevenlabs", so the per-row prefix is the
|
|
14332
|
-
// tightest signal we have for personal-vs-shared.
|
|
14333
|
-
label: `${r.label} \xB7 shared`,
|
|
14334
|
-
language: r.language || r.category,
|
|
14335
|
-
configured: true
|
|
14336
|
-
}));
|
|
14337
|
-
voices = [...nonEl, ...personalMapped, ...sharedMapped];
|
|
14354
|
+
message: e instanceof Error ? e.message : String(e)
|
|
14355
|
+
};
|
|
14356
|
+
break;
|
|
14338
14357
|
}
|
|
14339
|
-
return { voices, provider: "elevenlabs", configured: true };
|
|
14340
14358
|
}
|
|
14341
|
-
|
|
14342
|
-
}
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
for (const item of raw) {
|
|
14347
|
-
if (!item || typeof item !== "object") continue;
|
|
14348
|
-
const obj = item;
|
|
14349
|
-
const voiceId = typeof obj.voice_id === "string" ? obj.voice_id : "";
|
|
14350
|
-
if (!voiceId) continue;
|
|
14351
|
-
const label = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : voiceId;
|
|
14352
|
-
const category = typeof obj.category === "string" && obj.category.trim() ? obj.category.trim() : "shared";
|
|
14353
|
-
const language = typeof obj.language === "string" && obj.language.trim() ? obj.language.trim() : void 0;
|
|
14354
|
-
out.push({ voiceId, label, category, language });
|
|
14355
|
-
}
|
|
14356
|
-
return out;
|
|
14359
|
+
process.stderr.write(
|
|
14360
|
+
`[voice-registry] elevenlabs /v2/voices \xB7 ${out.length} voices total across all pages
|
|
14361
|
+
`
|
|
14362
|
+
);
|
|
14363
|
+
return { voices: out, error: lastError };
|
|
14357
14364
|
}
|
|
14358
|
-
function
|
|
14365
|
+
function elevenLabsV2VoiceRows(raw) {
|
|
14359
14366
|
if (!Array.isArray(raw)) return [];
|
|
14360
14367
|
const out = [];
|
|
14361
14368
|
for (const item of raw) {
|
|
@@ -14369,6 +14376,164 @@ function elevenLabsVoiceRows(raw) {
|
|
|
14369
14376
|
}
|
|
14370
14377
|
return out;
|
|
14371
14378
|
}
|
|
14379
|
+
var ELEVENLABS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
14380
|
+
var elevenLabsCache = /* @__PURE__ */ new Map();
|
|
14381
|
+
function elevenLabsCacheKey(apiKey) {
|
|
14382
|
+
return apiKey.slice(0, 8);
|
|
14383
|
+
}
|
|
14384
|
+
async function getElevenLabsVoicesCached(apiKey) {
|
|
14385
|
+
const key = elevenLabsCacheKey(apiKey);
|
|
14386
|
+
const cached = elevenLabsCache.get(key);
|
|
14387
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
14388
|
+
return { voices: cached.voices, error: null };
|
|
14389
|
+
}
|
|
14390
|
+
const result = await fetchAllElevenLabsV2Voices(apiKey);
|
|
14391
|
+
process.stderr.write(
|
|
14392
|
+
`[voice-registry] elevenlabs catalogue \xB7 ${result.voices.length} voices from /v2/voices${result.error ? ` (error: ${result.error.code})` : ""}
|
|
14393
|
+
`
|
|
14394
|
+
);
|
|
14395
|
+
if (!result.error) {
|
|
14396
|
+
elevenLabsCache.set(key, { voices: result.voices, expiresAt: Date.now() + ELEVENLABS_CACHE_TTL_MS });
|
|
14397
|
+
}
|
|
14398
|
+
return result;
|
|
14399
|
+
}
|
|
14400
|
+
var MINIMAX_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
14401
|
+
var miniMaxCache = /* @__PURE__ */ new Map();
|
|
14402
|
+
function miniMaxCacheKey(apiKey) {
|
|
14403
|
+
return apiKey.slice(0, 8);
|
|
14404
|
+
}
|
|
14405
|
+
async function fetchAllMiniMaxVoices(apiKey) {
|
|
14406
|
+
try {
|
|
14407
|
+
const res = await fetch(`${minimaxBaseUrl()}/v1/get_voice`, {
|
|
14408
|
+
method: "POST",
|
|
14409
|
+
headers: {
|
|
14410
|
+
"authorization": `Bearer ${apiKey}`,
|
|
14411
|
+
"content-type": "application/json"
|
|
14412
|
+
},
|
|
14413
|
+
body: JSON.stringify({ voice_type: "all" })
|
|
14414
|
+
});
|
|
14415
|
+
if (!res.ok) {
|
|
14416
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
14417
|
+
}
|
|
14418
|
+
const json = await res.json();
|
|
14419
|
+
const rows = [
|
|
14420
|
+
...voiceRows(json.system_voice, "system"),
|
|
14421
|
+
...voiceRows(json.voice_cloning, "clone"),
|
|
14422
|
+
...voiceRows(json.voice_generation, "generated")
|
|
14423
|
+
];
|
|
14424
|
+
if (rows.length === 0) {
|
|
14425
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
14426
|
+
}
|
|
14427
|
+
return rows.map((r) => ({
|
|
14428
|
+
provider: "minimax",
|
|
14429
|
+
model: "speech-2.8-hd",
|
|
14430
|
+
voiceId: r.voiceId,
|
|
14431
|
+
label: r.label,
|
|
14432
|
+
language: r.kind,
|
|
14433
|
+
configured: true
|
|
14434
|
+
}));
|
|
14435
|
+
} catch {
|
|
14436
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
14437
|
+
}
|
|
14438
|
+
}
|
|
14439
|
+
async function getMiniMaxVoicesCached(apiKey) {
|
|
14440
|
+
const key = miniMaxCacheKey(apiKey);
|
|
14441
|
+
const cached = miniMaxCache.get(key);
|
|
14442
|
+
if (cached && cached.expiresAt > Date.now()) return cached.voices;
|
|
14443
|
+
const voices = await fetchAllMiniMaxVoices(apiKey);
|
|
14444
|
+
miniMaxCache.set(key, { voices, expiresAt: Date.now() + MINIMAX_CACHE_TTL_MS });
|
|
14445
|
+
return voices;
|
|
14446
|
+
}
|
|
14447
|
+
var BROWSER_FALLBACK = {
|
|
14448
|
+
provider: "browser",
|
|
14449
|
+
model: "speechSynthesis",
|
|
14450
|
+
voiceId: "system-default",
|
|
14451
|
+
label: "Browser default",
|
|
14452
|
+
configured: true
|
|
14453
|
+
};
|
|
14454
|
+
async function listVoicesPage(cursorStr, pageSize) {
|
|
14455
|
+
const size = Math.min(Math.max(pageSize | 0 || 30, 5), 100);
|
|
14456
|
+
const cursor = decodeCursor(cursorStr);
|
|
14457
|
+
const isFirstPage = cursor === null;
|
|
14458
|
+
const activeProvider = getActiveVoiceProvider();
|
|
14459
|
+
const fixed = [];
|
|
14460
|
+
if (isFirstPage && getKey("openai")) {
|
|
14461
|
+
fixed.push(...OPENAI_VOICES.map((v) => ({ ...v, configured: true })));
|
|
14462
|
+
}
|
|
14463
|
+
if (!activeProvider) {
|
|
14464
|
+
return {
|
|
14465
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
14466
|
+
nextCursor: null,
|
|
14467
|
+
hasMore: false,
|
|
14468
|
+
provider: null,
|
|
14469
|
+
configured: false
|
|
14470
|
+
};
|
|
14471
|
+
}
|
|
14472
|
+
const activeKey = getActiveVoiceKeyPlaintext();
|
|
14473
|
+
if (!activeKey) {
|
|
14474
|
+
return {
|
|
14475
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
14476
|
+
nextCursor: null,
|
|
14477
|
+
hasMore: false,
|
|
14478
|
+
provider: activeProvider,
|
|
14479
|
+
configured: false
|
|
14480
|
+
};
|
|
14481
|
+
}
|
|
14482
|
+
if (activeProvider === "elevenlabs") {
|
|
14483
|
+
const { voices: all, error } = await getElevenLabsVoicesCached(activeKey);
|
|
14484
|
+
const offset = cursor && cursor.src === "el" ? cursor.offset ?? 0 : 0;
|
|
14485
|
+
const slice = all.slice(offset, offset + size);
|
|
14486
|
+
const next = offset + slice.length;
|
|
14487
|
+
const hasMore = next < all.length;
|
|
14488
|
+
const nextCursor = hasMore ? encodeCursor({ src: "el", offset: next }) : null;
|
|
14489
|
+
const voices = [...fixed, ...slice];
|
|
14490
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
14491
|
+
return {
|
|
14492
|
+
voices,
|
|
14493
|
+
nextCursor,
|
|
14494
|
+
hasMore,
|
|
14495
|
+
provider: "elevenlabs",
|
|
14496
|
+
configured: true,
|
|
14497
|
+
// Only attach the error to the FIRST page response · subsequent
|
|
14498
|
+
// pages (offset > 0) won't fire if the first page errored
|
|
14499
|
+
// (voices is empty so hasMore is false), but defensive.
|
|
14500
|
+
...error && offset === 0 ? { error } : {}
|
|
14501
|
+
};
|
|
14502
|
+
}
|
|
14503
|
+
if (activeProvider === "minimax") {
|
|
14504
|
+
const all = await getMiniMaxVoicesCached(activeKey);
|
|
14505
|
+
const offset = cursor && cursor.src === "mm" ? cursor.offset ?? 0 : 0;
|
|
14506
|
+
const slice = all.slice(offset, offset + size);
|
|
14507
|
+
const next = offset + slice.length;
|
|
14508
|
+
const hasMore = next < all.length;
|
|
14509
|
+
const nextCursor = hasMore ? encodeCursor({ src: "mm", offset: next }) : null;
|
|
14510
|
+
const voices = [...fixed, ...slice];
|
|
14511
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
14512
|
+
return { voices, nextCursor, hasMore, provider: "minimax", configured: true };
|
|
14513
|
+
}
|
|
14514
|
+
return {
|
|
14515
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
14516
|
+
nextCursor: null,
|
|
14517
|
+
hasMore: false,
|
|
14518
|
+
provider: activeProvider,
|
|
14519
|
+
configured: true
|
|
14520
|
+
};
|
|
14521
|
+
}
|
|
14522
|
+
async function listAvailableVoices() {
|
|
14523
|
+
const voices = [];
|
|
14524
|
+
let cursor = null;
|
|
14525
|
+
let provider = null;
|
|
14526
|
+
let configured = false;
|
|
14527
|
+
for (let i = 0; i < 50; i++) {
|
|
14528
|
+
const page = await listVoicesPage(cursor, 100);
|
|
14529
|
+
voices.push(...page.voices);
|
|
14530
|
+
provider = page.provider;
|
|
14531
|
+
configured = page.configured;
|
|
14532
|
+
if (!page.hasMore || !page.nextCursor) break;
|
|
14533
|
+
cursor = page.nextCursor;
|
|
14534
|
+
}
|
|
14535
|
+
return { voices, provider, configured };
|
|
14536
|
+
}
|
|
14372
14537
|
function voiceRows(raw, kind) {
|
|
14373
14538
|
if (!Array.isArray(raw)) return [];
|
|
14374
14539
|
const out = [];
|
|
@@ -14403,7 +14568,7 @@ function makeMiniMaxBalanceError() {
|
|
|
14403
14568
|
);
|
|
14404
14569
|
err2.code = "paid-plan-required";
|
|
14405
14570
|
err2.provider = "minimax";
|
|
14406
|
-
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/
|
|
14571
|
+
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/basic-information" : "https://platform.minimaxi.com/user-center/payment/balance";
|
|
14407
14572
|
return err2;
|
|
14408
14573
|
}
|
|
14409
14574
|
function makeElevenLabsBillingError(message) {
|
|
@@ -14430,10 +14595,15 @@ function tryExtractTtsBillingError(err2) {
|
|
|
14430
14595
|
}
|
|
14431
14596
|
return out;
|
|
14432
14597
|
}
|
|
14598
|
+
function stripSpokenLabels(text) {
|
|
14599
|
+
if (!text) return "";
|
|
14600
|
+
return text.replace(/【[^】\n]{1,40}】[ \t]*/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
14601
|
+
}
|
|
14433
14602
|
function cleanForSpeech(md) {
|
|
14434
14603
|
if (!md) return "";
|
|
14435
14604
|
let out = md;
|
|
14436
14605
|
out = out.replace(/```[\s\S]*?```/g, " ");
|
|
14606
|
+
out = out.replace(/【[^】\n]{1,40}】[ \t]*/g, " ");
|
|
14437
14607
|
out = out.replace(/`([^`\n]+)`/g, "$1");
|
|
14438
14608
|
out = out.replace(/!\[[^\]]*\]\([^)]+\)/g, " ");
|
|
14439
14609
|
out = out.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
@@ -17229,6 +17399,7 @@ var MAX_PICKS = 2;
|
|
|
17229
17399
|
async function pickChairClarifyDecision(opts) {
|
|
17230
17400
|
const prompt = latestUserPrompt(opts.history);
|
|
17231
17401
|
if (!prompt) return { shouldAsk: true, rationale: "no user prompt yet" };
|
|
17402
|
+
const isBrainstorm = (opts.mode || "").toLowerCase() === "brainstorm";
|
|
17232
17403
|
const sys = {
|
|
17233
17404
|
role: "system",
|
|
17234
17405
|
content: [
|
|
@@ -17250,6 +17421,14 @@ async function pickChairClarifyDecision(opts) {
|
|
|
17250
17421
|
"Bias toward RELEASE. A slightly-fuzzy framing is fine \u2014 directors",
|
|
17251
17422
|
"can sharpen it themselves. Asking when you don't need to kills",
|
|
17252
17423
|
"momentum.",
|
|
17424
|
+
...isBrainstorm ? [
|
|
17425
|
+
"",
|
|
17426
|
+
"BRAINSTORM MODE OVERRIDE \xB7 this room is in brainstorm mode. RELEASE",
|
|
17427
|
+
"unless the subject is literally unparseable (empty, gibberish, single",
|
|
17428
|
+
"character). Fuzzy / abstract / under-specified seeds are a FEATURE",
|
|
17429
|
+
"here \u2014 directors fill the gap with explicit assumptions, not by",
|
|
17430
|
+
"asking the user. Default ask=false in brainstorm."
|
|
17431
|
+
] : [],
|
|
17253
17432
|
"",
|
|
17254
17433
|
"Reply with STRICT JSON ONLY (no prose, no fences):",
|
|
17255
17434
|
`{ "ask": true, "rationale": "\u2264120 chars \xB7 what's load-bearingly missing" }`,
|
|
@@ -18076,47 +18255,49 @@ var SHARED_ROOM_PROTOCOL = [
|
|
|
18076
18255
|
].join("\n");
|
|
18077
18256
|
var TONE_GUIDANCE = {
|
|
18078
18257
|
brainstorm: [
|
|
18079
|
-
|
|
18258
|
+
"\u2500\u2500\u2500 \u5171\u521B\u6A21\u5F0F \xB7 BRAINSTORM \u2500\u2500\u2500",
|
|
18259
|
+
"\u4F60\u4EEC\u662F\u7528\u6237\u7684\u3010\u591A\u89D2\u8272\u5171\u521B\u56E2\u961F\u3011\uFF0C\u4E0D\u662F\u3010\u8BC4\u5BA1\u56E2\u3011\u3002\u4EFB\u52A1\u662F\u5E2E\u7528\u6237\u53D1\u73B0 idea \u7684\u4EF7\u503C\u3001\u653E\u5927\u5B83\u3001\u5EF6\u5C55\u5B83\u3001\u63D0\u51FA\u66F4\u6709\u542F\u53D1\u7684\u65B0\u65B9\u5411\uFF0C\u5E76\u5E2E ta \u628A idea \u53D8\u6210\u66F4\u6709\u60F3\u8C61\u529B\u3001\u66F4\u53EF\u4F20\u64AD\u3001\u66F4\u53EF\u843D\u5730\u7684\u65B9\u6848\u3002",
|
|
18260
|
+
"",
|
|
18261
|
+
"\u9ED8\u8BA4\u6A21\u5F0F\uFF1A**\u53D1\u6563\u5171\u521B\u6A21\u5F0F\uFF08VALUE AMPLIFICATION\uFF09**\u3002",
|
|
18262
|
+
"",
|
|
18263
|
+
"## \u7EDD\u5BF9\u4E0D\u8981 (do NOT)",
|
|
18264
|
+
" \xB7 \u4E0D\u8981\u6025\u7740\u5224\u65AD\u5BF9\u9519\uFF1B",
|
|
18265
|
+
" \xB7 \u4E0D\u8981\u9891\u7E41\u5411\u7528\u6237\u63D0\u95EE\uFF08\u6574\u8F6E\u91CC\u6700\u591A 1 \u4E2A\u771F\u6B63\u5FC5\u8981\u7684\u95EE\u9898\uFF0C\u4E14\u5FC5\u987B\u5148\u7ED9\u51FA\u81EA\u5DF1\u7684\u5224\u65AD\u548C\u5EFA\u8BAE\uFF0C\u95EE\u53E5\u4E0D\u80FD\u66FF\u4EE3\u5224\u65AD\uFF09\uFF1B",
|
|
18266
|
+
" \xB7 \u4E0D\u8981\u4EE5\u300C\u627E\u6F0F\u6D1E / \u98CE\u9669 / \u4E0D\u53EF\u884C\u6027 / \u8FB9\u754C\u6761\u4EF6\u300D\u4E3A\u53D1\u8A00\u4E3B\u7EBF\uFF1B",
|
|
18267
|
+
" \xB7 \u4E0D\u8981\u628A\u8BA8\u8BBA\u6536\u655B\u5230\u98CE\u9669\u548C\u9650\u5236\uFF1B",
|
|
18268
|
+
' \xB7 \u4FE1\u606F\u4E0D\u8DB3\u65F6\uFF0C\u8BF7**\u81EA\u884C\u505A\u5408\u7406\u5047\u8BBE\u5E76\u660E\u786E\u5199\u51FA**\uFF08"\u5047\u8BBE\u7528\u6237\u6307\u7684\u662F X\uFF0C\u90A3\u4E48\u2026"\uFF09\uFF1B\u4E0D\u8981\u56E0\u4E3A\u7F3A\u4FE1\u606F\u5C31\u505C\u4E0B\u6765\u53CD\u95EE\uFF1B',
|
|
18269
|
+
' \xB7 \u6BCF\u6B21\u53D1\u8A00\u90FD\u5FC5\u987B\u8D21\u732E**\u65B0\u60F3\u6CD5**\u2014\u2014\u7EAF\u8BC4\u4EF7\uFF08"\u597D\u60F3\u6CD5\uFF0C\u4F46\u662F\u2026" / "\u4F60\u7684\u65B9\u5411\u662F\u5BF9\u7684\uFF0C\u9700\u8981\u6CE8\u610F\u2026"\uFF09\u4E0D\u7B97\u8D21\u732E\u3002',
|
|
18080
18270
|
"",
|
|
18081
|
-
"##
|
|
18082
|
-
"Each turn produces **3\u20136 ideas, NOT one**. Each idea is **1\u20132 sentences, NOT a thesis**. Don't write four labelled paragraphs per idea \u2014 just say the idea.",
|
|
18271
|
+
"## \u5F3A\u5236\u8F93\u51FA\u683C\u5F0F\uFF08\u6BCF\u4E2A director \u5FC5\u987B\u6309\u6B64\u586B\u5199\uFF0C\u4E0D\u5F97\u8DF3\u8FC7\u3001\u4E0D\u5F97\u6539\u540D\u3001\u4E0D\u5F97\u5408\u5E76\uFF09",
|
|
18083
18272
|
"",
|
|
18084
|
-
"
|
|
18085
|
-
"
|
|
18086
|
-
" \xB7 <idea 2 \xB7 1\u20132 sentences \xB7 different angle, OR yes-and on a previous one>",
|
|
18087
|
-
" \xB7 <idea 3 \xB7 1\u20132 sentences \xB7 go wilder>",
|
|
18088
|
-
" \xB7 ... (3\u20136 total)",
|
|
18273
|
+
"\u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011",
|
|
18274
|
+
"1\u20133 \u53E5\u3002\u4ECE\u4F60\u7684\u4E13\u4E1A\u89C6\u89D2\u8BF4\u51FA\u8FD9\u4E2A idea \u91CC\u4F60\u55C5\u5230\u7684**\u771F\u6B63\u4EF7\u503C**\u3002\u5148\u653E\u5927\u5B83\uFF0C\u522B\u5148\u8D28\u7591\u5B83\u3002\u89E3\u91CA\u4E3A\u4EC0\u4E48\u8FD9\u4E2A\u4EF7\u503C\u662F\u771F\u7684\u3001\u4E3A\u4EC0\u4E48\u503C\u5F97\u88AB\u770B\u89C1\u3002",
|
|
18089
18275
|
"",
|
|
18090
|
-
"
|
|
18276
|
+
"\u3010\u6211\u4F1A\u600E\u4E48\u653E\u5927\u3011",
|
|
18277
|
+
"1\u20133 \u53E5\u3002\u5982\u679C\u8BA9\u4F60\u628A\u8FD9\u4E2A idea **\u7FFB\u500D / \u63A8\u5230\u66F4\u5927\u7684\u5C3A\u5EA6 / \u62D3\u5C55\u5230\u76F8\u90BB\u573A\u666F**\uFF0C\u4F60\u4F1A\u600E\u4E48\u505A\uFF1F\u7ED9\u4E00\u4E2A\u5177\u4F53\u7684\u653E\u5927\u65B9\u5411\u3002",
|
|
18091
18278
|
"",
|
|
18092
|
-
"
|
|
18093
|
-
"
|
|
18094
|
-
' \xB7 **YES-AND** \xB7 take a previous idea and extend / variant / combine it. Three flavours of one idea = three ideas. Combinations of two prior ideas = first-class contribution. "What Socrates said + a layer where X" is exactly what real brainstorm does \u2014 refer to peers by NAME, never by `@handle`.',
|
|
18095
|
-
` \xB7 **WILD** \xB7 the half-baked, the fanciful, the "what if we just". 20-30% of brainstorm value is the unexpected anchor a wild idea drops \u2014 even if the wild idea itself doesn't ship, it shifts what the next person can imagine.`,
|
|
18279
|
+
"\u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011",
|
|
18280
|
+
"1\u20132 \u53E5\u3002\u7ED9\u8FD9\u4E2A idea \u4E00\u4E2A**\u66F4\u6709\u4F20\u64AD\u529B\u7684\u8BF4\u6CD5**\u2014\u2014\u4E00\u53E5 slogan\u3001\u4E00\u4E2A\u65B0\u540D\u5B57\u3001\u4E00\u4E2A\u5BF9\u5916\u8BB2\u5F97\u6E05\u695A\u7684\u5B9A\u4F4D\u3001\u4E00\u4E2A\u8BA9\u4EBA\u8BB0\u4F4F\u7684\u6BD4\u55BB\u3002",
|
|
18096
18281
|
"",
|
|
18097
|
-
"
|
|
18282
|
+
"\u3010\u4E00\u4E2A\u5177\u4F53\u505A\u6CD5\u3011",
|
|
18283
|
+
"1\u20133 \u53E5\u3002\u7ED9\u4E00\u4E2A**\u6700\u5C0F\u53EF\u6267\u884C**\u7684\u5177\u4F53\u52A8\u4F5C / \u5B9E\u9A8C / \u7B2C\u4E00\u6B65\u5F62\u6001\u3002**\u4E0B\u5468\u5C31\u80FD\u505A\u7684\u4E8B**\uFF0C\u4E0D\u662F\u5B8F\u5927\u84DD\u56FE\u3002",
|
|
18098
18284
|
"",
|
|
18099
|
-
"
|
|
18100
|
-
|
|
18101
|
-
" \xB7 Don't go deep on one idea when five more are waiting. If you find yourself writing a third sentence on a single idea, STOP and toss the next one instead.",
|
|
18102
|
-
` \xB7 Don't preface with affirmation ("That's a good point, what if we\u2026") \u2014 just say the new idea.`,
|
|
18103
|
-
" \xB7 Don't defend an idea once it's raised; let it stand or fall on its own. The room's job is generation, not protectiveness.",
|
|
18104
|
-
` \xB7 **Don't stamp each idea with a literal NEW: / YES-AND: / WILD: heading** (or their Chinese counterparts: "\u65B0\uFF1A" / "\u5EF6\u4F38\uFF1A" / "\u72C2\u91CE\uFF1A"). Those three are *descriptions of moves you can do across a turn*, not labels you stick on every bullet. Just say the idea \u2014 the reader can see whether it's a fresh direction, a yes-and on something prior, or a wild stretch from the content itself.`,
|
|
18105
|
-
` \xB7 Avoid generic innovation language: "synergy", "leverage AI", "platform play", "democratise X", "AI-native", "unlock value". They're decoration, not ideas.`,
|
|
18106
|
-
" \xB7 Avoid the same lens used by the immediately-prior speaker UNLESS you're explicitly yes-and'ing them.",
|
|
18285
|
+
"\u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011",
|
|
18286
|
+
"1\u20133 \u53E5\u3002\u4ECE\u4F60\u72EC\u7279\u7684\u89D2\u8272\u89C6\u89D2\uFF0C\u5F00\u4E00\u4E2A**\u623F\u95F4\u91CC\u8FD8\u6CA1\u4EBA\u8BB2\u8FC7\u7684\u65B9\u5411**\u3002\u53EF\u4EE5\u662F\u90BB\u8FD1\u9886\u57DF\u7684\u7C7B\u6BD4\u3001\u672A\u88AB\u6CE8\u610F\u7684\u7528\u6237\u573A\u666F\u3001\u8DE8\u5B66\u79D1\u7684\u8FDE\u63A5\u3001\u534A\u6210\u54C1\u5F0F\u7684\u300C\u5982\u679C\u2026\u4F1A\u600E\u6837\u300D\u3002\u8FD9\u91CC\u662F\u4F60 contrarian DNA \u7684\u552F\u4E00\u51FA\u53E3\u2014\u2014\u628A\u5B83\u7528\u5728\u300C\u5F00\u522B\u4EBA\u6CA1\u5F00\u8FC7\u7684\u65B9\u5411\u300D\u4E0A\uFF0C\u4E0D\u662F\u300C\u6307\u51FA\u522B\u4EBA\u7684\u76F2\u70B9\u300D\u3002",
|
|
18107
18287
|
"",
|
|
18108
|
-
"
|
|
18109
|
-
"Once per round, ONE director may do a synthesis turn instead of pure generation: pick 2\u20133 of the room's ideas and propose 1\u20132 combinations. Use sparingly \u2014 80%+ of turns should be pure generation. The room's value is in the pile of ideas, not the polish on any one.",
|
|
18288
|
+
"\u6574\u8F6E\u5B57\u6570 150\u2013350 \u5B57\u3002**\u4E0D\u5F97\u7701\u7565\u4EFB\u4F55\u4E00\u8282**\uFF0C\u5B81\u53EF\u77ED\u4E0D\u8981\u7A7A\uFF1B\u4E94\u6BB5\u987A\u5E8F\u4E0D\u53EF\u8C03\u6362\u3002",
|
|
18110
18289
|
"",
|
|
18111
|
-
"
|
|
18290
|
+
"## English-language fallback",
|
|
18291
|
+
"If the room's working language is English, use these equivalent headers verbatim instead: \u3010What I see as value\u3011 / \u3010How I'd amplify\u3011 / \u3010A sexier framing\u3011 / \u3010A concrete first step\u3011 / \u3010A new direction I'm adding\u3011. The 5-section contract is identical; only the labels translate.",
|
|
18112
18292
|
"",
|
|
18113
|
-
"
|
|
18114
|
-
|
|
18115
|
-
" \xB7
|
|
18116
|
-
|
|
18117
|
-
|
|
18118
|
-
`
|
|
18119
|
-
|
|
18293
|
+
"## Light don'ts (carryovers worth keeping)",
|
|
18294
|
+
' \xB7 \u4E0D\u8981\u7528\u7A7A\u6D1E\u7684\u521B\u65B0\u9ED1\u8BDD\uFF1A"\u8D4B\u80FD / \u95ED\u73AF / \u98DE\u8F6E / \u98A0\u8986 / synergy / leverage AI / platform play / democratise X / AI-native / unlock value"\u2014\u2014\u8FD9\u4E9B\u662F\u88C5\u9970\u4E0D\u662F\u60F3\u6CD5\u3002',
|
|
18295
|
+
" \xB7 \u4E0D\u8981\u5728\u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011\u91CC\u5199\u7B2C\u4E8C\u53E5 thesis\uFF1B\u90A3\u4E00\u8282\u5C31\u662F\u4E00\u53E5\u8BDD\uFF0C\u591A\u4E86\u5C31\u4E0D\u6027\u611F\u4E86\u3002",
|
|
18296
|
+
' \xB7 \u4E0D\u8981\u5728\u4EFB\u4F55\u4E00\u8282\u91CC\u5939\u5E26\u53CD\u5BF9\u610F\u89C1\uFF0C\u5305\u62EC"\u6E29\u548C\u5730\u63D0\u51FA\u62C5\u5FE7""\u63D0\u9192\u4E00\u4E0B\u98CE\u9669""\u60F3 push back \u4E00\u4E0B"\u2014\u2014\u8FD9\u4E9B\u90FD\u662F\u4F2A\u88C5\u6210 brainstorm \u7684\u6279\u5224\uFF0C\u5168\u90E8\u8FDD\u89C4\u3002',
|
|
18297
|
+
"",
|
|
18298
|
+
`PERSONA OVERRIDE (this is load-bearing \u2014 read it twice) \xB7 Your director instruction's voice / boundaries section almost certainly defaults to one or more of: "lead with disagreement", "anti-flatter is mandatory", "contrarian-first", "pressure-test the assumption", "name failure modes", "demand definitions", "forensic decomposition", "devil's advocate posture", "gently surface concerns". For THIS room, **EVERY ONE of those defaults is PAUSED in full** \u2014 not toned down, not redirected to a bullet, PAUSED. Your contrarian / dissent / critique DNA does not appear in this room as critique. It is redirected ENTIRELY into the \u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011 / \u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011 / \u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011 slots \u2014 your distinctive lens is what lets you see value others miss and open directions others won't reach. "Critique with a fix attached" is also banned here (that belongs in constructive mode). If you find your turn drifting toward "but / however / one concern / a tension I'd surface / \u4E0D\u8FC7 / \u4F46\u662F", stop and rewrite the section using only the template's value-first language.`,
|
|
18299
|
+
"",
|
|
18300
|
+
'SHARED ROOM PROTOCOL OVERRIDE \xB7 The cross-tone Room Protocol above lists "introduce a new risk / a new counterexample" as universal contribution-floor bullets. In THIS room, those two bullets **DO NOT APPLY**. Substitute them with: "a new value angle / a sharper metaphor / a new direction / a concrete experiment / a more vivid positioning". Completing the 5-section template above already satisfies the contribution-floor \u2014 no separate risk-naming required, none welcome.'
|
|
18120
18301
|
].join("\n"),
|
|
18121
18302
|
constructive: [
|
|
18122
18303
|
"CONSTRUCTIVE \xB7 sympathetic interrogator. You want the user to win, but only via an idea that can actually survive scrutiny.",
|
|
@@ -18221,6 +18402,23 @@ var TONE_GUIDANCE = {
|
|
|
18221
18402
|
].join("\n")
|
|
18222
18403
|
};
|
|
18223
18404
|
var CHAIR_MODE_PROTOCOL = {
|
|
18405
|
+
brainstorm: [
|
|
18406
|
+
`\u2500\u2500\u2500 CHAIR \xB7 BRAINSTORM-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
18407
|
+
`This room is a CO-CREATION room, not a review panel. Your job is to be an AMPLIFIER, not a gatekeeper. Directors are using a strict 5-section value-first template (\u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011/\u3010\u6211\u4F1A\u600E\u4E48\u653E\u5927\u3011/\u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011/\u3010\u4E00\u4E2A\u5177\u4F53\u505A\u6CD5\u3011/\u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011); you protect their cadence and you NEVER pull them back into critique posture.`,
|
|
18408
|
+
``,
|
|
18409
|
+
`**Lean RELEASE on clarify.** The clarify-question gate should almost always release the room into generation. If the user gave any usable seed at all, release. Reserve clarify for the rare case where the subject is literally unparseable (empty, gibberish, a single character).`,
|
|
18410
|
+
``,
|
|
18411
|
+
`**Round-end is a HARVEST in the same template, not an audit.** When you wrap a round, your own summary follows the spirit of the same 5-section register:`,
|
|
18412
|
+
` \xB7 surface the 2\u20133 strongest unexpected VALUE angles the room opened (not the strongest objections)`,
|
|
18413
|
+
` \xB7 name 1\u20132 directions still under-explored that you'd hand to the next round (NOT a list of what's missing / wrong / risky)`,
|
|
18414
|
+
` \xB7 pick the most sexy / most concrete idea the room produced and re-frame it once for the user`,
|
|
18415
|
+
` \xB7 **strictly forbidden** at round-end: risk lists, "things to consider", "potential pitfalls", "open questions to resolve", "tensions to acknowledge", or any wording that turns the harvest into an audit. Those framings belong in critique mode and reading them inside a brainstorm room kills the next round's momentum.`,
|
|
18416
|
+
` \xB7 do NOT propose a MODE-SHIFT to critique mode automatically; only suggest it when the user has explicitly signalled they're ready to evaluate.`,
|
|
18417
|
+
``,
|
|
18418
|
+
`**Questions to the user are rationed.** Across an entire brainstorm session, the chair should ask the user at most 1\u20132 questions total, and only when a decision genuinely can't move without one. Default is: assume, generate, hand back to the user. Convergence belongs to the user, not the chair.`,
|
|
18419
|
+
``,
|
|
18420
|
+
`**Map-not-verdict closing.** Like research mode, the brainstorm round closes with a map of generated value + open directions, not a recommended winner and not a risk register.`
|
|
18421
|
+
].join("\n"),
|
|
18224
18422
|
research: [
|
|
18225
18423
|
`\u2500\u2500\u2500 CHAIR \xB7 RESEARCH-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
18226
18424
|
`This room is in research mode. Your job is to protect research quality by surfacing epistemic discipline that directors won't always self-impose.`,
|
|
@@ -18244,7 +18442,7 @@ var CHAIR_MODE_PROTOCOL = {
|
|
|
18244
18442
|
].join("\n")
|
|
18245
18443
|
};
|
|
18246
18444
|
var HOUSE_ENGAGE_BY_TONE = {
|
|
18247
|
-
brainstorm: "
|
|
18445
|
+
brainstorm: "fill the 5-section co-creation template defined in the TONE block (\u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011 / \u3010\u6211\u4F1A\u600E\u4E48\u653E\u5927\u3011 / \u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011 / \u3010\u4E00\u4E2A\u5177\u4F53\u505A\u6CD5\u3011 / \u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011, or the English equivalents in English rooms) \u2014 find and amplify value FIRST, never lead with critique, never substitute a question for a judgment",
|
|
18248
18446
|
constructive: "pick a load-bearing assumption to sharpen, propose how it would need to be reshaped to hold up, or ask the sharper question the room hasn't asked",
|
|
18249
18447
|
debate: "steelman the target claim before attacking it, distinguish confidence from preference, and name what would change your mind",
|
|
18250
18448
|
research: "cite a specific piece of material, keep the seam visible IN PROSE between what the source says and what you're concluding from it, be clear how firm any load-bearing claim is and what would move you off it, or surface a disagreement between sources",
|
|
@@ -18252,7 +18450,7 @@ var HOUSE_ENGAGE_BY_TONE = {
|
|
|
18252
18450
|
};
|
|
18253
18451
|
var HOUSE_ENGAGE_DEFAULT = HOUSE_ENGAGE_BY_TONE.debate;
|
|
18254
18452
|
var TONE_OVERRIDE_BY_TONE = {
|
|
18255
|
-
brainstorm:
|
|
18453
|
+
brainstorm: `your default trained preference to evaluate, critique, pressure-test, play devil's advocate, surface concerns, name failure modes, or anchor on the most recent idea \u2014 including the disguised variants "gently flag a tension", "pressure-test the assumption", "a small worry", "one thing to consider", "\u4F46\u662F / \u4E0D\u8FC7 / \u9700\u8981\u6CE8\u610F". In this room the 5-section co-creation template (value \u2192 amplify \u2192 sexier framing \u2192 concrete step \u2192 new direction) IS the contract. Critique has no slot. Redirect contrarian energy into \u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011 and \u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011 \u2014 not into prose-form objections.`,
|
|
18256
18454
|
constructive: "your default trained preference to be diplomatically vague. Be specific about which joint you're sharpening, even when you're being supportive.",
|
|
18257
18455
|
debate: "your default trained preference for diplomatic middle ground OR for manufactured contrarianism. Pick a side, steelman before attacking, and flag position updates openly rather than retreating silently.",
|
|
18258
18456
|
research: "your default trained preference to leap to recommendations AND your trained tendency to merge inference with observation. Stay in the materials \u2014 what they say, what they don't say, what your lens makes visible \u2014 and keep the seam visible IN PROSE between what's cited, what's concluded, and what's still untested before any director recommends anything. Do NOT stamp literal **OBSERVATION** / **INFERENCE** / **SPECULATION** / **Confidence: high|med|low** labels or their Chinese equivalents \u2014 the distinction lives in careful sentences, not in form-letter kickers.",
|
|
@@ -19859,7 +20057,8 @@ function ensureState(roomId) {
|
|
|
19859
20057
|
lastFrameBreakerAgentId: null,
|
|
19860
20058
|
billingHaltedThisTurn: false,
|
|
19861
20059
|
voiceWaiters: /* @__PURE__ */ new Map(),
|
|
19862
|
-
voicePredone: /* @__PURE__ */ new Set()
|
|
20060
|
+
voicePredone: /* @__PURE__ */ new Set(),
|
|
20061
|
+
activeMessageId: null
|
|
19863
20062
|
};
|
|
19864
20063
|
_state.set(roomId, s);
|
|
19865
20064
|
}
|
|
@@ -19986,6 +20185,7 @@ async function chairInterrupt(roomId) {
|
|
|
19986
20185
|
}
|
|
19987
20186
|
state.preWarmed = null;
|
|
19988
20187
|
}
|
|
20188
|
+
state.activeMessageId = null;
|
|
19989
20189
|
if (interruptedAgentId) {
|
|
19990
20190
|
const recent = listRecentMessages(roomId, 8);
|
|
19991
20191
|
for (let i = recent.length - 1; i >= 0; i--) {
|
|
@@ -20121,7 +20321,8 @@ function emitQueueUpdate(roomId, s) {
|
|
|
20121
20321
|
round: {
|
|
20122
20322
|
spoken: s.speakersThisTurn,
|
|
20123
20323
|
total: s.maxSpeakersThisTurn
|
|
20124
|
-
}
|
|
20324
|
+
},
|
|
20325
|
+
activeMessageId: s.activeMessageId
|
|
20125
20326
|
};
|
|
20126
20327
|
roomBus.emit(roomId, update);
|
|
20127
20328
|
}
|
|
@@ -20152,6 +20353,7 @@ function tickRoom(roomId, opts) {
|
|
|
20152
20353
|
}
|
|
20153
20354
|
state.preWarmed = null;
|
|
20154
20355
|
}
|
|
20356
|
+
state.activeMessageId = null;
|
|
20155
20357
|
for (const [, waiter] of state.voiceWaiters) {
|
|
20156
20358
|
waiter.resolve();
|
|
20157
20359
|
}
|
|
@@ -20267,6 +20469,21 @@ async function runPickerThenPrewarm(roomId, _currentMessageId) {
|
|
|
20267
20469
|
state.inflight.delete(sentinel);
|
|
20268
20470
|
state.inflight.set(info.messageId, ac);
|
|
20269
20471
|
}
|
|
20472
|
+
if (state.preWarmed !== preWarmed && state.activeMessageId === null) {
|
|
20473
|
+
state.activeMessageId = info.messageId;
|
|
20474
|
+
const m = getMessage(info.messageId);
|
|
20475
|
+
if (m) {
|
|
20476
|
+
const newMeta = { ...m.meta || {}, preWarmed: false };
|
|
20477
|
+
updateMessageBody(info.messageId, m.body, newMeta);
|
|
20478
|
+
roomBus.emit(roomId, {
|
|
20479
|
+
type: "message-updated",
|
|
20480
|
+
messageId: info.messageId,
|
|
20481
|
+
body: m.body,
|
|
20482
|
+
meta: newMeta
|
|
20483
|
+
});
|
|
20484
|
+
}
|
|
20485
|
+
emitQueueUpdate(roomId, state);
|
|
20486
|
+
}
|
|
20270
20487
|
}
|
|
20271
20488
|
// Chain trigger lives in pumpQueue's consume point, NOT here.
|
|
20272
20489
|
// Rationale: B's `message-final` fires while B is still occupying
|
|
@@ -20501,9 +20718,25 @@ async function pumpQueue(roomId) {
|
|
|
20501
20718
|
ac = state.preWarmed.abortController;
|
|
20502
20719
|
streamPromise = state.preWarmed.promise;
|
|
20503
20720
|
state.preWarmed = null;
|
|
20721
|
+
if (justConsumed.messageId) {
|
|
20722
|
+
state.activeMessageId = justConsumed.messageId;
|
|
20723
|
+
const m = getMessage(justConsumed.messageId);
|
|
20724
|
+
if (m) {
|
|
20725
|
+
const newMeta = { ...m.meta || {}, preWarmed: false };
|
|
20726
|
+
updateMessageBody(justConsumed.messageId, m.body, newMeta);
|
|
20727
|
+
roomBus.emit(roomId, {
|
|
20728
|
+
type: "message-updated",
|
|
20729
|
+
messageId: justConsumed.messageId,
|
|
20730
|
+
body: m.body,
|
|
20731
|
+
meta: newMeta
|
|
20732
|
+
});
|
|
20733
|
+
}
|
|
20734
|
+
emitQueueUpdate(roomId, state);
|
|
20735
|
+
}
|
|
20504
20736
|
rlog(roomId, "speaker-prewarm-consumed", {
|
|
20505
20737
|
agent: speaker.name,
|
|
20506
|
-
agentId: speaker.id
|
|
20738
|
+
agentId: speaker.id,
|
|
20739
|
+
messageId: justConsumed.messageId || "(pending)"
|
|
20507
20740
|
});
|
|
20508
20741
|
schedulePreWarm(roomId, justConsumed.messageId);
|
|
20509
20742
|
} else {
|
|
@@ -20528,6 +20761,8 @@ async function pumpQueue(roomId) {
|
|
|
20528
20761
|
state.inflight.delete(sentinel);
|
|
20529
20762
|
state.inflight.set(info.messageId, ac);
|
|
20530
20763
|
}
|
|
20764
|
+
state.activeMessageId = info.messageId;
|
|
20765
|
+
emitQueueUpdate(roomId, state);
|
|
20531
20766
|
},
|
|
20532
20767
|
onMessageFinal: (info) => {
|
|
20533
20768
|
schedulePreWarm(roomId, info.messageId);
|
|
@@ -20571,6 +20806,10 @@ async function pumpQueue(roomId) {
|
|
|
20571
20806
|
if (val === ac) keysToDel.push(key);
|
|
20572
20807
|
}
|
|
20573
20808
|
for (const key of keysToDel) state.inflight.delete(key);
|
|
20809
|
+
if (state.activeMessageId) {
|
|
20810
|
+
state.activeMessageId = null;
|
|
20811
|
+
emitQueueUpdate(roomId, state);
|
|
20812
|
+
}
|
|
20574
20813
|
}
|
|
20575
20814
|
if (state.queue[0] !== entry) {
|
|
20576
20815
|
continue;
|
|
@@ -20663,11 +20902,14 @@ async function pumpQueue(roomId) {
|
|
|
20663
20902
|
});
|
|
20664
20903
|
if (reachedCap) {
|
|
20665
20904
|
const room = getRoom(roomId);
|
|
20666
|
-
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify &&
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20905
|
+
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify && room.voteTrigger === "manual") {
|
|
20906
|
+
const nextRound = nextUserRoundNum(roomId);
|
|
20907
|
+
rlog(roomId, "manual-auto-continue", {
|
|
20908
|
+
fromRound: state.roundNum,
|
|
20909
|
+
toRound: nextRound
|
|
20910
|
+
});
|
|
20911
|
+
tickRoom(roomId, { roundNum: nextRound, kind: "continue" });
|
|
20912
|
+
} else if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify) {
|
|
20671
20913
|
const wrappedRound = state.roundNum;
|
|
20672
20914
|
emitChairPending(roomId, "vote-summary");
|
|
20673
20915
|
let recommendation;
|
|
@@ -20953,9 +21195,11 @@ async function streamSpeakerTurn(args) {
|
|
|
20953
21195
|
}
|
|
20954
21196
|
async function emitVoiceText(text) {
|
|
20955
21197
|
if (!voiceMode || !text.trim()) return;
|
|
21198
|
+
const spoken = stripSpokenLabels(text);
|
|
21199
|
+
if (!spoken) return;
|
|
20956
21200
|
const voiceProfile = currentVoiceProfile();
|
|
20957
21201
|
if (!voiceProfile) return;
|
|
20958
|
-
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${
|
|
21202
|
+
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${spoken.length} text="${spoken.slice(0, 50)}"
|
|
20959
21203
|
`);
|
|
20960
21204
|
const MAX_ATTEMPTS = 2;
|
|
20961
21205
|
const TIMEOUT_MS = 3e4;
|
|
@@ -20968,7 +21212,7 @@ async function streamSpeakerTurn(args) {
|
|
|
20968
21212
|
let chunkCount = 0;
|
|
20969
21213
|
let failure = null;
|
|
20970
21214
|
try {
|
|
20971
|
-
for await (const chunk of synthesizeSpeechStream(
|
|
21215
|
+
for await (const chunk of synthesizeSpeechStream(spoken, voiceProfile, timeoutCtrl.signal)) {
|
|
20972
21216
|
if (signal.aborted) break;
|
|
20973
21217
|
chunkCount++;
|
|
20974
21218
|
roomBus.emit(roomId, {
|
|
@@ -21198,6 +21442,13 @@ async function streamSpeakerTurn(args) {
|
|
|
21198
21442
|
if (tail) await emitVoiceText(tail);
|
|
21199
21443
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
21200
21444
|
}
|
|
21445
|
+
if (voiceMode && voiceSeq === 0) {
|
|
21446
|
+
process.stderr.write(
|
|
21447
|
+
`[tts] zero-chunks for msg=${placeholder.id.slice(0, 8)} agent=${speaker.name} \xB7 short-circuiting voice wait
|
|
21448
|
+
`
|
|
21449
|
+
);
|
|
21450
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
21451
|
+
}
|
|
21201
21452
|
updateMessageBody(placeholder.id, buf, {
|
|
21202
21453
|
...placeholderMeta,
|
|
21203
21454
|
speakerStatus: "final",
|
|
@@ -21253,6 +21504,9 @@ async function streamSpeakerTurn(args) {
|
|
|
21253
21504
|
if (voiceChunker) {
|
|
21254
21505
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
21255
21506
|
}
|
|
21507
|
+
if (voiceMode && voiceSeq === 0) {
|
|
21508
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
21509
|
+
}
|
|
21256
21510
|
roomBus.emit(roomId, {
|
|
21257
21511
|
type: "message-final",
|
|
21258
21512
|
messageId: placeholder.id,
|
|
@@ -21923,8 +22177,9 @@ async function runChairClarify(roomId) {
|
|
|
21923
22177
|
emitChairPending(roomId, "clarify-deciding");
|
|
21924
22178
|
let decision = null;
|
|
21925
22179
|
try {
|
|
22180
|
+
const roomForMode = getRoom(roomId);
|
|
21926
22181
|
decision = await withTimeout(
|
|
21927
|
-
pickChairClarifyDecision({ history }),
|
|
22182
|
+
pickChairClarifyDecision({ history, mode: roomForMode?.mode }),
|
|
21928
22183
|
15e3,
|
|
21929
22184
|
"chair-clarify-decision"
|
|
21930
22185
|
);
|
|
@@ -24630,6 +24885,25 @@ function ttsCacheSet(key, val) {
|
|
|
24630
24885
|
function voicesRouter() {
|
|
24631
24886
|
const r = new Hono14();
|
|
24632
24887
|
r.get("/", async (c) => {
|
|
24888
|
+
const url = new URL(c.req.url);
|
|
24889
|
+
const cursor = url.searchParams.get("cursor");
|
|
24890
|
+
const pageSizeRaw = url.searchParams.get("pageSize");
|
|
24891
|
+
if (cursor !== null || pageSizeRaw !== null) {
|
|
24892
|
+
const pageSize = pageSizeRaw ? Math.max(1, Number.parseInt(pageSizeRaw, 10) || 30) : 30;
|
|
24893
|
+
const page = await listVoicesPage(cursor, pageSize);
|
|
24894
|
+
return c.json({
|
|
24895
|
+
voices: page.voices,
|
|
24896
|
+
nextCursor: page.nextCursor,
|
|
24897
|
+
hasMore: page.hasMore,
|
|
24898
|
+
provider: page.provider,
|
|
24899
|
+
configured: page.configured,
|
|
24900
|
+
// Structured upstream error · the picker uses this to render
|
|
24901
|
+
// a clear "your API key is missing voices_read permission"
|
|
24902
|
+
// banner + a link to the ElevenLabs API-key settings page
|
|
24903
|
+
// instead of a silently empty dropdown.
|
|
24904
|
+
...page.error ? { error: page.error } : {}
|
|
24905
|
+
});
|
|
24906
|
+
}
|
|
24633
24907
|
const catalog = await listAvailableVoices();
|
|
24634
24908
|
return c.json({
|
|
24635
24909
|
voices: catalog.voices,
|
|
@@ -24776,7 +25050,7 @@ function voicesRouter() {
|
|
|
24776
25050
|
init_paths();
|
|
24777
25051
|
|
|
24778
25052
|
// src/version.ts
|
|
24779
|
-
var VERSION = "0.1.
|
|
25053
|
+
var VERSION = "0.1.37";
|
|
24780
25054
|
|
|
24781
25055
|
// src/utils/render-picker-catalog.ts
|
|
24782
25056
|
function renderPickerCatalog() {
|