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/boot.js
CHANGED
|
@@ -2636,7 +2636,13 @@ var MODELS = {
|
|
|
2636
2636
|
baiId: "claude-opus-4.7",
|
|
2637
2637
|
displayName: "Opus 4.7",
|
|
2638
2638
|
contextBudget: 2e5,
|
|
2639
|
-
deck: "deep reasoning"
|
|
2639
|
+
deck: "deep reasoning",
|
|
2640
|
+
// Anthropic dropped `temperature` for the 4.7 family · sending it
|
|
2641
|
+
// returns HTTP 400 "temperature is deprecated for this model"
|
|
2642
|
+
// across every carrier (direct / OR / B.AI all proxy to the same
|
|
2643
|
+
// upstream). The adapter omits temperature for any model with
|
|
2644
|
+
// this flag.
|
|
2645
|
+
noTemperature: true
|
|
2640
2646
|
},
|
|
2641
2647
|
"opus-4-6-fast": {
|
|
2642
2648
|
v: "opus-4-6-fast",
|
|
@@ -2848,6 +2854,16 @@ function getModel(v) {
|
|
|
2848
2854
|
function isModelV(v) {
|
|
2849
2855
|
return Object.hasOwn(MODELS, v);
|
|
2850
2856
|
}
|
|
2857
|
+
function noTemperatureModelIds() {
|
|
2858
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2859
|
+
for (const m of Object.values(MODELS)) {
|
|
2860
|
+
if (!m.noTemperature) continue;
|
|
2861
|
+
if (m.directApiId) ids.add(m.directApiId);
|
|
2862
|
+
if (m.openrouterId) ids.add(m.openrouterId);
|
|
2863
|
+
if (m.baiId) ids.add(m.baiId);
|
|
2864
|
+
}
|
|
2865
|
+
return ids;
|
|
2866
|
+
}
|
|
2851
2867
|
|
|
2852
2868
|
// src/routes/agents.ts
|
|
2853
2869
|
init_persona_jobs();
|
|
@@ -4040,21 +4056,47 @@ function redactHeaderValue(name, value) {
|
|
|
4040
4056
|
const tail = v.slice(-4);
|
|
4041
4057
|
return tail ? `****${tail}` : "****";
|
|
4042
4058
|
}
|
|
4059
|
+
var NO_TEMP_IDS_CACHE = null;
|
|
4060
|
+
function noTempIds() {
|
|
4061
|
+
if (!NO_TEMP_IDS_CACHE) NO_TEMP_IDS_CACHE = noTemperatureModelIds();
|
|
4062
|
+
return NO_TEMP_IDS_CACHE;
|
|
4063
|
+
}
|
|
4064
|
+
function stripTemperatureForNoTempModels(rawBody) {
|
|
4065
|
+
try {
|
|
4066
|
+
const parsed = JSON.parse(rawBody);
|
|
4067
|
+
const modelId = typeof parsed.model === "string" ? parsed.model : null;
|
|
4068
|
+
if (modelId && noTempIds().has(modelId) && "temperature" in parsed) {
|
|
4069
|
+
delete parsed.temperature;
|
|
4070
|
+
return { body: JSON.stringify(parsed), stripped: true };
|
|
4071
|
+
}
|
|
4072
|
+
} catch {
|
|
4073
|
+
}
|
|
4074
|
+
return { body: rawBody, stripped: false };
|
|
4075
|
+
}
|
|
4043
4076
|
function makeLoggedFetch(tag) {
|
|
4044
4077
|
return function loggedFetch2(input, init) {
|
|
4078
|
+
let effectiveInit = init;
|
|
4079
|
+
let stripNote = "";
|
|
4080
|
+
if (init?.body && typeof init.body === "string") {
|
|
4081
|
+
const r = stripTemperatureForNoTempModels(init.body);
|
|
4082
|
+
if (r.stripped) {
|
|
4083
|
+
effectiveInit = { ...init, body: r.body };
|
|
4084
|
+
stripNote = ` \xB7 stripped temperature (noTemperature model)`;
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4045
4087
|
const url = typeof input === "string" || input instanceof URL ? String(input) : input.url;
|
|
4046
|
-
const method = (
|
|
4047
|
-
const headers = new Headers(
|
|
4088
|
+
const method = (effectiveInit?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
4089
|
+
const headers = new Headers(effectiveInit?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
4048
4090
|
const headerLines = [];
|
|
4049
4091
|
headers.forEach((v, k) => {
|
|
4050
4092
|
headerLines.push(` ${k}: ${redactHeaderValue(k, v)}`);
|
|
4051
4093
|
});
|
|
4052
4094
|
let bodyPretty = "";
|
|
4053
|
-
if (
|
|
4095
|
+
if (effectiveInit?.body && typeof effectiveInit.body === "string") {
|
|
4054
4096
|
try {
|
|
4055
|
-
bodyPretty = JSON.stringify(JSON.parse(
|
|
4097
|
+
bodyPretty = JSON.stringify(JSON.parse(effectiveInit.body), null, 2);
|
|
4056
4098
|
} catch {
|
|
4057
|
-
bodyPretty =
|
|
4099
|
+
bodyPretty = effectiveInit.body.length > 2e3 ? effectiveInit.body.slice(0, 2e3) + "\u2026" : effectiveInit.body;
|
|
4058
4100
|
}
|
|
4059
4101
|
}
|
|
4060
4102
|
const sep = "\u2500".repeat(60);
|
|
@@ -4067,14 +4109,14 @@ function makeLoggedFetch(tag) {
|
|
|
4067
4109
|
process.stderr.write(
|
|
4068
4110
|
`
|
|
4069
4111
|
\u250C${sep}
|
|
4070
|
-
\u2502 [${tag} \u2192] ${method} ${url}
|
|
4112
|
+
\u2502 [${tag} \u2192] ${method} ${url}${stripNote}
|
|
4071
4113
|
` + headerBlock + `
|
|
4072
4114
|
` + bodyBlock + `
|
|
4073
4115
|
\u2514${sep}
|
|
4074
4116
|
`
|
|
4075
4117
|
);
|
|
4076
4118
|
const t0 = Date.now();
|
|
4077
|
-
return fetch(input,
|
|
4119
|
+
return fetch(input, effectiveInit).then(async (res) => {
|
|
4078
4120
|
const ms = Date.now() - t0;
|
|
4079
4121
|
const resHeaderLines = [];
|
|
4080
4122
|
res.headers.forEach((v, k) => resHeaderLines.push(` ${k}: ${v}`));
|
|
@@ -4335,6 +4377,7 @@ async function* callLLMStream(req) {
|
|
|
4335
4377
|
yield { type: "error", message: formatStreamError(e) };
|
|
4336
4378
|
return;
|
|
4337
4379
|
}
|
|
4380
|
+
const temperature = getModel(req.modelV).noTemperature ? void 0 : req.temperature;
|
|
4338
4381
|
let attempt = 0;
|
|
4339
4382
|
let lastTransientMessage = "";
|
|
4340
4383
|
let yieldedText = false;
|
|
@@ -4360,7 +4403,7 @@ async function* callLLMStream(req) {
|
|
|
4360
4403
|
model: resolved.model,
|
|
4361
4404
|
providerOptions: resolved.providerOptions,
|
|
4362
4405
|
messages: req.messages,
|
|
4363
|
-
temperature
|
|
4406
|
+
temperature,
|
|
4364
4407
|
// Vercel SDK names this maxOutputTokens in v4+; tolerate both.
|
|
4365
4408
|
maxTokens: req.maxTokens,
|
|
4366
4409
|
abortSignal: req.signal
|
|
@@ -7987,8 +8030,11 @@ var CLUSTER_MAX_SIZE = 60;
|
|
|
7987
8030
|
var PROMOTE_MIN_PROVENANCE = 3;
|
|
7988
8031
|
var PROMOTE_MIN_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7989
8032
|
var PROMOTE_MIN_CONFIDENCE = 0.6;
|
|
7990
|
-
var USER_LONG_HARVEST_MIN_LONG =
|
|
7991
|
-
var USER_LONG_HARVEST_MIN_NEW_PROMOTED =
|
|
8033
|
+
var USER_LONG_HARVEST_MIN_LONG = 2;
|
|
8034
|
+
var USER_LONG_HARVEST_MIN_NEW_PROMOTED = 1;
|
|
8035
|
+
var USER_LONG_HARVEST_MIN_SHORT_HIGH = 6;
|
|
8036
|
+
var USER_LONG_SHORT_CONF_FLOOR = 0.6;
|
|
8037
|
+
var USER_LONG_HARVEST_INPUT_CAP = 40;
|
|
7992
8038
|
var USER_LONG_CAP = 30;
|
|
7993
8039
|
async function runDreamCycle(agentId, config = {}) {
|
|
7994
8040
|
const startedAt = Date.now();
|
|
@@ -8099,13 +8145,18 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
8099
8145
|
if (!config.skipLLM && utility && agent?.roleKind === "moderator") {
|
|
8100
8146
|
try {
|
|
8101
8147
|
const chairLong = listTierForAgent(agentId, "long");
|
|
8102
|
-
const
|
|
8148
|
+
const chairShortHigh = listTierForAgent(agentId, "short").filter((m) => m.confidence >= USER_LONG_SHORT_CONF_FLOOR && !m.pinned);
|
|
8149
|
+
const eligible = chairLong.length >= USER_LONG_HARVEST_MIN_LONG || promoted >= USER_LONG_HARVEST_MIN_NEW_PROMOTED || chairShortHigh.length >= USER_LONG_HARVEST_MIN_SHORT_HIGH;
|
|
8103
8150
|
if (eligible) {
|
|
8104
8151
|
const existing = listActiveUserLongMemory();
|
|
8152
|
+
const pool = [
|
|
8153
|
+
...chairLong,
|
|
8154
|
+
...chairShortHigh.slice().sort((a, b) => b.confidence - a.confidence)
|
|
8155
|
+
].slice(0, USER_LONG_HARVEST_INPUT_CAP);
|
|
8105
8156
|
const harvest = await harvestUserLongMemory({
|
|
8106
8157
|
modelV: utility,
|
|
8107
8158
|
userName,
|
|
8108
|
-
chairLong,
|
|
8159
|
+
chairLong: pool,
|
|
8109
8160
|
existing
|
|
8110
8161
|
});
|
|
8111
8162
|
for (const t of harvest.newTags) {
|
|
@@ -8194,14 +8245,14 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
8194
8245
|
var HARVEST_EMPTY = { newTags: [], reinforce: [], supersede: [] };
|
|
8195
8246
|
function buildHarvestPrompt(opts) {
|
|
8196
8247
|
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");
|
|
8197
|
-
const chairBlock = opts.chairLong.length === 0 ? "(no
|
|
8248
|
+
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");
|
|
8198
8249
|
return [
|
|
8199
|
-
`You are reviewing the chair's
|
|
8250
|
+
`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).`,
|
|
8200
8251
|
``,
|
|
8201
8252
|
`## Existing user-long-memory tags`,
|
|
8202
8253
|
existingBlock,
|
|
8203
8254
|
``,
|
|
8204
|
-
`## Chair
|
|
8255
|
+
`## Chair memories about ${opts.userName} (mixed pool \xB7 prefer long-tier or high-confidence short-tier entries when proposing tags)`,
|
|
8205
8256
|
chairBlock,
|
|
8206
8257
|
``,
|
|
8207
8258
|
`## Output`,
|
|
@@ -14964,161 +15015,117 @@ function listConfiguredVoices() {
|
|
|
14964
15015
|
});
|
|
14965
15016
|
return out;
|
|
14966
15017
|
}
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
15018
|
+
function encodeCursor(c) {
|
|
15019
|
+
return Buffer.from(JSON.stringify(c), "utf8").toString("base64url");
|
|
15020
|
+
}
|
|
15021
|
+
function decodeCursor(s) {
|
|
15022
|
+
if (!s) return null;
|
|
15023
|
+
try {
|
|
15024
|
+
const obj = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
15025
|
+
if (obj && (obj.src === "el" || obj.src === "mm")) return obj;
|
|
15026
|
+
} catch {
|
|
14972
15027
|
}
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
15028
|
+
return null;
|
|
15029
|
+
}
|
|
15030
|
+
function classifyElevenLabsError(status, body) {
|
|
15031
|
+
let parsed = null;
|
|
15032
|
+
try {
|
|
15033
|
+
parsed = JSON.parse(body);
|
|
15034
|
+
} catch {
|
|
14976
15035
|
}
|
|
14977
|
-
|
|
15036
|
+
const detail = parsed?.detail;
|
|
15037
|
+
const upstreamStatus = typeof detail?.status === "string" ? detail.status : "";
|
|
15038
|
+
const upstreamMessage = typeof detail?.message === "string" ? detail.message : body.slice(0, 200);
|
|
15039
|
+
if (status === 401 && upstreamStatus === "missing_permissions") {
|
|
15040
|
+
return {
|
|
15041
|
+
code: "missing_permissions",
|
|
15042
|
+
provider: "elevenlabs",
|
|
15043
|
+
message: upstreamMessage,
|
|
15044
|
+
// Direct link to the API-key management page · "Update key
|
|
15045
|
+
// permissions" is what the user needs to do, and ElevenLabs's
|
|
15046
|
+
// settings page surfaces the scope checkboxes prominently.
|
|
15047
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
15048
|
+
};
|
|
15049
|
+
}
|
|
15050
|
+
if (status === 401 || status === 403) {
|
|
15051
|
+
return {
|
|
15052
|
+
code: "auth_failed",
|
|
15053
|
+
provider: "elevenlabs",
|
|
15054
|
+
message: upstreamMessage,
|
|
15055
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
15056
|
+
};
|
|
15057
|
+
}
|
|
15058
|
+
if (status === 429) {
|
|
15059
|
+
return {
|
|
15060
|
+
code: "rate_limited",
|
|
15061
|
+
provider: "elevenlabs",
|
|
15062
|
+
message: upstreamMessage
|
|
15063
|
+
};
|
|
15064
|
+
}
|
|
15065
|
+
return {
|
|
15066
|
+
code: "fetch_failed",
|
|
15067
|
+
provider: "elevenlabs",
|
|
15068
|
+
message: `HTTP ${status}: ${upstreamMessage}`
|
|
15069
|
+
};
|
|
15070
|
+
}
|
|
15071
|
+
async function fetchAllElevenLabsV2Voices(apiKey) {
|
|
15072
|
+
const out = [];
|
|
15073
|
+
let token = null;
|
|
15074
|
+
let lastError = null;
|
|
15075
|
+
for (let i = 0; i < 20; i++) {
|
|
15076
|
+
const url = new URL("https://api.elevenlabs.io/v2/voices");
|
|
15077
|
+
url.searchParams.set("page_size", "100");
|
|
15078
|
+
if (token) url.searchParams.set("next_page_token", token);
|
|
14978
15079
|
try {
|
|
14979
|
-
const res = await fetch(
|
|
14980
|
-
|
|
14981
|
-
headers: {
|
|
14982
|
-
"authorization": `Bearer ${activeKey}`,
|
|
14983
|
-
"content-type": "application/json"
|
|
14984
|
-
},
|
|
14985
|
-
body: JSON.stringify({ voice_type: "all" })
|
|
15080
|
+
const res = await fetch(url.toString(), {
|
|
15081
|
+
headers: { "xi-api-key": apiKey }
|
|
14986
15082
|
});
|
|
14987
|
-
if (res.ok) {
|
|
14988
|
-
const
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
...voiceRows(json.voice_cloning, "clone"),
|
|
14992
|
-
...voiceRows(json.voice_generation, "generated")
|
|
14993
|
-
];
|
|
14994
|
-
if (rows.length > 0) {
|
|
14995
|
-
const nonMiniMax = voices.filter((v) => v.provider !== "minimax");
|
|
14996
|
-
voices = [
|
|
14997
|
-
...nonMiniMax,
|
|
14998
|
-
...rows.map((r) => ({
|
|
14999
|
-
provider: "minimax",
|
|
15000
|
-
model: "speech-2.8-hd",
|
|
15001
|
-
voiceId: r.voiceId,
|
|
15002
|
-
label: r.label,
|
|
15003
|
-
language: r.kind,
|
|
15004
|
-
configured: true
|
|
15005
|
-
}))
|
|
15006
|
-
];
|
|
15007
|
-
}
|
|
15008
|
-
}
|
|
15009
|
-
} catch {
|
|
15010
|
-
}
|
|
15011
|
-
return { voices, provider: "minimax", configured: true };
|
|
15012
|
-
}
|
|
15013
|
-
if (activeProvider === "elevenlabs") {
|
|
15014
|
-
const personal = [];
|
|
15015
|
-
const shared = [];
|
|
15016
|
-
await Promise.all([
|
|
15017
|
-
(async () => {
|
|
15018
|
-
try {
|
|
15019
|
-
const res = await fetch(
|
|
15020
|
-
"https://api.elevenlabs.io/v1/voices?show_legacy=true&include_total_count=true",
|
|
15021
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
15022
|
-
);
|
|
15023
|
-
if (!res.ok) {
|
|
15024
|
-
const errText = await res.text();
|
|
15025
|
-
process.stderr.write(
|
|
15026
|
-
`[voice-registry] elevenlabs /v1/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15027
|
-
`
|
|
15028
|
-
);
|
|
15029
|
-
return;
|
|
15030
|
-
}
|
|
15031
|
-
const json = await res.json();
|
|
15032
|
-
const rows = elevenLabsVoiceRows(json.voices);
|
|
15033
|
-
process.stderr.write(`[voice-registry] elevenlabs /v1/voices \xB7 ${rows.length} voices in personal library
|
|
15034
|
-
`);
|
|
15035
|
-
personal.push(...rows);
|
|
15036
|
-
} catch (e) {
|
|
15037
|
-
const cause = e instanceof Error ? e.cause : null;
|
|
15038
|
-
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
15039
|
-
process.stderr.write(
|
|
15040
|
-
`[voice-registry] elevenlabs /v1/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
15041
|
-
`
|
|
15042
|
-
);
|
|
15043
|
-
}
|
|
15044
|
-
})(),
|
|
15045
|
-
(async () => {
|
|
15046
|
-
try {
|
|
15047
|
-
const res = await fetch(
|
|
15048
|
-
"https://api.elevenlabs.io/v1/shared-voices?page_size=100",
|
|
15049
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
15050
|
-
);
|
|
15051
|
-
if (!res.ok) {
|
|
15052
|
-
const errText = await res.text();
|
|
15053
|
-
process.stderr.write(
|
|
15054
|
-
`[voice-registry] elevenlabs /v1/shared-voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15083
|
+
if (!res.ok) {
|
|
15084
|
+
const errText = await res.text();
|
|
15085
|
+
process.stderr.write(
|
|
15086
|
+
`[voice-registry] elevenlabs /v2/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15055
15087
|
`
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15088
|
+
);
|
|
15089
|
+
lastError = classifyElevenLabsError(res.status, errText);
|
|
15090
|
+
break;
|
|
15091
|
+
}
|
|
15092
|
+
const json = await res.json();
|
|
15093
|
+
const rows = elevenLabsV2VoiceRows(json.voices);
|
|
15094
|
+
for (const r of rows) {
|
|
15095
|
+
out.push({
|
|
15096
|
+
provider: "elevenlabs",
|
|
15097
|
+
model: "eleven_multilingual_v2",
|
|
15098
|
+
voiceId: r.voiceId,
|
|
15099
|
+
label: r.label,
|
|
15100
|
+
language: r.category,
|
|
15101
|
+
configured: true
|
|
15102
|
+
});
|
|
15103
|
+
}
|
|
15104
|
+
const nextToken = json.has_more === true && typeof json.next_page_token === "string" ? json.next_page_token : null;
|
|
15105
|
+
if (!nextToken) break;
|
|
15106
|
+
token = nextToken;
|
|
15107
|
+
} catch (e) {
|
|
15108
|
+
const cause = e instanceof Error ? e.cause : null;
|
|
15109
|
+
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
15110
|
+
process.stderr.write(
|
|
15111
|
+
`[voice-registry] elevenlabs /v2/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
15069
15112
|
`
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
]);
|
|
15074
|
-
if (personal.length > 0 || shared.length > 0) {
|
|
15075
|
-
const nonEl = voices.filter((v) => v.provider !== "elevenlabs");
|
|
15076
|
-
const personalIds = new Set(personal.map((r) => r.voiceId));
|
|
15077
|
-
const sharedDeduped = shared.filter((r) => !personalIds.has(r.voiceId));
|
|
15078
|
-
const personalMapped = personal.map((r) => ({
|
|
15079
|
-
provider: "elevenlabs",
|
|
15080
|
-
model: "eleven_multilingual_v2",
|
|
15081
|
-
voiceId: r.voiceId,
|
|
15082
|
-
label: r.label,
|
|
15083
|
-
// Personal-library rows keep their actual category
|
|
15084
|
-
// ("premade", "cloned", "professional", "generated").
|
|
15085
|
-
language: r.category,
|
|
15086
|
-
configured: true
|
|
15087
|
-
}));
|
|
15088
|
-
const sharedMapped = sharedDeduped.map((r) => ({
|
|
15113
|
+
);
|
|
15114
|
+
lastError = {
|
|
15115
|
+
code: "fetch_failed",
|
|
15089
15116
|
provider: "elevenlabs",
|
|
15090
|
-
|
|
15091
|
-
|
|
15092
|
-
|
|
15093
|
-
// which set they're picking from. The dropdown's group header
|
|
15094
|
-
// already says "elevenlabs", so the per-row prefix is the
|
|
15095
|
-
// tightest signal we have for personal-vs-shared.
|
|
15096
|
-
label: `${r.label} \xB7 shared`,
|
|
15097
|
-
language: r.language || r.category,
|
|
15098
|
-
configured: true
|
|
15099
|
-
}));
|
|
15100
|
-
voices = [...nonEl, ...personalMapped, ...sharedMapped];
|
|
15117
|
+
message: e instanceof Error ? e.message : String(e)
|
|
15118
|
+
};
|
|
15119
|
+
break;
|
|
15101
15120
|
}
|
|
15102
|
-
return { voices, provider: "elevenlabs", configured: true };
|
|
15103
15121
|
}
|
|
15104
|
-
|
|
15105
|
-
}
|
|
15106
|
-
|
|
15107
|
-
|
|
15108
|
-
|
|
15109
|
-
for (const item of raw) {
|
|
15110
|
-
if (!item || typeof item !== "object") continue;
|
|
15111
|
-
const obj = item;
|
|
15112
|
-
const voiceId = typeof obj.voice_id === "string" ? obj.voice_id : "";
|
|
15113
|
-
if (!voiceId) continue;
|
|
15114
|
-
const label = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : voiceId;
|
|
15115
|
-
const category = typeof obj.category === "string" && obj.category.trim() ? obj.category.trim() : "shared";
|
|
15116
|
-
const language = typeof obj.language === "string" && obj.language.trim() ? obj.language.trim() : void 0;
|
|
15117
|
-
out.push({ voiceId, label, category, language });
|
|
15118
|
-
}
|
|
15119
|
-
return out;
|
|
15122
|
+
process.stderr.write(
|
|
15123
|
+
`[voice-registry] elevenlabs /v2/voices \xB7 ${out.length} voices total across all pages
|
|
15124
|
+
`
|
|
15125
|
+
);
|
|
15126
|
+
return { voices: out, error: lastError };
|
|
15120
15127
|
}
|
|
15121
|
-
function
|
|
15128
|
+
function elevenLabsV2VoiceRows(raw) {
|
|
15122
15129
|
if (!Array.isArray(raw)) return [];
|
|
15123
15130
|
const out = [];
|
|
15124
15131
|
for (const item of raw) {
|
|
@@ -15132,6 +15139,164 @@ function elevenLabsVoiceRows(raw) {
|
|
|
15132
15139
|
}
|
|
15133
15140
|
return out;
|
|
15134
15141
|
}
|
|
15142
|
+
var ELEVENLABS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
15143
|
+
var elevenLabsCache = /* @__PURE__ */ new Map();
|
|
15144
|
+
function elevenLabsCacheKey(apiKey) {
|
|
15145
|
+
return apiKey.slice(0, 8);
|
|
15146
|
+
}
|
|
15147
|
+
async function getElevenLabsVoicesCached(apiKey) {
|
|
15148
|
+
const key = elevenLabsCacheKey(apiKey);
|
|
15149
|
+
const cached = elevenLabsCache.get(key);
|
|
15150
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
15151
|
+
return { voices: cached.voices, error: null };
|
|
15152
|
+
}
|
|
15153
|
+
const result = await fetchAllElevenLabsV2Voices(apiKey);
|
|
15154
|
+
process.stderr.write(
|
|
15155
|
+
`[voice-registry] elevenlabs catalogue \xB7 ${result.voices.length} voices from /v2/voices${result.error ? ` (error: ${result.error.code})` : ""}
|
|
15156
|
+
`
|
|
15157
|
+
);
|
|
15158
|
+
if (!result.error) {
|
|
15159
|
+
elevenLabsCache.set(key, { voices: result.voices, expiresAt: Date.now() + ELEVENLABS_CACHE_TTL_MS });
|
|
15160
|
+
}
|
|
15161
|
+
return result;
|
|
15162
|
+
}
|
|
15163
|
+
var MINIMAX_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
15164
|
+
var miniMaxCache = /* @__PURE__ */ new Map();
|
|
15165
|
+
function miniMaxCacheKey(apiKey) {
|
|
15166
|
+
return apiKey.slice(0, 8);
|
|
15167
|
+
}
|
|
15168
|
+
async function fetchAllMiniMaxVoices(apiKey) {
|
|
15169
|
+
try {
|
|
15170
|
+
const res = await fetch(`${minimaxBaseUrl()}/v1/get_voice`, {
|
|
15171
|
+
method: "POST",
|
|
15172
|
+
headers: {
|
|
15173
|
+
"authorization": `Bearer ${apiKey}`,
|
|
15174
|
+
"content-type": "application/json"
|
|
15175
|
+
},
|
|
15176
|
+
body: JSON.stringify({ voice_type: "all" })
|
|
15177
|
+
});
|
|
15178
|
+
if (!res.ok) {
|
|
15179
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15180
|
+
}
|
|
15181
|
+
const json = await res.json();
|
|
15182
|
+
const rows = [
|
|
15183
|
+
...voiceRows(json.system_voice, "system"),
|
|
15184
|
+
...voiceRows(json.voice_cloning, "clone"),
|
|
15185
|
+
...voiceRows(json.voice_generation, "generated")
|
|
15186
|
+
];
|
|
15187
|
+
if (rows.length === 0) {
|
|
15188
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15189
|
+
}
|
|
15190
|
+
return rows.map((r) => ({
|
|
15191
|
+
provider: "minimax",
|
|
15192
|
+
model: "speech-2.8-hd",
|
|
15193
|
+
voiceId: r.voiceId,
|
|
15194
|
+
label: r.label,
|
|
15195
|
+
language: r.kind,
|
|
15196
|
+
configured: true
|
|
15197
|
+
}));
|
|
15198
|
+
} catch {
|
|
15199
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15200
|
+
}
|
|
15201
|
+
}
|
|
15202
|
+
async function getMiniMaxVoicesCached(apiKey) {
|
|
15203
|
+
const key = miniMaxCacheKey(apiKey);
|
|
15204
|
+
const cached = miniMaxCache.get(key);
|
|
15205
|
+
if (cached && cached.expiresAt > Date.now()) return cached.voices;
|
|
15206
|
+
const voices = await fetchAllMiniMaxVoices(apiKey);
|
|
15207
|
+
miniMaxCache.set(key, { voices, expiresAt: Date.now() + MINIMAX_CACHE_TTL_MS });
|
|
15208
|
+
return voices;
|
|
15209
|
+
}
|
|
15210
|
+
var BROWSER_FALLBACK = {
|
|
15211
|
+
provider: "browser",
|
|
15212
|
+
model: "speechSynthesis",
|
|
15213
|
+
voiceId: "system-default",
|
|
15214
|
+
label: "Browser default",
|
|
15215
|
+
configured: true
|
|
15216
|
+
};
|
|
15217
|
+
async function listVoicesPage(cursorStr, pageSize) {
|
|
15218
|
+
const size = Math.min(Math.max(pageSize | 0 || 30, 5), 100);
|
|
15219
|
+
const cursor = decodeCursor(cursorStr);
|
|
15220
|
+
const isFirstPage = cursor === null;
|
|
15221
|
+
const activeProvider = getActiveVoiceProvider();
|
|
15222
|
+
const fixed = [];
|
|
15223
|
+
if (isFirstPage && getKey("openai")) {
|
|
15224
|
+
fixed.push(...OPENAI_VOICES.map((v) => ({ ...v, configured: true })));
|
|
15225
|
+
}
|
|
15226
|
+
if (!activeProvider) {
|
|
15227
|
+
return {
|
|
15228
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15229
|
+
nextCursor: null,
|
|
15230
|
+
hasMore: false,
|
|
15231
|
+
provider: null,
|
|
15232
|
+
configured: false
|
|
15233
|
+
};
|
|
15234
|
+
}
|
|
15235
|
+
const activeKey = getActiveVoiceKeyPlaintext();
|
|
15236
|
+
if (!activeKey) {
|
|
15237
|
+
return {
|
|
15238
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15239
|
+
nextCursor: null,
|
|
15240
|
+
hasMore: false,
|
|
15241
|
+
provider: activeProvider,
|
|
15242
|
+
configured: false
|
|
15243
|
+
};
|
|
15244
|
+
}
|
|
15245
|
+
if (activeProvider === "elevenlabs") {
|
|
15246
|
+
const { voices: all, error } = await getElevenLabsVoicesCached(activeKey);
|
|
15247
|
+
const offset = cursor && cursor.src === "el" ? cursor.offset ?? 0 : 0;
|
|
15248
|
+
const slice = all.slice(offset, offset + size);
|
|
15249
|
+
const next = offset + slice.length;
|
|
15250
|
+
const hasMore = next < all.length;
|
|
15251
|
+
const nextCursor = hasMore ? encodeCursor({ src: "el", offset: next }) : null;
|
|
15252
|
+
const voices = [...fixed, ...slice];
|
|
15253
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
15254
|
+
return {
|
|
15255
|
+
voices,
|
|
15256
|
+
nextCursor,
|
|
15257
|
+
hasMore,
|
|
15258
|
+
provider: "elevenlabs",
|
|
15259
|
+
configured: true,
|
|
15260
|
+
// Only attach the error to the FIRST page response · subsequent
|
|
15261
|
+
// pages (offset > 0) won't fire if the first page errored
|
|
15262
|
+
// (voices is empty so hasMore is false), but defensive.
|
|
15263
|
+
...error && offset === 0 ? { error } : {}
|
|
15264
|
+
};
|
|
15265
|
+
}
|
|
15266
|
+
if (activeProvider === "minimax") {
|
|
15267
|
+
const all = await getMiniMaxVoicesCached(activeKey);
|
|
15268
|
+
const offset = cursor && cursor.src === "mm" ? cursor.offset ?? 0 : 0;
|
|
15269
|
+
const slice = all.slice(offset, offset + size);
|
|
15270
|
+
const next = offset + slice.length;
|
|
15271
|
+
const hasMore = next < all.length;
|
|
15272
|
+
const nextCursor = hasMore ? encodeCursor({ src: "mm", offset: next }) : null;
|
|
15273
|
+
const voices = [...fixed, ...slice];
|
|
15274
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
15275
|
+
return { voices, nextCursor, hasMore, provider: "minimax", configured: true };
|
|
15276
|
+
}
|
|
15277
|
+
return {
|
|
15278
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15279
|
+
nextCursor: null,
|
|
15280
|
+
hasMore: false,
|
|
15281
|
+
provider: activeProvider,
|
|
15282
|
+
configured: true
|
|
15283
|
+
};
|
|
15284
|
+
}
|
|
15285
|
+
async function listAvailableVoices() {
|
|
15286
|
+
const voices = [];
|
|
15287
|
+
let cursor = null;
|
|
15288
|
+
let provider = null;
|
|
15289
|
+
let configured = false;
|
|
15290
|
+
for (let i = 0; i < 50; i++) {
|
|
15291
|
+
const page = await listVoicesPage(cursor, 100);
|
|
15292
|
+
voices.push(...page.voices);
|
|
15293
|
+
provider = page.provider;
|
|
15294
|
+
configured = page.configured;
|
|
15295
|
+
if (!page.hasMore || !page.nextCursor) break;
|
|
15296
|
+
cursor = page.nextCursor;
|
|
15297
|
+
}
|
|
15298
|
+
return { voices, provider, configured };
|
|
15299
|
+
}
|
|
15135
15300
|
function voiceRows(raw, kind) {
|
|
15136
15301
|
if (!Array.isArray(raw)) return [];
|
|
15137
15302
|
const out = [];
|
|
@@ -15166,7 +15331,7 @@ function makeMiniMaxBalanceError() {
|
|
|
15166
15331
|
);
|
|
15167
15332
|
err2.code = "paid-plan-required";
|
|
15168
15333
|
err2.provider = "minimax";
|
|
15169
|
-
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/
|
|
15334
|
+
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/basic-information" : "https://platform.minimaxi.com/user-center/payment/balance";
|
|
15170
15335
|
return err2;
|
|
15171
15336
|
}
|
|
15172
15337
|
function makeElevenLabsBillingError(message) {
|
|
@@ -15193,10 +15358,15 @@ function tryExtractTtsBillingError(err2) {
|
|
|
15193
15358
|
}
|
|
15194
15359
|
return out;
|
|
15195
15360
|
}
|
|
15361
|
+
function stripSpokenLabels(text) {
|
|
15362
|
+
if (!text) return "";
|
|
15363
|
+
return text.replace(/【[^】\n]{1,40}】[ \t]*/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
15364
|
+
}
|
|
15196
15365
|
function cleanForSpeech(md) {
|
|
15197
15366
|
if (!md) return "";
|
|
15198
15367
|
let out = md;
|
|
15199
15368
|
out = out.replace(/```[\s\S]*?```/g, " ");
|
|
15369
|
+
out = out.replace(/【[^】\n]{1,40}】[ \t]*/g, " ");
|
|
15200
15370
|
out = out.replace(/`([^`\n]+)`/g, "$1");
|
|
15201
15371
|
out = out.replace(/!\[[^\]]*\]\([^)]+\)/g, " ");
|
|
15202
15372
|
out = out.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
@@ -17992,6 +18162,7 @@ var MAX_PICKS = 2;
|
|
|
17992
18162
|
async function pickChairClarifyDecision(opts) {
|
|
17993
18163
|
const prompt = latestUserPrompt(opts.history);
|
|
17994
18164
|
if (!prompt) return { shouldAsk: true, rationale: "no user prompt yet" };
|
|
18165
|
+
const isBrainstorm = (opts.mode || "").toLowerCase() === "brainstorm";
|
|
17995
18166
|
const sys = {
|
|
17996
18167
|
role: "system",
|
|
17997
18168
|
content: [
|
|
@@ -18013,6 +18184,14 @@ async function pickChairClarifyDecision(opts) {
|
|
|
18013
18184
|
"Bias toward RELEASE. A slightly-fuzzy framing is fine \u2014 directors",
|
|
18014
18185
|
"can sharpen it themselves. Asking when you don't need to kills",
|
|
18015
18186
|
"momentum.",
|
|
18187
|
+
...isBrainstorm ? [
|
|
18188
|
+
"",
|
|
18189
|
+
"BRAINSTORM MODE OVERRIDE \xB7 this room is in brainstorm mode. RELEASE",
|
|
18190
|
+
"unless the subject is literally unparseable (empty, gibberish, single",
|
|
18191
|
+
"character). Fuzzy / abstract / under-specified seeds are a FEATURE",
|
|
18192
|
+
"here \u2014 directors fill the gap with explicit assumptions, not by",
|
|
18193
|
+
"asking the user. Default ask=false in brainstorm."
|
|
18194
|
+
] : [],
|
|
18016
18195
|
"",
|
|
18017
18196
|
"Reply with STRICT JSON ONLY (no prose, no fences):",
|
|
18018
18197
|
`{ "ask": true, "rationale": "\u2264120 chars \xB7 what's load-bearingly missing" }`,
|
|
@@ -18839,47 +19018,49 @@ var SHARED_ROOM_PROTOCOL = [
|
|
|
18839
19018
|
].join("\n");
|
|
18840
19019
|
var TONE_GUIDANCE = {
|
|
18841
19020
|
brainstorm: [
|
|
18842
|
-
|
|
19021
|
+
"\u2500\u2500\u2500 \u5171\u521B\u6A21\u5F0F \xB7 BRAINSTORM \u2500\u2500\u2500",
|
|
19022
|
+
"\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",
|
|
19023
|
+
"",
|
|
19024
|
+
"\u9ED8\u8BA4\u6A21\u5F0F\uFF1A**\u53D1\u6563\u5171\u521B\u6A21\u5F0F\uFF08VALUE AMPLIFICATION\uFF09**\u3002",
|
|
19025
|
+
"",
|
|
19026
|
+
"## \u7EDD\u5BF9\u4E0D\u8981 (do NOT)",
|
|
19027
|
+
" \xB7 \u4E0D\u8981\u6025\u7740\u5224\u65AD\u5BF9\u9519\uFF1B",
|
|
19028
|
+
" \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",
|
|
19029
|
+
" \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",
|
|
19030
|
+
" \xB7 \u4E0D\u8981\u628A\u8BA8\u8BBA\u6536\u655B\u5230\u98CE\u9669\u548C\u9650\u5236\uFF1B",
|
|
19031
|
+
' \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',
|
|
19032
|
+
' \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',
|
|
18843
19033
|
"",
|
|
18844
|
-
"##
|
|
18845
|
-
"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.",
|
|
19034
|
+
"## \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",
|
|
18846
19035
|
"",
|
|
18847
|
-
"
|
|
18848
|
-
"
|
|
18849
|
-
" \xB7 <idea 2 \xB7 1\u20132 sentences \xB7 different angle, OR yes-and on a previous one>",
|
|
18850
|
-
" \xB7 <idea 3 \xB7 1\u20132 sentences \xB7 go wilder>",
|
|
18851
|
-
" \xB7 ... (3\u20136 total)",
|
|
19036
|
+
"\u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011",
|
|
19037
|
+
"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",
|
|
18852
19038
|
"",
|
|
18853
|
-
"
|
|
19039
|
+
"\u3010\u6211\u4F1A\u600E\u4E48\u653E\u5927\u3011",
|
|
19040
|
+
"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",
|
|
18854
19041
|
"",
|
|
18855
|
-
"
|
|
18856
|
-
"
|
|
18857
|
-
' \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`.',
|
|
18858
|
-
` \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.`,
|
|
19042
|
+
"\u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011",
|
|
19043
|
+
"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",
|
|
18859
19044
|
"",
|
|
18860
|
-
"
|
|
19045
|
+
"\u3010\u4E00\u4E2A\u5177\u4F53\u505A\u6CD5\u3011",
|
|
19046
|
+
"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",
|
|
18861
19047
|
"",
|
|
18862
|
-
"
|
|
18863
|
-
|
|
18864
|
-
" \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.",
|
|
18865
|
-
` \xB7 Don't preface with affirmation ("That's a good point, what if we\u2026") \u2014 just say the new idea.`,
|
|
18866
|
-
" \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.",
|
|
18867
|
-
` \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.`,
|
|
18868
|
-
` \xB7 Avoid generic innovation language: "synergy", "leverage AI", "platform play", "democratise X", "AI-native", "unlock value". They're decoration, not ideas.`,
|
|
18869
|
-
" \xB7 Avoid the same lens used by the immediately-prior speaker UNLESS you're explicitly yes-and'ing them.",
|
|
19048
|
+
"\u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011",
|
|
19049
|
+
"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",
|
|
18870
19050
|
"",
|
|
18871
|
-
"
|
|
18872
|
-
"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.",
|
|
19051
|
+
"\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",
|
|
18873
19052
|
"",
|
|
18874
|
-
"
|
|
19053
|
+
"## English-language fallback",
|
|
19054
|
+
"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.",
|
|
18875
19055
|
"",
|
|
18876
|
-
"
|
|
18877
|
-
|
|
18878
|
-
" \xB7
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
`
|
|
18882
|
-
|
|
19056
|
+
"## Light don'ts (carryovers worth keeping)",
|
|
19057
|
+
' \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',
|
|
19058
|
+
" \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",
|
|
19059
|
+
' \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',
|
|
19060
|
+
"",
|
|
19061
|
+
`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.`,
|
|
19062
|
+
"",
|
|
19063
|
+
'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.'
|
|
18883
19064
|
].join("\n"),
|
|
18884
19065
|
constructive: [
|
|
18885
19066
|
"CONSTRUCTIVE \xB7 sympathetic interrogator. You want the user to win, but only via an idea that can actually survive scrutiny.",
|
|
@@ -18984,6 +19165,23 @@ var TONE_GUIDANCE = {
|
|
|
18984
19165
|
].join("\n")
|
|
18985
19166
|
};
|
|
18986
19167
|
var CHAIR_MODE_PROTOCOL = {
|
|
19168
|
+
brainstorm: [
|
|
19169
|
+
`\u2500\u2500\u2500 CHAIR \xB7 BRAINSTORM-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
19170
|
+
`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.`,
|
|
19171
|
+
``,
|
|
19172
|
+
`**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).`,
|
|
19173
|
+
``,
|
|
19174
|
+
`**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:`,
|
|
19175
|
+
` \xB7 surface the 2\u20133 strongest unexpected VALUE angles the room opened (not the strongest objections)`,
|
|
19176
|
+
` \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)`,
|
|
19177
|
+
` \xB7 pick the most sexy / most concrete idea the room produced and re-frame it once for the user`,
|
|
19178
|
+
` \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.`,
|
|
19179
|
+
` \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.`,
|
|
19180
|
+
``,
|
|
19181
|
+
`**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.`,
|
|
19182
|
+
``,
|
|
19183
|
+
`**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.`
|
|
19184
|
+
].join("\n"),
|
|
18987
19185
|
research: [
|
|
18988
19186
|
`\u2500\u2500\u2500 CHAIR \xB7 RESEARCH-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
18989
19187
|
`This room is in research mode. Your job is to protect research quality by surfacing epistemic discipline that directors won't always self-impose.`,
|
|
@@ -19007,7 +19205,7 @@ var CHAIR_MODE_PROTOCOL = {
|
|
|
19007
19205
|
].join("\n")
|
|
19008
19206
|
};
|
|
19009
19207
|
var HOUSE_ENGAGE_BY_TONE = {
|
|
19010
|
-
brainstorm: "
|
|
19208
|
+
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",
|
|
19011
19209
|
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",
|
|
19012
19210
|
debate: "steelman the target claim before attacking it, distinguish confidence from preference, and name what would change your mind",
|
|
19013
19211
|
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",
|
|
@@ -19015,7 +19213,7 @@ var HOUSE_ENGAGE_BY_TONE = {
|
|
|
19015
19213
|
};
|
|
19016
19214
|
var HOUSE_ENGAGE_DEFAULT = HOUSE_ENGAGE_BY_TONE.debate;
|
|
19017
19215
|
var TONE_OVERRIDE_BY_TONE = {
|
|
19018
|
-
brainstorm:
|
|
19216
|
+
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.`,
|
|
19019
19217
|
constructive: "your default trained preference to be diplomatically vague. Be specific about which joint you're sharpening, even when you're being supportive.",
|
|
19020
19218
|
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.",
|
|
19021
19219
|
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.",
|
|
@@ -20622,7 +20820,8 @@ function ensureState(roomId) {
|
|
|
20622
20820
|
lastFrameBreakerAgentId: null,
|
|
20623
20821
|
billingHaltedThisTurn: false,
|
|
20624
20822
|
voiceWaiters: /* @__PURE__ */ new Map(),
|
|
20625
|
-
voicePredone: /* @__PURE__ */ new Set()
|
|
20823
|
+
voicePredone: /* @__PURE__ */ new Set(),
|
|
20824
|
+
activeMessageId: null
|
|
20626
20825
|
};
|
|
20627
20826
|
_state.set(roomId, s);
|
|
20628
20827
|
}
|
|
@@ -20749,6 +20948,7 @@ async function chairInterrupt(roomId) {
|
|
|
20749
20948
|
}
|
|
20750
20949
|
state.preWarmed = null;
|
|
20751
20950
|
}
|
|
20951
|
+
state.activeMessageId = null;
|
|
20752
20952
|
if (interruptedAgentId) {
|
|
20753
20953
|
const recent = listRecentMessages(roomId, 8);
|
|
20754
20954
|
for (let i = recent.length - 1; i >= 0; i--) {
|
|
@@ -20884,7 +21084,8 @@ function emitQueueUpdate(roomId, s) {
|
|
|
20884
21084
|
round: {
|
|
20885
21085
|
spoken: s.speakersThisTurn,
|
|
20886
21086
|
total: s.maxSpeakersThisTurn
|
|
20887
|
-
}
|
|
21087
|
+
},
|
|
21088
|
+
activeMessageId: s.activeMessageId
|
|
20888
21089
|
};
|
|
20889
21090
|
roomBus.emit(roomId, update);
|
|
20890
21091
|
}
|
|
@@ -20915,6 +21116,7 @@ function tickRoom(roomId, opts) {
|
|
|
20915
21116
|
}
|
|
20916
21117
|
state.preWarmed = null;
|
|
20917
21118
|
}
|
|
21119
|
+
state.activeMessageId = null;
|
|
20918
21120
|
for (const [, waiter] of state.voiceWaiters) {
|
|
20919
21121
|
waiter.resolve();
|
|
20920
21122
|
}
|
|
@@ -21030,6 +21232,21 @@ async function runPickerThenPrewarm(roomId, _currentMessageId) {
|
|
|
21030
21232
|
state.inflight.delete(sentinel);
|
|
21031
21233
|
state.inflight.set(info.messageId, ac);
|
|
21032
21234
|
}
|
|
21235
|
+
if (state.preWarmed !== preWarmed && state.activeMessageId === null) {
|
|
21236
|
+
state.activeMessageId = info.messageId;
|
|
21237
|
+
const m = getMessage(info.messageId);
|
|
21238
|
+
if (m) {
|
|
21239
|
+
const newMeta = { ...m.meta || {}, preWarmed: false };
|
|
21240
|
+
updateMessageBody(info.messageId, m.body, newMeta);
|
|
21241
|
+
roomBus.emit(roomId, {
|
|
21242
|
+
type: "message-updated",
|
|
21243
|
+
messageId: info.messageId,
|
|
21244
|
+
body: m.body,
|
|
21245
|
+
meta: newMeta
|
|
21246
|
+
});
|
|
21247
|
+
}
|
|
21248
|
+
emitQueueUpdate(roomId, state);
|
|
21249
|
+
}
|
|
21033
21250
|
}
|
|
21034
21251
|
// Chain trigger lives in pumpQueue's consume point, NOT here.
|
|
21035
21252
|
// Rationale: B's `message-final` fires while B is still occupying
|
|
@@ -21264,9 +21481,25 @@ async function pumpQueue(roomId) {
|
|
|
21264
21481
|
ac = state.preWarmed.abortController;
|
|
21265
21482
|
streamPromise = state.preWarmed.promise;
|
|
21266
21483
|
state.preWarmed = null;
|
|
21484
|
+
if (justConsumed.messageId) {
|
|
21485
|
+
state.activeMessageId = justConsumed.messageId;
|
|
21486
|
+
const m = getMessage(justConsumed.messageId);
|
|
21487
|
+
if (m) {
|
|
21488
|
+
const newMeta = { ...m.meta || {}, preWarmed: false };
|
|
21489
|
+
updateMessageBody(justConsumed.messageId, m.body, newMeta);
|
|
21490
|
+
roomBus.emit(roomId, {
|
|
21491
|
+
type: "message-updated",
|
|
21492
|
+
messageId: justConsumed.messageId,
|
|
21493
|
+
body: m.body,
|
|
21494
|
+
meta: newMeta
|
|
21495
|
+
});
|
|
21496
|
+
}
|
|
21497
|
+
emitQueueUpdate(roomId, state);
|
|
21498
|
+
}
|
|
21267
21499
|
rlog(roomId, "speaker-prewarm-consumed", {
|
|
21268
21500
|
agent: speaker.name,
|
|
21269
|
-
agentId: speaker.id
|
|
21501
|
+
agentId: speaker.id,
|
|
21502
|
+
messageId: justConsumed.messageId || "(pending)"
|
|
21270
21503
|
});
|
|
21271
21504
|
schedulePreWarm(roomId, justConsumed.messageId);
|
|
21272
21505
|
} else {
|
|
@@ -21291,6 +21524,8 @@ async function pumpQueue(roomId) {
|
|
|
21291
21524
|
state.inflight.delete(sentinel);
|
|
21292
21525
|
state.inflight.set(info.messageId, ac);
|
|
21293
21526
|
}
|
|
21527
|
+
state.activeMessageId = info.messageId;
|
|
21528
|
+
emitQueueUpdate(roomId, state);
|
|
21294
21529
|
},
|
|
21295
21530
|
onMessageFinal: (info) => {
|
|
21296
21531
|
schedulePreWarm(roomId, info.messageId);
|
|
@@ -21334,6 +21569,10 @@ async function pumpQueue(roomId) {
|
|
|
21334
21569
|
if (val === ac) keysToDel.push(key);
|
|
21335
21570
|
}
|
|
21336
21571
|
for (const key of keysToDel) state.inflight.delete(key);
|
|
21572
|
+
if (state.activeMessageId) {
|
|
21573
|
+
state.activeMessageId = null;
|
|
21574
|
+
emitQueueUpdate(roomId, state);
|
|
21575
|
+
}
|
|
21337
21576
|
}
|
|
21338
21577
|
if (state.queue[0] !== entry) {
|
|
21339
21578
|
continue;
|
|
@@ -21426,11 +21665,14 @@ async function pumpQueue(roomId) {
|
|
|
21426
21665
|
});
|
|
21427
21666
|
if (reachedCap) {
|
|
21428
21667
|
const room = getRoom(roomId);
|
|
21429
|
-
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify &&
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21668
|
+
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify && room.voteTrigger === "manual") {
|
|
21669
|
+
const nextRound = nextUserRoundNum(roomId);
|
|
21670
|
+
rlog(roomId, "manual-auto-continue", {
|
|
21671
|
+
fromRound: state.roundNum,
|
|
21672
|
+
toRound: nextRound
|
|
21673
|
+
});
|
|
21674
|
+
tickRoom(roomId, { roundNum: nextRound, kind: "continue" });
|
|
21675
|
+
} else if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify) {
|
|
21434
21676
|
const wrappedRound = state.roundNum;
|
|
21435
21677
|
emitChairPending(roomId, "vote-summary");
|
|
21436
21678
|
let recommendation;
|
|
@@ -21716,9 +21958,11 @@ async function streamSpeakerTurn(args) {
|
|
|
21716
21958
|
}
|
|
21717
21959
|
async function emitVoiceText(text) {
|
|
21718
21960
|
if (!voiceMode || !text.trim()) return;
|
|
21961
|
+
const spoken = stripSpokenLabels(text);
|
|
21962
|
+
if (!spoken) return;
|
|
21719
21963
|
const voiceProfile = currentVoiceProfile();
|
|
21720
21964
|
if (!voiceProfile) return;
|
|
21721
|
-
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${
|
|
21965
|
+
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${spoken.length} text="${spoken.slice(0, 50)}"
|
|
21722
21966
|
`);
|
|
21723
21967
|
const MAX_ATTEMPTS = 2;
|
|
21724
21968
|
const TIMEOUT_MS = 3e4;
|
|
@@ -21731,7 +21975,7 @@ async function streamSpeakerTurn(args) {
|
|
|
21731
21975
|
let chunkCount = 0;
|
|
21732
21976
|
let failure = null;
|
|
21733
21977
|
try {
|
|
21734
|
-
for await (const chunk of synthesizeSpeechStream(
|
|
21978
|
+
for await (const chunk of synthesizeSpeechStream(spoken, voiceProfile, timeoutCtrl.signal)) {
|
|
21735
21979
|
if (signal.aborted) break;
|
|
21736
21980
|
chunkCount++;
|
|
21737
21981
|
roomBus.emit(roomId, {
|
|
@@ -21961,6 +22205,13 @@ async function streamSpeakerTurn(args) {
|
|
|
21961
22205
|
if (tail) await emitVoiceText(tail);
|
|
21962
22206
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
21963
22207
|
}
|
|
22208
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22209
|
+
process.stderr.write(
|
|
22210
|
+
`[tts] zero-chunks for msg=${placeholder.id.slice(0, 8)} agent=${speaker.name} \xB7 short-circuiting voice wait
|
|
22211
|
+
`
|
|
22212
|
+
);
|
|
22213
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22214
|
+
}
|
|
21964
22215
|
updateMessageBody(placeholder.id, buf, {
|
|
21965
22216
|
...placeholderMeta,
|
|
21966
22217
|
speakerStatus: "final",
|
|
@@ -22016,6 +22267,9 @@ async function streamSpeakerTurn(args) {
|
|
|
22016
22267
|
if (voiceChunker) {
|
|
22017
22268
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
22018
22269
|
}
|
|
22270
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22271
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22272
|
+
}
|
|
22019
22273
|
roomBus.emit(roomId, {
|
|
22020
22274
|
type: "message-final",
|
|
22021
22275
|
messageId: placeholder.id,
|
|
@@ -22686,8 +22940,9 @@ async function runChairClarify(roomId) {
|
|
|
22686
22940
|
emitChairPending(roomId, "clarify-deciding");
|
|
22687
22941
|
let decision = null;
|
|
22688
22942
|
try {
|
|
22943
|
+
const roomForMode = getRoom(roomId);
|
|
22689
22944
|
decision = await withTimeout(
|
|
22690
|
-
pickChairClarifyDecision({ history }),
|
|
22945
|
+
pickChairClarifyDecision({ history, mode: roomForMode?.mode }),
|
|
22691
22946
|
15e3,
|
|
22692
22947
|
"chair-clarify-decision"
|
|
22693
22948
|
);
|
|
@@ -25393,6 +25648,25 @@ function ttsCacheSet(key, val) {
|
|
|
25393
25648
|
function voicesRouter() {
|
|
25394
25649
|
const r = new Hono14();
|
|
25395
25650
|
r.get("/", async (c) => {
|
|
25651
|
+
const url = new URL(c.req.url);
|
|
25652
|
+
const cursor = url.searchParams.get("cursor");
|
|
25653
|
+
const pageSizeRaw = url.searchParams.get("pageSize");
|
|
25654
|
+
if (cursor !== null || pageSizeRaw !== null) {
|
|
25655
|
+
const pageSize = pageSizeRaw ? Math.max(1, Number.parseInt(pageSizeRaw, 10) || 30) : 30;
|
|
25656
|
+
const page = await listVoicesPage(cursor, pageSize);
|
|
25657
|
+
return c.json({
|
|
25658
|
+
voices: page.voices,
|
|
25659
|
+
nextCursor: page.nextCursor,
|
|
25660
|
+
hasMore: page.hasMore,
|
|
25661
|
+
provider: page.provider,
|
|
25662
|
+
configured: page.configured,
|
|
25663
|
+
// Structured upstream error · the picker uses this to render
|
|
25664
|
+
// a clear "your API key is missing voices_read permission"
|
|
25665
|
+
// banner + a link to the ElevenLabs API-key settings page
|
|
25666
|
+
// instead of a silently empty dropdown.
|
|
25667
|
+
...page.error ? { error: page.error } : {}
|
|
25668
|
+
});
|
|
25669
|
+
}
|
|
25396
25670
|
const catalog = await listAvailableVoices();
|
|
25397
25671
|
return c.json({
|
|
25398
25672
|
voices: catalog.voices,
|
|
@@ -25539,7 +25813,7 @@ function voicesRouter() {
|
|
|
25539
25813
|
init_paths();
|
|
25540
25814
|
|
|
25541
25815
|
// src/version.ts
|
|
25542
|
-
var VERSION = "0.1.
|
|
25816
|
+
var VERSION = "0.1.37";
|
|
25543
25817
|
|
|
25544
25818
|
// src/utils/render-picker-catalog.ts
|
|
25545
25819
|
function renderPickerCatalog() {
|