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/cli.js
CHANGED
|
@@ -2640,7 +2640,13 @@ var MODELS = {
|
|
|
2640
2640
|
baiId: "claude-opus-4.7",
|
|
2641
2641
|
displayName: "Opus 4.7",
|
|
2642
2642
|
contextBudget: 2e5,
|
|
2643
|
-
deck: "deep reasoning"
|
|
2643
|
+
deck: "deep reasoning",
|
|
2644
|
+
// Anthropic dropped `temperature` for the 4.7 family · sending it
|
|
2645
|
+
// returns HTTP 400 "temperature is deprecated for this model"
|
|
2646
|
+
// across every carrier (direct / OR / B.AI all proxy to the same
|
|
2647
|
+
// upstream). The adapter omits temperature for any model with
|
|
2648
|
+
// this flag.
|
|
2649
|
+
noTemperature: true
|
|
2644
2650
|
},
|
|
2645
2651
|
"opus-4-6-fast": {
|
|
2646
2652
|
v: "opus-4-6-fast",
|
|
@@ -2852,6 +2858,16 @@ function getModel(v) {
|
|
|
2852
2858
|
function isModelV(v) {
|
|
2853
2859
|
return Object.hasOwn(MODELS, v);
|
|
2854
2860
|
}
|
|
2861
|
+
function noTemperatureModelIds() {
|
|
2862
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2863
|
+
for (const m of Object.values(MODELS)) {
|
|
2864
|
+
if (!m.noTemperature) continue;
|
|
2865
|
+
if (m.directApiId) ids.add(m.directApiId);
|
|
2866
|
+
if (m.openrouterId) ids.add(m.openrouterId);
|
|
2867
|
+
if (m.baiId) ids.add(m.baiId);
|
|
2868
|
+
}
|
|
2869
|
+
return ids;
|
|
2870
|
+
}
|
|
2855
2871
|
|
|
2856
2872
|
// src/routes/agents.ts
|
|
2857
2873
|
init_persona_jobs();
|
|
@@ -4044,21 +4060,47 @@ function redactHeaderValue(name, value) {
|
|
|
4044
4060
|
const tail = v.slice(-4);
|
|
4045
4061
|
return tail ? `****${tail}` : "****";
|
|
4046
4062
|
}
|
|
4063
|
+
var NO_TEMP_IDS_CACHE = null;
|
|
4064
|
+
function noTempIds() {
|
|
4065
|
+
if (!NO_TEMP_IDS_CACHE) NO_TEMP_IDS_CACHE = noTemperatureModelIds();
|
|
4066
|
+
return NO_TEMP_IDS_CACHE;
|
|
4067
|
+
}
|
|
4068
|
+
function stripTemperatureForNoTempModels(rawBody) {
|
|
4069
|
+
try {
|
|
4070
|
+
const parsed = JSON.parse(rawBody);
|
|
4071
|
+
const modelId = typeof parsed.model === "string" ? parsed.model : null;
|
|
4072
|
+
if (modelId && noTempIds().has(modelId) && "temperature" in parsed) {
|
|
4073
|
+
delete parsed.temperature;
|
|
4074
|
+
return { body: JSON.stringify(parsed), stripped: true };
|
|
4075
|
+
}
|
|
4076
|
+
} catch {
|
|
4077
|
+
}
|
|
4078
|
+
return { body: rawBody, stripped: false };
|
|
4079
|
+
}
|
|
4047
4080
|
function makeLoggedFetch(tag) {
|
|
4048
4081
|
return function loggedFetch2(input, init) {
|
|
4082
|
+
let effectiveInit = init;
|
|
4083
|
+
let stripNote = "";
|
|
4084
|
+
if (init?.body && typeof init.body === "string") {
|
|
4085
|
+
const r = stripTemperatureForNoTempModels(init.body);
|
|
4086
|
+
if (r.stripped) {
|
|
4087
|
+
effectiveInit = { ...init, body: r.body };
|
|
4088
|
+
stripNote = ` \xB7 stripped temperature (noTemperature model)`;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4049
4091
|
const url = typeof input === "string" || input instanceof URL ? String(input) : input.url;
|
|
4050
|
-
const method = (
|
|
4051
|
-
const headers = new Headers(
|
|
4092
|
+
const method = (effectiveInit?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
4093
|
+
const headers = new Headers(effectiveInit?.headers ?? (input instanceof Request ? input.headers : void 0));
|
|
4052
4094
|
const headerLines = [];
|
|
4053
4095
|
headers.forEach((v, k) => {
|
|
4054
4096
|
headerLines.push(` ${k}: ${redactHeaderValue(k, v)}`);
|
|
4055
4097
|
});
|
|
4056
4098
|
let bodyPretty = "";
|
|
4057
|
-
if (
|
|
4099
|
+
if (effectiveInit?.body && typeof effectiveInit.body === "string") {
|
|
4058
4100
|
try {
|
|
4059
|
-
bodyPretty = JSON.stringify(JSON.parse(
|
|
4101
|
+
bodyPretty = JSON.stringify(JSON.parse(effectiveInit.body), null, 2);
|
|
4060
4102
|
} catch {
|
|
4061
|
-
bodyPretty =
|
|
4103
|
+
bodyPretty = effectiveInit.body.length > 2e3 ? effectiveInit.body.slice(0, 2e3) + "\u2026" : effectiveInit.body;
|
|
4062
4104
|
}
|
|
4063
4105
|
}
|
|
4064
4106
|
const sep = "\u2500".repeat(60);
|
|
@@ -4071,14 +4113,14 @@ function makeLoggedFetch(tag) {
|
|
|
4071
4113
|
process.stderr.write(
|
|
4072
4114
|
`
|
|
4073
4115
|
\u250C${sep}
|
|
4074
|
-
\u2502 [${tag} \u2192] ${method} ${url}
|
|
4116
|
+
\u2502 [${tag} \u2192] ${method} ${url}${stripNote}
|
|
4075
4117
|
` + headerBlock + `
|
|
4076
4118
|
` + bodyBlock + `
|
|
4077
4119
|
\u2514${sep}
|
|
4078
4120
|
`
|
|
4079
4121
|
);
|
|
4080
4122
|
const t0 = Date.now();
|
|
4081
|
-
return fetch(input,
|
|
4123
|
+
return fetch(input, effectiveInit).then(async (res) => {
|
|
4082
4124
|
const ms = Date.now() - t0;
|
|
4083
4125
|
const resHeaderLines = [];
|
|
4084
4126
|
res.headers.forEach((v, k) => resHeaderLines.push(` ${k}: ${v}`));
|
|
@@ -4339,6 +4381,7 @@ async function* callLLMStream(req) {
|
|
|
4339
4381
|
yield { type: "error", message: formatStreamError(e) };
|
|
4340
4382
|
return;
|
|
4341
4383
|
}
|
|
4384
|
+
const temperature = getModel(req.modelV).noTemperature ? void 0 : req.temperature;
|
|
4342
4385
|
let attempt = 0;
|
|
4343
4386
|
let lastTransientMessage = "";
|
|
4344
4387
|
let yieldedText = false;
|
|
@@ -4364,7 +4407,7 @@ async function* callLLMStream(req) {
|
|
|
4364
4407
|
model: resolved.model,
|
|
4365
4408
|
providerOptions: resolved.providerOptions,
|
|
4366
4409
|
messages: req.messages,
|
|
4367
|
-
temperature
|
|
4410
|
+
temperature,
|
|
4368
4411
|
// Vercel SDK names this maxOutputTokens in v4+; tolerate both.
|
|
4369
4412
|
maxTokens: req.maxTokens,
|
|
4370
4413
|
abortSignal: req.signal
|
|
@@ -7991,8 +8034,11 @@ var CLUSTER_MAX_SIZE = 60;
|
|
|
7991
8034
|
var PROMOTE_MIN_PROVENANCE = 3;
|
|
7992
8035
|
var PROMOTE_MIN_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7993
8036
|
var PROMOTE_MIN_CONFIDENCE = 0.6;
|
|
7994
|
-
var USER_LONG_HARVEST_MIN_LONG =
|
|
7995
|
-
var USER_LONG_HARVEST_MIN_NEW_PROMOTED =
|
|
8037
|
+
var USER_LONG_HARVEST_MIN_LONG = 2;
|
|
8038
|
+
var USER_LONG_HARVEST_MIN_NEW_PROMOTED = 1;
|
|
8039
|
+
var USER_LONG_HARVEST_MIN_SHORT_HIGH = 6;
|
|
8040
|
+
var USER_LONG_SHORT_CONF_FLOOR = 0.6;
|
|
8041
|
+
var USER_LONG_HARVEST_INPUT_CAP = 40;
|
|
7996
8042
|
var USER_LONG_CAP = 30;
|
|
7997
8043
|
async function runDreamCycle(agentId, config = {}) {
|
|
7998
8044
|
const startedAt = Date.now();
|
|
@@ -8103,13 +8149,18 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
8103
8149
|
if (!config.skipLLM && utility && agent?.roleKind === "moderator") {
|
|
8104
8150
|
try {
|
|
8105
8151
|
const chairLong = listTierForAgent(agentId, "long");
|
|
8106
|
-
const
|
|
8152
|
+
const chairShortHigh = listTierForAgent(agentId, "short").filter((m) => m.confidence >= USER_LONG_SHORT_CONF_FLOOR && !m.pinned);
|
|
8153
|
+
const eligible = chairLong.length >= USER_LONG_HARVEST_MIN_LONG || promoted >= USER_LONG_HARVEST_MIN_NEW_PROMOTED || chairShortHigh.length >= USER_LONG_HARVEST_MIN_SHORT_HIGH;
|
|
8107
8154
|
if (eligible) {
|
|
8108
8155
|
const existing = listActiveUserLongMemory();
|
|
8156
|
+
const pool = [
|
|
8157
|
+
...chairLong,
|
|
8158
|
+
...chairShortHigh.slice().sort((a, b) => b.confidence - a.confidence)
|
|
8159
|
+
].slice(0, USER_LONG_HARVEST_INPUT_CAP);
|
|
8109
8160
|
const harvest = await harvestUserLongMemory({
|
|
8110
8161
|
modelV: utility,
|
|
8111
8162
|
userName,
|
|
8112
|
-
chairLong,
|
|
8163
|
+
chairLong: pool,
|
|
8113
8164
|
existing
|
|
8114
8165
|
});
|
|
8115
8166
|
for (const t of harvest.newTags) {
|
|
@@ -8198,14 +8249,14 @@ async function runDreamCycle(agentId, config = {}) {
|
|
|
8198
8249
|
var HARVEST_EMPTY = { newTags: [], reinforce: [], supersede: [] };
|
|
8199
8250
|
function buildHarvestPrompt(opts) {
|
|
8200
8251
|
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");
|
|
8201
|
-
const chairBlock = opts.chairLong.length === 0 ? "(no
|
|
8252
|
+
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");
|
|
8202
8253
|
return [
|
|
8203
|
-
`You are reviewing the chair's
|
|
8254
|
+
`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).`,
|
|
8204
8255
|
``,
|
|
8205
8256
|
`## Existing user-long-memory tags`,
|
|
8206
8257
|
existingBlock,
|
|
8207
8258
|
``,
|
|
8208
|
-
`## Chair
|
|
8259
|
+
`## Chair memories about ${opts.userName} (mixed pool \xB7 prefer long-tier or high-confidence short-tier entries when proposing tags)`,
|
|
8209
8260
|
chairBlock,
|
|
8210
8261
|
``,
|
|
8211
8262
|
`## Output`,
|
|
@@ -14968,161 +15019,117 @@ function listConfiguredVoices() {
|
|
|
14968
15019
|
});
|
|
14969
15020
|
return out;
|
|
14970
15021
|
}
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
15022
|
+
function encodeCursor(c) {
|
|
15023
|
+
return Buffer.from(JSON.stringify(c), "utf8").toString("base64url");
|
|
15024
|
+
}
|
|
15025
|
+
function decodeCursor(s) {
|
|
15026
|
+
if (!s) return null;
|
|
15027
|
+
try {
|
|
15028
|
+
const obj = JSON.parse(Buffer.from(s, "base64url").toString("utf8"));
|
|
15029
|
+
if (obj && (obj.src === "el" || obj.src === "mm")) return obj;
|
|
15030
|
+
} catch {
|
|
14976
15031
|
}
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
15032
|
+
return null;
|
|
15033
|
+
}
|
|
15034
|
+
function classifyElevenLabsError(status, body) {
|
|
15035
|
+
let parsed = null;
|
|
15036
|
+
try {
|
|
15037
|
+
parsed = JSON.parse(body);
|
|
15038
|
+
} catch {
|
|
14980
15039
|
}
|
|
14981
|
-
|
|
15040
|
+
const detail = parsed?.detail;
|
|
15041
|
+
const upstreamStatus = typeof detail?.status === "string" ? detail.status : "";
|
|
15042
|
+
const upstreamMessage = typeof detail?.message === "string" ? detail.message : body.slice(0, 200);
|
|
15043
|
+
if (status === 401 && upstreamStatus === "missing_permissions") {
|
|
15044
|
+
return {
|
|
15045
|
+
code: "missing_permissions",
|
|
15046
|
+
provider: "elevenlabs",
|
|
15047
|
+
message: upstreamMessage,
|
|
15048
|
+
// Direct link to the API-key management page · "Update key
|
|
15049
|
+
// permissions" is what the user needs to do, and ElevenLabs's
|
|
15050
|
+
// settings page surfaces the scope checkboxes prominently.
|
|
15051
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
15052
|
+
};
|
|
15053
|
+
}
|
|
15054
|
+
if (status === 401 || status === 403) {
|
|
15055
|
+
return {
|
|
15056
|
+
code: "auth_failed",
|
|
15057
|
+
provider: "elevenlabs",
|
|
15058
|
+
message: upstreamMessage,
|
|
15059
|
+
fixUrl: "https://elevenlabs.io/app/settings/api-keys"
|
|
15060
|
+
};
|
|
15061
|
+
}
|
|
15062
|
+
if (status === 429) {
|
|
15063
|
+
return {
|
|
15064
|
+
code: "rate_limited",
|
|
15065
|
+
provider: "elevenlabs",
|
|
15066
|
+
message: upstreamMessage
|
|
15067
|
+
};
|
|
15068
|
+
}
|
|
15069
|
+
return {
|
|
15070
|
+
code: "fetch_failed",
|
|
15071
|
+
provider: "elevenlabs",
|
|
15072
|
+
message: `HTTP ${status}: ${upstreamMessage}`
|
|
15073
|
+
};
|
|
15074
|
+
}
|
|
15075
|
+
async function fetchAllElevenLabsV2Voices(apiKey) {
|
|
15076
|
+
const out = [];
|
|
15077
|
+
let token = null;
|
|
15078
|
+
let lastError = null;
|
|
15079
|
+
for (let i = 0; i < 20; i++) {
|
|
15080
|
+
const url = new URL("https://api.elevenlabs.io/v2/voices");
|
|
15081
|
+
url.searchParams.set("page_size", "100");
|
|
15082
|
+
if (token) url.searchParams.set("next_page_token", token);
|
|
14982
15083
|
try {
|
|
14983
|
-
const res = await fetch(
|
|
14984
|
-
|
|
14985
|
-
headers: {
|
|
14986
|
-
"authorization": `Bearer ${activeKey}`,
|
|
14987
|
-
"content-type": "application/json"
|
|
14988
|
-
},
|
|
14989
|
-
body: JSON.stringify({ voice_type: "all" })
|
|
15084
|
+
const res = await fetch(url.toString(), {
|
|
15085
|
+
headers: { "xi-api-key": apiKey }
|
|
14990
15086
|
});
|
|
14991
|
-
if (res.ok) {
|
|
14992
|
-
const
|
|
14993
|
-
|
|
14994
|
-
|
|
14995
|
-
...voiceRows(json.voice_cloning, "clone"),
|
|
14996
|
-
...voiceRows(json.voice_generation, "generated")
|
|
14997
|
-
];
|
|
14998
|
-
if (rows.length > 0) {
|
|
14999
|
-
const nonMiniMax = voices.filter((v) => v.provider !== "minimax");
|
|
15000
|
-
voices = [
|
|
15001
|
-
...nonMiniMax,
|
|
15002
|
-
...rows.map((r) => ({
|
|
15003
|
-
provider: "minimax",
|
|
15004
|
-
model: "speech-2.8-hd",
|
|
15005
|
-
voiceId: r.voiceId,
|
|
15006
|
-
label: r.label,
|
|
15007
|
-
language: r.kind,
|
|
15008
|
-
configured: true
|
|
15009
|
-
}))
|
|
15010
|
-
];
|
|
15011
|
-
}
|
|
15012
|
-
}
|
|
15013
|
-
} catch {
|
|
15014
|
-
}
|
|
15015
|
-
return { voices, provider: "minimax", configured: true };
|
|
15016
|
-
}
|
|
15017
|
-
if (activeProvider === "elevenlabs") {
|
|
15018
|
-
const personal = [];
|
|
15019
|
-
const shared = [];
|
|
15020
|
-
await Promise.all([
|
|
15021
|
-
(async () => {
|
|
15022
|
-
try {
|
|
15023
|
-
const res = await fetch(
|
|
15024
|
-
"https://api.elevenlabs.io/v1/voices?show_legacy=true&include_total_count=true",
|
|
15025
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
15026
|
-
);
|
|
15027
|
-
if (!res.ok) {
|
|
15028
|
-
const errText = await res.text();
|
|
15029
|
-
process.stderr.write(
|
|
15030
|
-
`[voice-registry] elevenlabs /v1/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15031
|
-
`
|
|
15032
|
-
);
|
|
15033
|
-
return;
|
|
15034
|
-
}
|
|
15035
|
-
const json = await res.json();
|
|
15036
|
-
const rows = elevenLabsVoiceRows(json.voices);
|
|
15037
|
-
process.stderr.write(`[voice-registry] elevenlabs /v1/voices \xB7 ${rows.length} voices in personal library
|
|
15038
|
-
`);
|
|
15039
|
-
personal.push(...rows);
|
|
15040
|
-
} catch (e) {
|
|
15041
|
-
const cause = e instanceof Error ? e.cause : null;
|
|
15042
|
-
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
15043
|
-
process.stderr.write(
|
|
15044
|
-
`[voice-registry] elevenlabs /v1/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
15045
|
-
`
|
|
15046
|
-
);
|
|
15047
|
-
}
|
|
15048
|
-
})(),
|
|
15049
|
-
(async () => {
|
|
15050
|
-
try {
|
|
15051
|
-
const res = await fetch(
|
|
15052
|
-
"https://api.elevenlabs.io/v1/shared-voices?page_size=100",
|
|
15053
|
-
{ headers: { "xi-api-key": activeKey } }
|
|
15054
|
-
);
|
|
15055
|
-
if (!res.ok) {
|
|
15056
|
-
const errText = await res.text();
|
|
15057
|
-
process.stderr.write(
|
|
15058
|
-
`[voice-registry] elevenlabs /v1/shared-voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15087
|
+
if (!res.ok) {
|
|
15088
|
+
const errText = await res.text();
|
|
15089
|
+
process.stderr.write(
|
|
15090
|
+
`[voice-registry] elevenlabs /v2/voices HTTP ${res.status}: ${errText.slice(0, 300)}
|
|
15059
15091
|
`
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15069
|
-
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15092
|
+
);
|
|
15093
|
+
lastError = classifyElevenLabsError(res.status, errText);
|
|
15094
|
+
break;
|
|
15095
|
+
}
|
|
15096
|
+
const json = await res.json();
|
|
15097
|
+
const rows = elevenLabsV2VoiceRows(json.voices);
|
|
15098
|
+
for (const r of rows) {
|
|
15099
|
+
out.push({
|
|
15100
|
+
provider: "elevenlabs",
|
|
15101
|
+
model: "eleven_multilingual_v2",
|
|
15102
|
+
voiceId: r.voiceId,
|
|
15103
|
+
label: r.label,
|
|
15104
|
+
language: r.category,
|
|
15105
|
+
configured: true
|
|
15106
|
+
});
|
|
15107
|
+
}
|
|
15108
|
+
const nextToken = json.has_more === true && typeof json.next_page_token === "string" ? json.next_page_token : null;
|
|
15109
|
+
if (!nextToken) break;
|
|
15110
|
+
token = nextToken;
|
|
15111
|
+
} catch (e) {
|
|
15112
|
+
const cause = e instanceof Error ? e.cause : null;
|
|
15113
|
+
const detail = cause?.message ? `: ${cause.message}` : "";
|
|
15114
|
+
process.stderr.write(
|
|
15115
|
+
`[voice-registry] elevenlabs /v2/voices fetch failed${detail} \xB7 ${e instanceof Error ? e.message : String(e)}
|
|
15073
15116
|
`
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
]);
|
|
15078
|
-
if (personal.length > 0 || shared.length > 0) {
|
|
15079
|
-
const nonEl = voices.filter((v) => v.provider !== "elevenlabs");
|
|
15080
|
-
const personalIds = new Set(personal.map((r) => r.voiceId));
|
|
15081
|
-
const sharedDeduped = shared.filter((r) => !personalIds.has(r.voiceId));
|
|
15082
|
-
const personalMapped = personal.map((r) => ({
|
|
15083
|
-
provider: "elevenlabs",
|
|
15084
|
-
model: "eleven_multilingual_v2",
|
|
15085
|
-
voiceId: r.voiceId,
|
|
15086
|
-
label: r.label,
|
|
15087
|
-
// Personal-library rows keep their actual category
|
|
15088
|
-
// ("premade", "cloned", "professional", "generated").
|
|
15089
|
-
language: r.category,
|
|
15090
|
-
configured: true
|
|
15091
|
-
}));
|
|
15092
|
-
const sharedMapped = sharedDeduped.map((r) => ({
|
|
15117
|
+
);
|
|
15118
|
+
lastError = {
|
|
15119
|
+
code: "fetch_failed",
|
|
15093
15120
|
provider: "elevenlabs",
|
|
15094
|
-
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
// which set they're picking from. The dropdown's group header
|
|
15098
|
-
// already says "elevenlabs", so the per-row prefix is the
|
|
15099
|
-
// tightest signal we have for personal-vs-shared.
|
|
15100
|
-
label: `${r.label} \xB7 shared`,
|
|
15101
|
-
language: r.language || r.category,
|
|
15102
|
-
configured: true
|
|
15103
|
-
}));
|
|
15104
|
-
voices = [...nonEl, ...personalMapped, ...sharedMapped];
|
|
15121
|
+
message: e instanceof Error ? e.message : String(e)
|
|
15122
|
+
};
|
|
15123
|
+
break;
|
|
15105
15124
|
}
|
|
15106
|
-
return { voices, provider: "elevenlabs", configured: true };
|
|
15107
15125
|
}
|
|
15108
|
-
|
|
15109
|
-
}
|
|
15110
|
-
|
|
15111
|
-
|
|
15112
|
-
|
|
15113
|
-
for (const item of raw) {
|
|
15114
|
-
if (!item || typeof item !== "object") continue;
|
|
15115
|
-
const obj = item;
|
|
15116
|
-
const voiceId = typeof obj.voice_id === "string" ? obj.voice_id : "";
|
|
15117
|
-
if (!voiceId) continue;
|
|
15118
|
-
const label = typeof obj.name === "string" && obj.name.trim() ? obj.name.trim() : voiceId;
|
|
15119
|
-
const category = typeof obj.category === "string" && obj.category.trim() ? obj.category.trim() : "shared";
|
|
15120
|
-
const language = typeof obj.language === "string" && obj.language.trim() ? obj.language.trim() : void 0;
|
|
15121
|
-
out.push({ voiceId, label, category, language });
|
|
15122
|
-
}
|
|
15123
|
-
return out;
|
|
15126
|
+
process.stderr.write(
|
|
15127
|
+
`[voice-registry] elevenlabs /v2/voices \xB7 ${out.length} voices total across all pages
|
|
15128
|
+
`
|
|
15129
|
+
);
|
|
15130
|
+
return { voices: out, error: lastError };
|
|
15124
15131
|
}
|
|
15125
|
-
function
|
|
15132
|
+
function elevenLabsV2VoiceRows(raw) {
|
|
15126
15133
|
if (!Array.isArray(raw)) return [];
|
|
15127
15134
|
const out = [];
|
|
15128
15135
|
for (const item of raw) {
|
|
@@ -15136,6 +15143,164 @@ function elevenLabsVoiceRows(raw) {
|
|
|
15136
15143
|
}
|
|
15137
15144
|
return out;
|
|
15138
15145
|
}
|
|
15146
|
+
var ELEVENLABS_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
15147
|
+
var elevenLabsCache = /* @__PURE__ */ new Map();
|
|
15148
|
+
function elevenLabsCacheKey(apiKey) {
|
|
15149
|
+
return apiKey.slice(0, 8);
|
|
15150
|
+
}
|
|
15151
|
+
async function getElevenLabsVoicesCached(apiKey) {
|
|
15152
|
+
const key = elevenLabsCacheKey(apiKey);
|
|
15153
|
+
const cached = elevenLabsCache.get(key);
|
|
15154
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
15155
|
+
return { voices: cached.voices, error: null };
|
|
15156
|
+
}
|
|
15157
|
+
const result = await fetchAllElevenLabsV2Voices(apiKey);
|
|
15158
|
+
process.stderr.write(
|
|
15159
|
+
`[voice-registry] elevenlabs catalogue \xB7 ${result.voices.length} voices from /v2/voices${result.error ? ` (error: ${result.error.code})` : ""}
|
|
15160
|
+
`
|
|
15161
|
+
);
|
|
15162
|
+
if (!result.error) {
|
|
15163
|
+
elevenLabsCache.set(key, { voices: result.voices, expiresAt: Date.now() + ELEVENLABS_CACHE_TTL_MS });
|
|
15164
|
+
}
|
|
15165
|
+
return result;
|
|
15166
|
+
}
|
|
15167
|
+
var MINIMAX_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
15168
|
+
var miniMaxCache = /* @__PURE__ */ new Map();
|
|
15169
|
+
function miniMaxCacheKey(apiKey) {
|
|
15170
|
+
return apiKey.slice(0, 8);
|
|
15171
|
+
}
|
|
15172
|
+
async function fetchAllMiniMaxVoices(apiKey) {
|
|
15173
|
+
try {
|
|
15174
|
+
const res = await fetch(`${minimaxBaseUrl()}/v1/get_voice`, {
|
|
15175
|
+
method: "POST",
|
|
15176
|
+
headers: {
|
|
15177
|
+
"authorization": `Bearer ${apiKey}`,
|
|
15178
|
+
"content-type": "application/json"
|
|
15179
|
+
},
|
|
15180
|
+
body: JSON.stringify({ voice_type: "all" })
|
|
15181
|
+
});
|
|
15182
|
+
if (!res.ok) {
|
|
15183
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15184
|
+
}
|
|
15185
|
+
const json = await res.json();
|
|
15186
|
+
const rows = [
|
|
15187
|
+
...voiceRows(json.system_voice, "system"),
|
|
15188
|
+
...voiceRows(json.voice_cloning, "clone"),
|
|
15189
|
+
...voiceRows(json.voice_generation, "generated")
|
|
15190
|
+
];
|
|
15191
|
+
if (rows.length === 0) {
|
|
15192
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15193
|
+
}
|
|
15194
|
+
return rows.map((r) => ({
|
|
15195
|
+
provider: "minimax",
|
|
15196
|
+
model: "speech-2.8-hd",
|
|
15197
|
+
voiceId: r.voiceId,
|
|
15198
|
+
label: r.label,
|
|
15199
|
+
language: r.kind,
|
|
15200
|
+
configured: true
|
|
15201
|
+
}));
|
|
15202
|
+
} catch {
|
|
15203
|
+
return MINIMAX_SYSTEM_VOICES.map((v) => ({ ...v, configured: true }));
|
|
15204
|
+
}
|
|
15205
|
+
}
|
|
15206
|
+
async function getMiniMaxVoicesCached(apiKey) {
|
|
15207
|
+
const key = miniMaxCacheKey(apiKey);
|
|
15208
|
+
const cached = miniMaxCache.get(key);
|
|
15209
|
+
if (cached && cached.expiresAt > Date.now()) return cached.voices;
|
|
15210
|
+
const voices = await fetchAllMiniMaxVoices(apiKey);
|
|
15211
|
+
miniMaxCache.set(key, { voices, expiresAt: Date.now() + MINIMAX_CACHE_TTL_MS });
|
|
15212
|
+
return voices;
|
|
15213
|
+
}
|
|
15214
|
+
var BROWSER_FALLBACK = {
|
|
15215
|
+
provider: "browser",
|
|
15216
|
+
model: "speechSynthesis",
|
|
15217
|
+
voiceId: "system-default",
|
|
15218
|
+
label: "Browser default",
|
|
15219
|
+
configured: true
|
|
15220
|
+
};
|
|
15221
|
+
async function listVoicesPage(cursorStr, pageSize) {
|
|
15222
|
+
const size = Math.min(Math.max(pageSize | 0 || 30, 5), 100);
|
|
15223
|
+
const cursor = decodeCursor(cursorStr);
|
|
15224
|
+
const isFirstPage = cursor === null;
|
|
15225
|
+
const activeProvider = getActiveVoiceProvider();
|
|
15226
|
+
const fixed = [];
|
|
15227
|
+
if (isFirstPage && getKey("openai")) {
|
|
15228
|
+
fixed.push(...OPENAI_VOICES.map((v) => ({ ...v, configured: true })));
|
|
15229
|
+
}
|
|
15230
|
+
if (!activeProvider) {
|
|
15231
|
+
return {
|
|
15232
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15233
|
+
nextCursor: null,
|
|
15234
|
+
hasMore: false,
|
|
15235
|
+
provider: null,
|
|
15236
|
+
configured: false
|
|
15237
|
+
};
|
|
15238
|
+
}
|
|
15239
|
+
const activeKey = getActiveVoiceKeyPlaintext();
|
|
15240
|
+
if (!activeKey) {
|
|
15241
|
+
return {
|
|
15242
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15243
|
+
nextCursor: null,
|
|
15244
|
+
hasMore: false,
|
|
15245
|
+
provider: activeProvider,
|
|
15246
|
+
configured: false
|
|
15247
|
+
};
|
|
15248
|
+
}
|
|
15249
|
+
if (activeProvider === "elevenlabs") {
|
|
15250
|
+
const { voices: all, error } = await getElevenLabsVoicesCached(activeKey);
|
|
15251
|
+
const offset = cursor && cursor.src === "el" ? cursor.offset ?? 0 : 0;
|
|
15252
|
+
const slice = all.slice(offset, offset + size);
|
|
15253
|
+
const next = offset + slice.length;
|
|
15254
|
+
const hasMore = next < all.length;
|
|
15255
|
+
const nextCursor = hasMore ? encodeCursor({ src: "el", offset: next }) : null;
|
|
15256
|
+
const voices = [...fixed, ...slice];
|
|
15257
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
15258
|
+
return {
|
|
15259
|
+
voices,
|
|
15260
|
+
nextCursor,
|
|
15261
|
+
hasMore,
|
|
15262
|
+
provider: "elevenlabs",
|
|
15263
|
+
configured: true,
|
|
15264
|
+
// Only attach the error to the FIRST page response · subsequent
|
|
15265
|
+
// pages (offset > 0) won't fire if the first page errored
|
|
15266
|
+
// (voices is empty so hasMore is false), but defensive.
|
|
15267
|
+
...error && offset === 0 ? { error } : {}
|
|
15268
|
+
};
|
|
15269
|
+
}
|
|
15270
|
+
if (activeProvider === "minimax") {
|
|
15271
|
+
const all = await getMiniMaxVoicesCached(activeKey);
|
|
15272
|
+
const offset = cursor && cursor.src === "mm" ? cursor.offset ?? 0 : 0;
|
|
15273
|
+
const slice = all.slice(offset, offset + size);
|
|
15274
|
+
const next = offset + slice.length;
|
|
15275
|
+
const hasMore = next < all.length;
|
|
15276
|
+
const nextCursor = hasMore ? encodeCursor({ src: "mm", offset: next }) : null;
|
|
15277
|
+
const voices = [...fixed, ...slice];
|
|
15278
|
+
if (!hasMore) voices.push(BROWSER_FALLBACK);
|
|
15279
|
+
return { voices, nextCursor, hasMore, provider: "minimax", configured: true };
|
|
15280
|
+
}
|
|
15281
|
+
return {
|
|
15282
|
+
voices: [...fixed, BROWSER_FALLBACK],
|
|
15283
|
+
nextCursor: null,
|
|
15284
|
+
hasMore: false,
|
|
15285
|
+
provider: activeProvider,
|
|
15286
|
+
configured: true
|
|
15287
|
+
};
|
|
15288
|
+
}
|
|
15289
|
+
async function listAvailableVoices() {
|
|
15290
|
+
const voices = [];
|
|
15291
|
+
let cursor = null;
|
|
15292
|
+
let provider = null;
|
|
15293
|
+
let configured = false;
|
|
15294
|
+
for (let i = 0; i < 50; i++) {
|
|
15295
|
+
const page = await listVoicesPage(cursor, 100);
|
|
15296
|
+
voices.push(...page.voices);
|
|
15297
|
+
provider = page.provider;
|
|
15298
|
+
configured = page.configured;
|
|
15299
|
+
if (!page.hasMore || !page.nextCursor) break;
|
|
15300
|
+
cursor = page.nextCursor;
|
|
15301
|
+
}
|
|
15302
|
+
return { voices, provider, configured };
|
|
15303
|
+
}
|
|
15139
15304
|
function voiceRows(raw, kind) {
|
|
15140
15305
|
if (!Array.isArray(raw)) return [];
|
|
15141
15306
|
const out = [];
|
|
@@ -15170,7 +15335,7 @@ function makeMiniMaxBalanceError() {
|
|
|
15170
15335
|
);
|
|
15171
15336
|
err2.code = "paid-plan-required";
|
|
15172
15337
|
err2.provider = "minimax";
|
|
15173
|
-
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/
|
|
15338
|
+
err2.upgradeUrl = getPrefs().minimaxRegion === "intl" ? "https://platform.minimax.io/user-center/basic-information" : "https://platform.minimaxi.com/user-center/payment/balance";
|
|
15174
15339
|
return err2;
|
|
15175
15340
|
}
|
|
15176
15341
|
function makeElevenLabsBillingError(message) {
|
|
@@ -15197,10 +15362,15 @@ function tryExtractTtsBillingError(err2) {
|
|
|
15197
15362
|
}
|
|
15198
15363
|
return out;
|
|
15199
15364
|
}
|
|
15365
|
+
function stripSpokenLabels(text) {
|
|
15366
|
+
if (!text) return "";
|
|
15367
|
+
return text.replace(/【[^】\n]{1,40}】[ \t]*/g, "").replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
15368
|
+
}
|
|
15200
15369
|
function cleanForSpeech(md) {
|
|
15201
15370
|
if (!md) return "";
|
|
15202
15371
|
let out = md;
|
|
15203
15372
|
out = out.replace(/```[\s\S]*?```/g, " ");
|
|
15373
|
+
out = out.replace(/【[^】\n]{1,40}】[ \t]*/g, " ");
|
|
15204
15374
|
out = out.replace(/`([^`\n]+)`/g, "$1");
|
|
15205
15375
|
out = out.replace(/!\[[^\]]*\]\([^)]+\)/g, " ");
|
|
15206
15376
|
out = out.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
@@ -17996,6 +18166,7 @@ var MAX_PICKS = 2;
|
|
|
17996
18166
|
async function pickChairClarifyDecision(opts) {
|
|
17997
18167
|
const prompt = latestUserPrompt(opts.history);
|
|
17998
18168
|
if (!prompt) return { shouldAsk: true, rationale: "no user prompt yet" };
|
|
18169
|
+
const isBrainstorm = (opts.mode || "").toLowerCase() === "brainstorm";
|
|
17999
18170
|
const sys = {
|
|
18000
18171
|
role: "system",
|
|
18001
18172
|
content: [
|
|
@@ -18017,6 +18188,14 @@ async function pickChairClarifyDecision(opts) {
|
|
|
18017
18188
|
"Bias toward RELEASE. A slightly-fuzzy framing is fine \u2014 directors",
|
|
18018
18189
|
"can sharpen it themselves. Asking when you don't need to kills",
|
|
18019
18190
|
"momentum.",
|
|
18191
|
+
...isBrainstorm ? [
|
|
18192
|
+
"",
|
|
18193
|
+
"BRAINSTORM MODE OVERRIDE \xB7 this room is in brainstorm mode. RELEASE",
|
|
18194
|
+
"unless the subject is literally unparseable (empty, gibberish, single",
|
|
18195
|
+
"character). Fuzzy / abstract / under-specified seeds are a FEATURE",
|
|
18196
|
+
"here \u2014 directors fill the gap with explicit assumptions, not by",
|
|
18197
|
+
"asking the user. Default ask=false in brainstorm."
|
|
18198
|
+
] : [],
|
|
18020
18199
|
"",
|
|
18021
18200
|
"Reply with STRICT JSON ONLY (no prose, no fences):",
|
|
18022
18201
|
`{ "ask": true, "rationale": "\u2264120 chars \xB7 what's load-bearingly missing" }`,
|
|
@@ -18843,47 +19022,49 @@ var SHARED_ROOM_PROTOCOL = [
|
|
|
18843
19022
|
].join("\n");
|
|
18844
19023
|
var TONE_GUIDANCE = {
|
|
18845
19024
|
brainstorm: [
|
|
18846
|
-
|
|
19025
|
+
"\u2500\u2500\u2500 \u5171\u521B\u6A21\u5F0F \xB7 BRAINSTORM \u2500\u2500\u2500",
|
|
19026
|
+
"\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",
|
|
18847
19027
|
"",
|
|
18848
|
-
"
|
|
18849
|
-
"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.",
|
|
19028
|
+
"\u9ED8\u8BA4\u6A21\u5F0F\uFF1A**\u53D1\u6563\u5171\u521B\u6A21\u5F0F\uFF08VALUE AMPLIFICATION\uFF09**\u3002",
|
|
18850
19029
|
"",
|
|
18851
|
-
"
|
|
18852
|
-
" \xB7
|
|
18853
|
-
" \xB7
|
|
18854
|
-
" \xB7
|
|
18855
|
-
" \xB7
|
|
19030
|
+
"## \u7EDD\u5BF9\u4E0D\u8981 (do NOT)",
|
|
19031
|
+
" \xB7 \u4E0D\u8981\u6025\u7740\u5224\u65AD\u5BF9\u9519\uFF1B",
|
|
19032
|
+
" \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",
|
|
19033
|
+
" \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",
|
|
19034
|
+
" \xB7 \u4E0D\u8981\u628A\u8BA8\u8BBA\u6536\u655B\u5230\u98CE\u9669\u548C\u9650\u5236\uFF1B",
|
|
19035
|
+
' \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',
|
|
19036
|
+
' \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',
|
|
18856
19037
|
"",
|
|
18857
|
-
"
|
|
19038
|
+
"## \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",
|
|
18858
19039
|
"",
|
|
18859
|
-
"
|
|
18860
|
-
"
|
|
18861
|
-
' \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`.',
|
|
18862
|
-
` \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.`,
|
|
19040
|
+
"\u3010\u6211\u770B\u5230\u7684\u4EF7\u503C\u3011",
|
|
19041
|
+
"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",
|
|
18863
19042
|
"",
|
|
18864
|
-
"
|
|
19043
|
+
"\u3010\u6211\u4F1A\u600E\u4E48\u653E\u5927\u3011",
|
|
19044
|
+
"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",
|
|
18865
19045
|
"",
|
|
18866
|
-
"
|
|
18867
|
-
|
|
18868
|
-
" \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.",
|
|
18869
|
-
` \xB7 Don't preface with affirmation ("That's a good point, what if we\u2026") \u2014 just say the new idea.`,
|
|
18870
|
-
" \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.",
|
|
18871
|
-
` \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.`,
|
|
18872
|
-
` \xB7 Avoid generic innovation language: "synergy", "leverage AI", "platform play", "democratise X", "AI-native", "unlock value". They're decoration, not ideas.`,
|
|
18873
|
-
" \xB7 Avoid the same lens used by the immediately-prior speaker UNLESS you're explicitly yes-and'ing them.",
|
|
19046
|
+
"\u3010\u4E00\u4E2A\u66F4\u6027\u611F\u7684\u8868\u8FBE\u3011",
|
|
19047
|
+
"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",
|
|
18874
19048
|
"",
|
|
18875
|
-
"
|
|
18876
|
-
"
|
|
19049
|
+
"\u3010\u4E00\u4E2A\u5177\u4F53\u505A\u6CD5\u3011",
|
|
19050
|
+
"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",
|
|
18877
19051
|
"",
|
|
18878
|
-
"
|
|
19052
|
+
"\u3010\u6211\u8865\u5145\u7684\u65B0\u65B9\u5411\u3011",
|
|
19053
|
+
"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",
|
|
18879
19054
|
"",
|
|
18880
|
-
"
|
|
18881
|
-
"
|
|
18882
|
-
"
|
|
18883
|
-
|
|
18884
|
-
|
|
18885
|
-
|
|
18886
|
-
|
|
19055
|
+
"\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",
|
|
19056
|
+
"",
|
|
19057
|
+
"## English-language fallback",
|
|
19058
|
+
"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.",
|
|
19059
|
+
"",
|
|
19060
|
+
"## Light don'ts (carryovers worth keeping)",
|
|
19061
|
+
' \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',
|
|
19062
|
+
" \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",
|
|
19063
|
+
' \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',
|
|
19064
|
+
"",
|
|
19065
|
+
`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.`,
|
|
19066
|
+
"",
|
|
19067
|
+
'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.'
|
|
18887
19068
|
].join("\n"),
|
|
18888
19069
|
constructive: [
|
|
18889
19070
|
"CONSTRUCTIVE \xB7 sympathetic interrogator. You want the user to win, but only via an idea that can actually survive scrutiny.",
|
|
@@ -18988,6 +19169,23 @@ var TONE_GUIDANCE = {
|
|
|
18988
19169
|
].join("\n")
|
|
18989
19170
|
};
|
|
18990
19171
|
var CHAIR_MODE_PROTOCOL = {
|
|
19172
|
+
brainstorm: [
|
|
19173
|
+
`\u2500\u2500\u2500 CHAIR \xB7 BRAINSTORM-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
19174
|
+
`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.`,
|
|
19175
|
+
``,
|
|
19176
|
+
`**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).`,
|
|
19177
|
+
``,
|
|
19178
|
+
`**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:`,
|
|
19179
|
+
` \xB7 surface the 2\u20133 strongest unexpected VALUE angles the room opened (not the strongest objections)`,
|
|
19180
|
+
` \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)`,
|
|
19181
|
+
` \xB7 pick the most sexy / most concrete idea the room produced and re-frame it once for the user`,
|
|
19182
|
+
` \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.`,
|
|
19183
|
+
` \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.`,
|
|
19184
|
+
``,
|
|
19185
|
+
`**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.`,
|
|
19186
|
+
``,
|
|
19187
|
+
`**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.`
|
|
19188
|
+
].join("\n"),
|
|
18991
19189
|
research: [
|
|
18992
19190
|
`\u2500\u2500\u2500 CHAIR \xB7 RESEARCH-MODE PROTOCOL \u2500\u2500\u2500`,
|
|
18993
19191
|
`This room is in research mode. Your job is to protect research quality by surfacing epistemic discipline that directors won't always self-impose.`,
|
|
@@ -19011,7 +19209,7 @@ var CHAIR_MODE_PROTOCOL = {
|
|
|
19011
19209
|
].join("\n")
|
|
19012
19210
|
};
|
|
19013
19211
|
var HOUSE_ENGAGE_BY_TONE = {
|
|
19014
|
-
brainstorm: "
|
|
19212
|
+
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",
|
|
19015
19213
|
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",
|
|
19016
19214
|
debate: "steelman the target claim before attacking it, distinguish confidence from preference, and name what would change your mind",
|
|
19017
19215
|
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",
|
|
@@ -19019,7 +19217,7 @@ var HOUSE_ENGAGE_BY_TONE = {
|
|
|
19019
19217
|
};
|
|
19020
19218
|
var HOUSE_ENGAGE_DEFAULT = HOUSE_ENGAGE_BY_TONE.debate;
|
|
19021
19219
|
var TONE_OVERRIDE_BY_TONE = {
|
|
19022
|
-
brainstorm:
|
|
19220
|
+
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.`,
|
|
19023
19221
|
constructive: "your default trained preference to be diplomatically vague. Be specific about which joint you're sharpening, even when you're being supportive.",
|
|
19024
19222
|
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.",
|
|
19025
19223
|
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.",
|
|
@@ -21430,11 +21628,14 @@ async function pumpQueue(roomId) {
|
|
|
21430
21628
|
});
|
|
21431
21629
|
if (reachedCap) {
|
|
21432
21630
|
const room = getRoom(roomId);
|
|
21433
|
-
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify &&
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21631
|
+
if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify && room.voteTrigger === "manual") {
|
|
21632
|
+
const nextRound = nextUserRoundNum(roomId);
|
|
21633
|
+
rlog(roomId, "manual-auto-continue", {
|
|
21634
|
+
fromRound: state.roundNum,
|
|
21635
|
+
toRound: nextRound
|
|
21636
|
+
});
|
|
21637
|
+
tickRoom(roomId, { roundNum: nextRound, kind: "continue" });
|
|
21638
|
+
} else if (room && room.status === "live" && !room.awaitingContinue && !room.awaitingClarify) {
|
|
21438
21639
|
const wrappedRound = state.roundNum;
|
|
21439
21640
|
emitChairPending(roomId, "vote-summary");
|
|
21440
21641
|
let recommendation;
|
|
@@ -21720,9 +21921,11 @@ async function streamSpeakerTurn(args) {
|
|
|
21720
21921
|
}
|
|
21721
21922
|
async function emitVoiceText(text) {
|
|
21722
21923
|
if (!voiceMode || !text.trim()) return;
|
|
21924
|
+
const spoken = stripSpokenLabels(text);
|
|
21925
|
+
if (!spoken) return;
|
|
21723
21926
|
const voiceProfile = currentVoiceProfile();
|
|
21724
21927
|
if (!voiceProfile) return;
|
|
21725
|
-
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${
|
|
21928
|
+
process.stderr.write(`[tts] emitVoiceText called: provider=${voiceProfile.provider} voiceId=${voiceProfile.voiceId} textLen=${spoken.length} text="${spoken.slice(0, 50)}"
|
|
21726
21929
|
`);
|
|
21727
21930
|
const MAX_ATTEMPTS = 2;
|
|
21728
21931
|
const TIMEOUT_MS = 3e4;
|
|
@@ -21735,7 +21938,7 @@ async function streamSpeakerTurn(args) {
|
|
|
21735
21938
|
let chunkCount = 0;
|
|
21736
21939
|
let failure = null;
|
|
21737
21940
|
try {
|
|
21738
|
-
for await (const chunk of synthesizeSpeechStream(
|
|
21941
|
+
for await (const chunk of synthesizeSpeechStream(spoken, voiceProfile, timeoutCtrl.signal)) {
|
|
21739
21942
|
if (signal.aborted) break;
|
|
21740
21943
|
chunkCount++;
|
|
21741
21944
|
roomBus.emit(roomId, {
|
|
@@ -21965,6 +22168,13 @@ async function streamSpeakerTurn(args) {
|
|
|
21965
22168
|
if (tail) await emitVoiceText(tail);
|
|
21966
22169
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
21967
22170
|
}
|
|
22171
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22172
|
+
process.stderr.write(
|
|
22173
|
+
`[tts] zero-chunks for msg=${placeholder.id.slice(0, 8)} agent=${speaker.name} \xB7 short-circuiting voice wait
|
|
22174
|
+
`
|
|
22175
|
+
);
|
|
22176
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22177
|
+
}
|
|
21968
22178
|
updateMessageBody(placeholder.id, buf, {
|
|
21969
22179
|
...placeholderMeta,
|
|
21970
22180
|
speakerStatus: "final",
|
|
@@ -22020,6 +22230,9 @@ async function streamSpeakerTurn(args) {
|
|
|
22020
22230
|
if (voiceChunker) {
|
|
22021
22231
|
roomBus.emit(roomId, { type: "voice-final", messageId: placeholder.id });
|
|
22022
22232
|
}
|
|
22233
|
+
if (voiceMode && voiceSeq === 0) {
|
|
22234
|
+
markVoicePlaybackDone(roomId, placeholder.id);
|
|
22235
|
+
}
|
|
22023
22236
|
roomBus.emit(roomId, {
|
|
22024
22237
|
type: "message-final",
|
|
22025
22238
|
messageId: placeholder.id,
|
|
@@ -22690,8 +22903,9 @@ async function runChairClarify(roomId) {
|
|
|
22690
22903
|
emitChairPending(roomId, "clarify-deciding");
|
|
22691
22904
|
let decision = null;
|
|
22692
22905
|
try {
|
|
22906
|
+
const roomForMode = getRoom(roomId);
|
|
22693
22907
|
decision = await withTimeout(
|
|
22694
|
-
pickChairClarifyDecision({ history }),
|
|
22908
|
+
pickChairClarifyDecision({ history, mode: roomForMode?.mode }),
|
|
22695
22909
|
15e3,
|
|
22696
22910
|
"chair-clarify-decision"
|
|
22697
22911
|
);
|
|
@@ -25397,6 +25611,25 @@ function ttsCacheSet(key, val) {
|
|
|
25397
25611
|
function voicesRouter() {
|
|
25398
25612
|
const r = new Hono14();
|
|
25399
25613
|
r.get("/", async (c) => {
|
|
25614
|
+
const url = new URL(c.req.url);
|
|
25615
|
+
const cursor = url.searchParams.get("cursor");
|
|
25616
|
+
const pageSizeRaw = url.searchParams.get("pageSize");
|
|
25617
|
+
if (cursor !== null || pageSizeRaw !== null) {
|
|
25618
|
+
const pageSize = pageSizeRaw ? Math.max(1, Number.parseInt(pageSizeRaw, 10) || 30) : 30;
|
|
25619
|
+
const page = await listVoicesPage(cursor, pageSize);
|
|
25620
|
+
return c.json({
|
|
25621
|
+
voices: page.voices,
|
|
25622
|
+
nextCursor: page.nextCursor,
|
|
25623
|
+
hasMore: page.hasMore,
|
|
25624
|
+
provider: page.provider,
|
|
25625
|
+
configured: page.configured,
|
|
25626
|
+
// Structured upstream error · the picker uses this to render
|
|
25627
|
+
// a clear "your API key is missing voices_read permission"
|
|
25628
|
+
// banner + a link to the ElevenLabs API-key settings page
|
|
25629
|
+
// instead of a silently empty dropdown.
|
|
25630
|
+
...page.error ? { error: page.error } : {}
|
|
25631
|
+
});
|
|
25632
|
+
}
|
|
25400
25633
|
const catalog = await listAvailableVoices();
|
|
25401
25634
|
return c.json({
|
|
25402
25635
|
voices: catalog.voices,
|
|
@@ -25543,7 +25776,7 @@ function voicesRouter() {
|
|
|
25543
25776
|
init_paths();
|
|
25544
25777
|
|
|
25545
25778
|
// src/version.ts
|
|
25546
|
-
var VERSION = "0.1.
|
|
25779
|
+
var VERSION = "0.1.36";
|
|
25547
25780
|
|
|
25548
25781
|
// src/utils/render-picker-catalog.ts
|
|
25549
25782
|
function renderPickerCatalog() {
|