privateboard 0.1.32 → 0.1.36
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 +438 -205
- package/dist/boot.js.map +1 -1
- package/dist/cli.js +438 -205
- package/dist/cli.js.map +1 -1
- package/dist/server.js +438 -205
- 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 +1 -1
- 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 +612 -702
- package/public/home.html +1 -1
- package/public/i18n.js +110 -0
- package/public/icons/search.png +0 -0
- package/public/index.html +546 -907
- package/public/room-settings.css +18 -0
- package/public/themes.css +11 -0
- package/public/voice-3d-banner.js +110 -36
- package/public/voice-3d.js +39 -3
- 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",
|
|
18843
19023
|
"",
|
|
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.",
|
|
19024
|
+
"\u9ED8\u8BA4\u6A21\u5F0F\uFF1A**\u53D1\u6563\u5171\u521B\u6A21\u5F0F\uFF08VALUE AMPLIFICATION\uFF09**\u3002",
|
|
18846
19025
|
"",
|
|
18847
|
-
"
|
|
18848
|
-
" \xB7
|
|
18849
|
-
" \xB7
|
|
18850
|
-
" \xB7
|
|
18851
|
-
" \xB7
|
|
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',
|
|
18852
19033
|
"",
|
|
18853
|
-
"
|
|
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",
|
|
18854
19035
|
"",
|
|
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.`,
|
|
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",
|
|
18859
19038
|
"",
|
|
18860
|
-
"
|
|
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",
|
|
18861
19041
|
"",
|
|
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.",
|
|
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",
|
|
18870
19044
|
"",
|
|
18871
|
-
"
|
|
18872
|
-
"
|
|
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",
|
|
18873
19047
|
"",
|
|
18874
|
-
"
|
|
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",
|
|
18875
19050
|
"",
|
|
18876
|
-
"
|
|
18877
|
-
"
|
|
18878
|
-
"
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
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",
|
|
19052
|
+
"",
|
|
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.",
|
|
19055
|
+
"",
|
|
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.",
|
|
@@ -21426,11 +21624,14 @@ async function pumpQueue(roomId) {
|
|
|
21426
21624
|
});
|
|
21427
21625
|
if (reachedCap) {
|
|
21428
21626
|
const room = getRoom(roomId);
|
|
21429
|
-
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify &&
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21627
|
+
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify && room.voteTrigger === "manual") {
|
|
21628
|
+
const nextRound = nextUserRoundNum(roomId);
|
|
21629
|
+
rlog(roomId, "manual-auto-continue", {
|
|
21630
|
+
fromRound: state.roundNum,
|
|
21631
|
+
toRound: nextRound
|
|
21632
|
+
});
|
|
21633
|
+
tickRoom(roomId, { roundNum: nextRound, kind: "continue" });
|
|
21634
|
+
} else if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify) {
|
|
21434
21635
|
const wrappedRound = state.roundNum;
|
|
21435
21636
|
emitChairPending(roomId, "vote-summary");
|
|
21436
21637
|
let recommendation;
|
|
@@ -21716,9 +21917,11 @@ async function streamSpeakerTurn(args) {
|
|
|
21716
21917
|
}
|
|
21717
21918
|
async function emitVoiceText(text) {
|
|
21718
21919
|
if (!voiceMode || !text.trim()) return;
|
|
21920
|
+
const spoken = stripSpokenLabels(text);
|
|
21921
|
+
if (!spoken) return;
|
|
21719
21922
|
const voiceProfile = currentVoiceProfile();
|
|
21720
21923
|
if (!voiceProfile) return;
|
|
21721
|
-
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${
|
|
21924
|
+
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${spoken.length} text="${spoken.slice(0, 50)}"
|
|
21722
21925
|
`);
|
|
21723
21926
|
const MAX_ATTEMPTS = 2;
|
|
21724
21927
|
const TIMEOUT_MS = 3e4;
|
|
@@ -21731,7 +21934,7 @@ async function streamSpeakerTurn(args) {
|
|
|
21731
21934
|
let chunkCount = 0;
|
|
21732
21935
|
let failure = null;
|
|
21733
21936
|
try {
|
|
21734
|
-
for await (const chunk of synthesizeSpeechStream(
|
|
21937
|
+
for await (const chunk of synthesizeSpeechStream(spoken, voiceProfile, timeoutCtrl.signal)) {
|
|
21735
21938
|
if (signal.aborted) break;
|
|
21736
21939
|
chunkCount++;
|
|
21737
21940
|
roomBus.emit(roomId, {
|
|
@@ -21961,6 +22164,13 @@ async function streamSpeakerTurn(args) {
|
|
|
21961
22164
|
if (tail) await emitVoiceText(tail);
|
|
21962
22165
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
21963
22166
|
}
|
|
22167
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22168
|
+
process.stderr.write(
|
|
22169
|
+
`[tts] zero-chunks for msg=${placeholder.id.slice(0, 8)} agent=${speaker.name} \xB7 short-circuiting voice wait
|
|
22170
|
+
`
|
|
22171
|
+
);
|
|
22172
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22173
|
+
}
|
|
21964
22174
|
updateMessageBody(placeholder.id, buf, {
|
|
21965
22175
|
...placeholderMeta,
|
|
21966
22176
|
speakerStatus: "final",
|
|
@@ -22016,6 +22226,9 @@ async function streamSpeakerTurn(args) {
|
|
|
22016
22226
|
if (voiceChunker) {
|
|
22017
22227
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
22018
22228
|
}
|
|
22229
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22230
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22231
|
+
}
|
|
22019
22232
|
roomBus.emit(roomId, {
|
|
22020
22233
|
type: "message-final",
|
|
22021
22234
|
messageId: placeholder.id,
|
|
@@ -22686,8 +22899,9 @@ async function runChairClarify(roomId) {
|
|
|
22686
22899
|
emitChairPending(roomId, "clarify-deciding");
|
|
22687
22900
|
let decision = null;
|
|
22688
22901
|
try {
|
|
22902
|
+
const roomForMode = getRoom(roomId);
|
|
22689
22903
|
decision = await withTimeout(
|
|
22690
|
-
pickChairClarifyDecision({ history }),
|
|
22904
|
+
pickChairClarifyDecision({ history, mode: roomForMode?.mode }),
|
|
22691
22905
|
15e3,
|
|
22692
22906
|
"chair-clarify-decision"
|
|
22693
22907
|
);
|
|
@@ -25393,6 +25607,25 @@ function ttsCacheSet(key, val) {
|
|
|
25393
25607
|
function voicesRouter() {
|
|
25394
25608
|
const r = new Hono14();
|
|
25395
25609
|
r.get("/", async (c) => {
|
|
25610
|
+
const url = new URL(c.req.url);
|
|
25611
|
+
const cursor = url.searchParams.get("cursor");
|
|
25612
|
+
const pageSizeRaw = url.searchParams.get("pageSize");
|
|
25613
|
+
if (cursor !== null || pageSizeRaw !== null) {
|
|
25614
|
+
const pageSize = pageSizeRaw ? Math.max(1, Number.parseInt(pageSizeRaw, 10) || 30) : 30;
|
|
25615
|
+
const page = await listVoicesPage(cursor, pageSize);
|
|
25616
|
+
return c.json({
|
|
25617
|
+
voices: page.voices,
|
|
25618
|
+
nextCursor: page.nextCursor,
|
|
25619
|
+
hasMore: page.hasMore,
|
|
25620
|
+
provider: page.provider,
|
|
25621
|
+
configured: page.configured,
|
|
25622
|
+
// Structured upstream error · the picker uses this to render
|
|
25623
|
+
// a clear "your API key is missing voices_read permission"
|
|
25624
|
+
// banner + a link to the ElevenLabs API-key settings page
|
|
25625
|
+
// instead of a silently empty dropdown.
|
|
25626
|
+
...page.error ? { error: page.error } : {}
|
|
25627
|
+
});
|
|
25628
|
+
}
|
|
25396
25629
|
const catalog = await listAvailableVoices();
|
|
25397
25630
|
return c.json({
|
|
25398
25631
|
voices: catalog.voices,
|
|
@@ -25539,7 +25772,7 @@ function voicesRouter() {
|
|
|
25539
25772
|
init_paths();
|
|
25540
25773
|
|
|
25541
25774
|
// src/version.ts
|
|
25542
|
-
var VERSION = "0.1.
|
|
25775
|
+
var VERSION = "0.1.36";
|
|
25543
25776
|
|
|
25544
25777
|
// src/utils/render-picker-catalog.ts
|
|
25545
25778
|
function renderPickerCatalog() {
|