privateboard 0.1.12 → 0.1.13
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/cli.js +229 -51
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/public/agent-profile.js +0 -2
- package/public/app.js +765 -61
- package/public/home.html +609 -1
- package/public/i18n.js +2 -0
- package/public/index.html +726 -95
- package/public/themes.css +38 -0
- package/public/typing-sfx.js +58 -1
- package/public/user-settings.js +3 -1
package/dist/cli.js
CHANGED
|
@@ -502,6 +502,14 @@ ALTER TABLE rooms ADD COLUMN vote_trigger TEXT NOT NULL DEFAULT 'auto';
|
|
|
502
502
|
}
|
|
503
503
|
});
|
|
504
504
|
|
|
505
|
+
// src/storage/migrations/033_room_members_removed_at.sql
|
|
506
|
+
var room_members_removed_at_default;
|
|
507
|
+
var init_room_members_removed_at = __esm({
|
|
508
|
+
"src/storage/migrations/033_room_members_removed_at.sql"() {
|
|
509
|
+
room_members_removed_at_default = "-- Soft-delete column on room_members so excused directors stay\n-- queryable for chat-history rendering + voice replay. NULL marks\n-- an active member; a non-null timestamp records when the chair\n-- excused them from the room. Prior to this migration a director\n-- removal hard-DELETEd the row, which dropped the director from\n-- `listRoomMembers` and broke speaker-name lookups + voice profile\n-- resolution for their past messages.\nALTER TABLE room_members ADD COLUMN removed_at INTEGER;\n";
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
505
513
|
// src/storage/db.ts
|
|
506
514
|
var db_exports = {};
|
|
507
515
|
__export(db_exports, {
|
|
@@ -591,6 +599,7 @@ var init_db = __esm({
|
|
|
591
599
|
init_minimax_region();
|
|
592
600
|
init_agent_persona_spec();
|
|
593
601
|
init_room_vote_trigger();
|
|
602
|
+
init_room_members_removed_at();
|
|
594
603
|
MIGRATIONS = [
|
|
595
604
|
{ name: "001_init.sql", sql: init_default },
|
|
596
605
|
{ name: "002_default_opus.sql", sql: default_opus_default },
|
|
@@ -623,7 +632,8 @@ var init_db = __esm({
|
|
|
623
632
|
{ name: "029_web_search_provider_pref.sql", sql: web_search_provider_pref_default },
|
|
624
633
|
{ name: "030_minimax_region.sql", sql: minimax_region_default },
|
|
625
634
|
{ name: "031_agent_persona_spec.sql", sql: agent_persona_spec_default },
|
|
626
|
-
{ name: "032_room_vote_trigger.sql", sql: room_vote_trigger_default }
|
|
635
|
+
{ name: "032_room_vote_trigger.sql", sql: room_vote_trigger_default },
|
|
636
|
+
{ name: "033_room_members_removed_at.sql", sql: room_members_removed_at_default }
|
|
627
637
|
];
|
|
628
638
|
_db = null;
|
|
629
639
|
}
|
|
@@ -3598,11 +3608,11 @@ async function callLLMWithUsage(req) {
|
|
|
3598
3608
|
|
|
3599
3609
|
// src/storage/reconcile-models.ts
|
|
3600
3610
|
var PRIMARY_BY_CARRIER = {
|
|
3601
|
-
openrouter: "opus-4-
|
|
3602
|
-
anthropic: "
|
|
3603
|
-
openai: "gpt-5-
|
|
3604
|
-
google: "gemini-3-flash",
|
|
3605
|
-
xai: "grok-4-
|
|
3611
|
+
openrouter: "opus-4-6-fast",
|
|
3612
|
+
anthropic: "haiku-4-5",
|
|
3613
|
+
openai: "gpt-5-4-mini",
|
|
3614
|
+
google: "gemini-3-1-flash",
|
|
3615
|
+
xai: "grok-4-1-fast"
|
|
3606
3616
|
};
|
|
3607
3617
|
var CARRIER_PRIORITY = ["openrouter", "anthropic", "openai", "google", "xai"];
|
|
3608
3618
|
function reachableModelVs() {
|
|
@@ -3650,23 +3660,21 @@ function activeCarrier() {
|
|
|
3650
3660
|
}
|
|
3651
3661
|
return null;
|
|
3652
3662
|
}
|
|
3653
|
-
function activeCarrierPrimary() {
|
|
3654
|
-
const carrier = activeCarrier();
|
|
3655
|
-
if (!carrier) return null;
|
|
3656
|
-
return PRIMARY_BY_CARRIER[carrier] ?? null;
|
|
3657
|
-
}
|
|
3658
3663
|
function reconcileAgentModels(opts = {}) {
|
|
3659
3664
|
const reachable = reachableModelVs();
|
|
3660
|
-
const
|
|
3665
|
+
const carrier = activeCarrier();
|
|
3666
|
+
const primary = carrier ? PRIMARY_BY_CARRIER[carrier] ?? null : null;
|
|
3661
3667
|
const forcePrimary = opts.forcePrimary === true;
|
|
3662
3668
|
const switched = [];
|
|
3663
3669
|
const cleared = [];
|
|
3664
3670
|
for (const agent of listAllAgents()) {
|
|
3665
3671
|
const v = (agent.modelV || "").trim();
|
|
3666
3672
|
if (!forcePrimary && v && reachable.has(v)) continue;
|
|
3667
|
-
if (primary) {
|
|
3668
|
-
|
|
3669
|
-
|
|
3673
|
+
if (primary && carrier) {
|
|
3674
|
+
const isChair = agent.roleKind === "moderator";
|
|
3675
|
+
const target = isChair ? primary : pickRandomFastModel(carrier) ?? primary;
|
|
3676
|
+
if (v === target) continue;
|
|
3677
|
+
updateAgent(agent.id, { modelV: target });
|
|
3670
3678
|
switched.push(agent.id);
|
|
3671
3679
|
} else {
|
|
3672
3680
|
if (v === "") continue;
|
|
@@ -3734,6 +3742,42 @@ var PROVIDER_FLAGSHIP = {
|
|
|
3734
3742
|
minimax: null,
|
|
3735
3743
|
elevenlabs: null
|
|
3736
3744
|
};
|
|
3745
|
+
var PROVIDER_FAST = {
|
|
3746
|
+
anthropic: "haiku-4-5",
|
|
3747
|
+
openai: "gpt-5-4-mini",
|
|
3748
|
+
google: "gemini-3-1-flash",
|
|
3749
|
+
xai: "grok-4-1-fast",
|
|
3750
|
+
deepseek: "deepseek-v4-flash",
|
|
3751
|
+
openrouter: "opus-4-6-fast",
|
|
3752
|
+
brave: null,
|
|
3753
|
+
tavily: null,
|
|
3754
|
+
minimax: null,
|
|
3755
|
+
elevenlabs: null
|
|
3756
|
+
};
|
|
3757
|
+
var FAST_POOL_BY_CARRIER = {
|
|
3758
|
+
openrouter: [
|
|
3759
|
+
"opus-4-6-fast",
|
|
3760
|
+
"haiku-4-5",
|
|
3761
|
+
"gpt-5-4-mini",
|
|
3762
|
+
"gemini-3-flash",
|
|
3763
|
+
"gemini-3-1-flash",
|
|
3764
|
+
"grok-4-1-fast",
|
|
3765
|
+
"deepseek-v4-flash"
|
|
3766
|
+
],
|
|
3767
|
+
anthropic: ["opus-4-6-fast", "haiku-4-5"],
|
|
3768
|
+
openai: ["gpt-5-4-mini"],
|
|
3769
|
+
google: ["gemini-3-flash", "gemini-3-1-flash"],
|
|
3770
|
+
xai: ["grok-4-1-fast"]
|
|
3771
|
+
};
|
|
3772
|
+
function pickRandomFastModel(carrier) {
|
|
3773
|
+
if (!carrier) return null;
|
|
3774
|
+
const pool = FAST_POOL_BY_CARRIER[carrier];
|
|
3775
|
+
if (!pool || pool.length === 0) return null;
|
|
3776
|
+
const reachable = new Set(reachableModels().map((m) => m.modelV));
|
|
3777
|
+
const candidates = pool.filter((v) => reachable.has(v));
|
|
3778
|
+
const list = candidates.length > 0 ? candidates : pool;
|
|
3779
|
+
return list[Math.floor(Math.random() * list.length)] ?? null;
|
|
3780
|
+
}
|
|
3737
3781
|
var FLAGSHIP_TIER = /* @__PURE__ */ new Set([
|
|
3738
3782
|
// Anthropic
|
|
3739
3783
|
"opus-4-7",
|
|
@@ -3766,13 +3810,21 @@ function defaultModelFor(keys = getProviderKeyState()) {
|
|
|
3766
3810
|
if (reachable.length === 0) return null;
|
|
3767
3811
|
if (!keys.hasOpenRouter && keys.directProviders.size === 1) {
|
|
3768
3812
|
const provider = Array.from(keys.directProviders)[0];
|
|
3813
|
+
const fast = PROVIDER_FAST[provider];
|
|
3814
|
+
if (fast && reachable.find((m) => m.modelV === fast)) return fast;
|
|
3769
3815
|
const flagship = PROVIDER_FLAGSHIP[provider];
|
|
3770
3816
|
if (flagship && reachable.find((m) => m.modelV === flagship)) return flagship;
|
|
3771
3817
|
}
|
|
3772
3818
|
if (keys.hasOpenRouter) {
|
|
3819
|
+
const fast = reachable.find((m) => m.modelV === "opus-4-6-fast");
|
|
3820
|
+
if (fast) return fast.modelV;
|
|
3773
3821
|
const opus = reachable.find((m) => m.modelV === "opus-4-7");
|
|
3774
3822
|
if (opus) return opus.modelV;
|
|
3775
3823
|
}
|
|
3824
|
+
for (const provider of keys.directProviders) {
|
|
3825
|
+
const fast = PROVIDER_FAST[provider];
|
|
3826
|
+
if (fast && reachable.find((m) => m.modelV === fast)) return fast;
|
|
3827
|
+
}
|
|
3776
3828
|
for (const provider of keys.directProviders) {
|
|
3777
3829
|
const flagship = PROVIDER_FLAGSHIP[provider];
|
|
3778
3830
|
if (flagship && reachable.find((m) => m.modelV === flagship)) return flagship;
|
|
@@ -6903,7 +6955,7 @@ function agentsRouter() {
|
|
|
6903
6955
|
const base = handle.replace(/^\/+/, "");
|
|
6904
6956
|
handle = uniqueHandle(base || slugifyHandle(name));
|
|
6905
6957
|
}
|
|
6906
|
-
const modelV = typeof b.modelV === "string" && isModelV(b.modelV) ? b.modelV : effectiveDefaultModel() ?? "opus-4-
|
|
6958
|
+
const modelV = typeof b.modelV === "string" && isModelV(b.modelV) ? b.modelV : pickRandomFastModel(activeCarrier()) ?? effectiveDefaultModel() ?? "opus-4-6-fast";
|
|
6907
6959
|
const roleTag = typeof b.roleTag === "string" && b.roleTag.trim().length > 0 ? b.roleTag.trim().slice(0, 80) : "director";
|
|
6908
6960
|
const bio = typeof b.bio === "string" && b.bio.trim().length >= BIO_MIN ? b.bio.trim().slice(0, BIO_MAX) : partial.description ? partial.description.slice(0, BIO_MAX) : `A custom director built via deep persona replication.`;
|
|
6909
6961
|
const coverQuote = typeof b.coverQuote === "string" ? b.coverQuote.trim().slice(0, 220) : null;
|
|
@@ -12165,7 +12217,12 @@ function mapRow7(row) {
|
|
|
12165
12217
|
};
|
|
12166
12218
|
}
|
|
12167
12219
|
function mapMember(row) {
|
|
12168
|
-
return {
|
|
12220
|
+
return {
|
|
12221
|
+
agentId: row.agent_id,
|
|
12222
|
+
position: row.position,
|
|
12223
|
+
joinedAt: row.joined_at,
|
|
12224
|
+
removedAt: row.removed_at
|
|
12225
|
+
};
|
|
12169
12226
|
}
|
|
12170
12227
|
function listRooms() {
|
|
12171
12228
|
const rows = getDb().prepare(`SELECT ${ROOM_COLS} FROM rooms ORDER BY created_at DESC`).all();
|
|
@@ -12177,7 +12234,13 @@ function getRoom(id) {
|
|
|
12177
12234
|
}
|
|
12178
12235
|
function listRoomMembers(roomId) {
|
|
12179
12236
|
const rows = getDb().prepare(
|
|
12180
|
-
"SELECT agent_id, position, joined_at FROM room_members WHERE room_id = ? ORDER BY position ASC"
|
|
12237
|
+
"SELECT agent_id, position, joined_at, removed_at FROM room_members WHERE room_id = ? AND removed_at IS NULL ORDER BY position ASC"
|
|
12238
|
+
).all(roomId);
|
|
12239
|
+
return rows.map(mapMember);
|
|
12240
|
+
}
|
|
12241
|
+
function listAllRoomMembers(roomId) {
|
|
12242
|
+
const rows = getDb().prepare(
|
|
12243
|
+
"SELECT agent_id, position, joined_at, removed_at FROM room_members WHERE room_id = ? ORDER BY position ASC"
|
|
12181
12244
|
).all(roomId);
|
|
12182
12245
|
return rows.map(mapMember);
|
|
12183
12246
|
}
|
|
@@ -12252,18 +12315,26 @@ function setRoomStatus(roomId, status, ts = {}) {
|
|
|
12252
12315
|
}
|
|
12253
12316
|
function addRoomMember(roomId, agentId) {
|
|
12254
12317
|
const db = getDb();
|
|
12255
|
-
const existing = db.prepare("SELECT agent_id, position, joined_at FROM room_members WHERE room_id = ? AND agent_id = ?").get(roomId, agentId);
|
|
12256
|
-
if (existing)
|
|
12318
|
+
const existing = db.prepare("SELECT agent_id, position, joined_at, removed_at FROM room_members WHERE room_id = ? AND agent_id = ?").get(roomId, agentId);
|
|
12319
|
+
if (existing) {
|
|
12320
|
+
if (existing.removed_at !== null) {
|
|
12321
|
+
db.prepare("UPDATE room_members SET removed_at = NULL WHERE room_id = ? AND agent_id = ?").run(roomId, agentId);
|
|
12322
|
+
return { agentId, position: existing.position, joinedAt: existing.joined_at, removedAt: null };
|
|
12323
|
+
}
|
|
12324
|
+
return mapMember(existing);
|
|
12325
|
+
}
|
|
12257
12326
|
const maxRow = db.prepare("SELECT COALESCE(MAX(position), -1) AS p FROM room_members WHERE room_id = ?").get(roomId);
|
|
12258
12327
|
const position = maxRow.p + 1;
|
|
12259
12328
|
const now = Date.now();
|
|
12260
12329
|
db.prepare(
|
|
12261
|
-
"INSERT INTO room_members (room_id, agent_id, position, joined_at) VALUES (?, ?, ?,
|
|
12330
|
+
"INSERT INTO room_members (room_id, agent_id, position, joined_at, removed_at) VALUES (?, ?, ?, ?, NULL)"
|
|
12262
12331
|
).run(roomId, agentId, position, now);
|
|
12263
|
-
return { agentId, position, joinedAt: now };
|
|
12332
|
+
return { agentId, position, joinedAt: now, removedAt: null };
|
|
12264
12333
|
}
|
|
12265
12334
|
function removeRoomMember(roomId, agentId) {
|
|
12266
|
-
const result = getDb().prepare(
|
|
12335
|
+
const result = getDb().prepare(
|
|
12336
|
+
"UPDATE room_members SET removed_at = ? WHERE room_id = ? AND agent_id = ? AND removed_at IS NULL"
|
|
12337
|
+
).run(Date.now(), roomId, agentId);
|
|
12267
12338
|
return result.changes > 0;
|
|
12268
12339
|
}
|
|
12269
12340
|
function setRoomIncognito(roomId, incognito) {
|
|
@@ -14920,7 +14991,7 @@ Does the chair need to ask a clarifying question before opening the room?`
|
|
|
14920
14991
|
return { shouldAsk: ask, rationale };
|
|
14921
14992
|
}
|
|
14922
14993
|
async function pickRoundWrap(opts) {
|
|
14923
|
-
const { history, roundNum, signal } = opts;
|
|
14994
|
+
const { history, roundNum, room, signal } = opts;
|
|
14924
14995
|
const transcript = history.slice(-20).filter((m) => {
|
|
14925
14996
|
if (!m.body || !m.body.trim()) return false;
|
|
14926
14997
|
const meta = m.meta;
|
|
@@ -14961,7 +15032,12 @@ async function pickRoundWrap(opts) {
|
|
|
14961
15032
|
"your phrasing across calls; don't lean on the same opener twice.",
|
|
14962
15033
|
"",
|
|
14963
15034
|
"Reply with STRICT JSON ONLY (no prose, no fences):",
|
|
14964
|
-
'{ "recommendation": "end" | "continue", "rationale": "\u2264120 chars \xB7 one tight sentence on the load-bearing reason" }'
|
|
15035
|
+
'{ "recommendation": "end" | "continue", "rationale": "\u2264120 chars \xB7 one tight sentence on the load-bearing reason" }',
|
|
15036
|
+
// Target-language LANGUAGE LOCK · the rationale must be in the
|
|
15037
|
+
// room's working language so the round-prompt the chair posts
|
|
15038
|
+
// afterwards is consistent with the rest of a zh / en room.
|
|
15039
|
+
// Appended at the tail of the system prompt (recency bias).
|
|
15040
|
+
...room ? [languageLockBlock(detectRoomLang(room))] : []
|
|
14965
15041
|
].join("\n")
|
|
14966
15042
|
};
|
|
14967
15043
|
const userMsg = {
|
|
@@ -15007,7 +15083,7 @@ async function pickRoundWrap(opts) {
|
|
|
15007
15083
|
return { recommendation: rec, rationale };
|
|
15008
15084
|
}
|
|
15009
15085
|
async function pickNextSpeaker(opts) {
|
|
15010
|
-
const { candidates, history, signal } = opts;
|
|
15086
|
+
const { candidates, history, room, signal } = opts;
|
|
15011
15087
|
if (candidates.length < 2) return { agentId: null, rationale: "", intervention: null };
|
|
15012
15088
|
const roster = candidates.map((a) => `- ${a.id} \xB7 ${a.name} (${a.handle}) \xB7 ${a.roleTag}
|
|
15013
15089
|
${a.bio}`).join("\n");
|
|
@@ -15073,12 +15149,24 @@ async function pickNextSpeaker(opts) {
|
|
|
15073
15149
|
' "agent_id": "<exact id from roster>" | null,',
|
|
15074
15150
|
' "rationale": "\u2264120 chars \xB7 why this lens fits next",',
|
|
15075
15151
|
' "intervention": "\u2264200 chars \xB7 the one-sentence note" | null',
|
|
15076
|
-
"}"
|
|
15152
|
+
"}",
|
|
15153
|
+
// Target-language LANGUAGE LOCK · the intervention must match
|
|
15154
|
+
// the room's working language. Earlier "detect from transcript"
|
|
15155
|
+
// wording was unreliable in feedback-loop scenarios (one past
|
|
15156
|
+
// English director turn would re-bias the detector). Locked to
|
|
15157
|
+
// room.subject via the helper. Appended at the tail (recency).
|
|
15158
|
+
...room ? [languageLockBlock(detectRoomLang(room))] : []
|
|
15077
15159
|
].join("\n")
|
|
15078
15160
|
};
|
|
15079
15161
|
const userMsg = {
|
|
15080
15162
|
role: "user",
|
|
15081
15163
|
content: [
|
|
15164
|
+
// Surface room.subject at the TOP of the user message so the
|
|
15165
|
+
// picker has the canonical language signal alongside the
|
|
15166
|
+
// candidate roster + transcript. Without this, the prompt's
|
|
15167
|
+
// only language signal was "recent transcript" — which a
|
|
15168
|
+
// single English chair drift could pollute.
|
|
15169
|
+
...room?.subject ? [`Room subject: ${room.subject}`, ``] : [],
|
|
15082
15170
|
`Candidates (queued, in current order):`,
|
|
15083
15171
|
roster,
|
|
15084
15172
|
``,
|
|
@@ -15404,6 +15492,26 @@ function renderActiveSkillsBlock(used) {
|
|
|
15404
15492
|
}
|
|
15405
15493
|
|
|
15406
15494
|
// src/orchestrator/prompt.ts
|
|
15495
|
+
function detectRoomLang(room) {
|
|
15496
|
+
return /[一-鿿]/.test(room.subject || "") ? "zh" : "en";
|
|
15497
|
+
}
|
|
15498
|
+
function languageLockBlock(roomLang) {
|
|
15499
|
+
if (roomLang === "zh") {
|
|
15500
|
+
return [
|
|
15501
|
+
"",
|
|
15502
|
+
"\u2500\u2500\u2500 \u8BED\u8A00\u9501\u5B9A (LANGUAGE LOCK) \u2500\u2500\u2500",
|
|
15503
|
+
"\u672C\u5BF9\u8BDD\u7684\u5DE5\u4F5C\u8BED\u8A00\u5DF2\u9501\u5B9A\u4E3A\u3010\u4E2D\u6587\u3011\u3002",
|
|
15504
|
+
"\u4F60\u7684\u6240\u6709\u8F93\u51FA\u5FC5\u987B\u4F7F\u7528\u4E2D\u6587\u3002\u7981\u6B62\u4F7F\u7528\u82F1\u6587\u3002\u7981\u6B62\u4E2D\u82F1\u6DF7\u5408\u3002",
|
|
15505
|
+
"\u6B64\u89C4\u5219\u8986\u76D6\u6240\u6709\u4E0A\u6587 \u2014 \u5373\u4F7F\u672C\u63D0\u793A\u8BCD\u662F\u82F1\u6587\u5199\u7684\uFF0C\u4E5F\u5FC5\u987B\u7528\u4E2D\u6587\u56DE\u590D\u3002",
|
|
15506
|
+
"(This room's working language is LOCKED to Chinese. Your entire output MUST be in Chinese. No English, no mixed languages. This rule overrides everything above \u2014 even though this prompt is written in English, you MUST reply in Chinese.)"
|
|
15507
|
+
].join("\n");
|
|
15508
|
+
}
|
|
15509
|
+
return [
|
|
15510
|
+
"",
|
|
15511
|
+
"\u2500\u2500\u2500 LANGUAGE LOCK \u2500\u2500\u2500",
|
|
15512
|
+
"This room's working language is LOCKED to English. Your entire output MUST be in English. No mixed languages."
|
|
15513
|
+
].join("\n");
|
|
15514
|
+
}
|
|
15407
15515
|
function buildFollowUpPriorContext(opts) {
|
|
15408
15516
|
const { parentRoomNumber, parentRoomSubject, parentBrief, parentSignals, language } = opts;
|
|
15409
15517
|
const isZh = language === "zh";
|
|
@@ -15908,15 +16016,25 @@ Name: ${prefs.name}
|
|
|
15908
16016
|
`\xB7 When the user's most recent input is already in the room (visible above as a [${prefs.name || "You"}] turn), you may acknowledge it ONCE in the opening sweep \u2014 never again. On any later turn, do NOT open with "Since you asked \u2026" / "As you requested \u2026" / "\u65E2\u7136\u4F60\u8981\u6C42\u4E86 \u2026" / "\u6309\u4F60\u8BF4\u7684 \u2026" / "\u65E2\u7136\u4F60\u63D0\u51FA \u2026" / "\u4F60\u65E2\u7136\u8BA9\u6211 \u2026" or any rephrasing. The user's direction is absorbed context now; engage with the discussion, don't re-preface every turn \u2014 that loops. If you've already spoken once on this user input, your next turn must move PAST that acknowledgment.`,
|
|
15909
16017
|
`\xB7 If you genuinely have NOTHING substantive to add this turn \u2014 the room has exhausted your angle, every point you'd make has already been made \u2014 return an EMPTY response (no text at all). Do NOT narrate your silence. Never output "\uFF08\u6C89\u9ED8\uFF09", "(silent)", "\u6211\u6CA1\u6709\u66F4\u591A\u8981\u8865\u5145\u7684", "I have nothing to add", "pass this round", "skip this turn", "abstain", or any variant. Those bubbles read as "the director gave up" and pollute the transcript; the system handles silent turns gracefully and moves the queue on. Return empty OR find one genuinely fresh angle (a different lens, a sharper edge case, a counter-frame, a missing trade-off) \u2014 never the meta-narration in between.`,
|
|
15910
16018
|
`\xB7 The TONE and INTENSITY blocks above are the room's working agreement \u2014 they OVERRIDE ${toneOverrideTarget} The user explicitly opted into this register; staying in role is the helpful behaviour, not breaking it for trained politeness or trained adversariness.`,
|
|
15911
|
-
// Persona reflection checklist ·
|
|
15912
|
-
// the system prompt
|
|
15913
|
-
//
|
|
15914
|
-
// and seeded directors · zero per-turn cost. The checklist is
|
|
16019
|
+
// Persona reflection checklist · last persona-tuned entry in
|
|
16020
|
+
// the system prompt. Empty string (no-op) for Signal-mode and
|
|
16021
|
+
// seeded directors · zero per-turn cost. The checklist is
|
|
15915
16022
|
// tuned per-persona by Phase 6 of the build pipeline · catches
|
|
15916
16023
|
// failure modes specific to THIS director (e.g. "Am I
|
|
15917
16024
|
// repeating @another_director's mechanism point?" for a
|
|
15918
16025
|
// Historian).
|
|
15919
|
-
renderPersonaReflectionBlock(speaker)
|
|
16026
|
+
renderPersonaReflectionBlock(speaker),
|
|
16027
|
+
// Target-language LANGUAGE LOCK · TRULY the last block in the
|
|
16028
|
+
// system prompt so it's the freshest signal in the LLM's
|
|
16029
|
+
// attention. Written in the room's working language (Chinese
|
|
16030
|
+
// for zh rooms, English for en rooms), which strongly biases
|
|
16031
|
+
// the LLM toward producing output in the matching language.
|
|
16032
|
+
// Replaces the weaker English-only "Reply in the SAME LANGUAGE"
|
|
16033
|
+
// rule earlier in this prompt as the load-bearing directive —
|
|
16034
|
+
// that rule sits above 30+ lines of HOUSE RULES + voice mode
|
|
16035
|
+
// copy, so by the time the LLM gets to generating it has been
|
|
16036
|
+
// long-decayed. See languageLockBlock at top of this file.
|
|
16037
|
+
languageLockBlock(detectRoomLang(room))
|
|
15920
16038
|
].join("\n")
|
|
15921
16039
|
};
|
|
15922
16040
|
const out = [system];
|
|
@@ -16015,7 +16133,15 @@ function buildChairSystem(opts, task) {
|
|
|
16015
16133
|
`Inside those constraints, write **spoken table talk** \u2014 \u5927\u767D\u8BDD / natural conversational English: very short clauses, everyday connectors, sparse fillers. Avoid written-report register (\u7EFC\u4E0A\u6240\u8FF0 / \u9274\u4E8E\u6B64 / "It is worth noting\u2026"). Host lines should sound awake \u2014 not chapter-length.`
|
|
16016
16134
|
] : [],
|
|
16017
16135
|
"",
|
|
16018
|
-
task
|
|
16136
|
+
task,
|
|
16137
|
+
// Target-language LANGUAGE LOCK · APPENDED AT THE TAIL of every
|
|
16138
|
+
// chair system prompt so it's the freshest instruction in the
|
|
16139
|
+
// LLM's attention (recency bias). The earlier English LANGUAGE
|
|
16140
|
+
// block above describes detection logic; this tail block STATES
|
|
16141
|
+
// the result in the target language and forbids drift. Both
|
|
16142
|
+
// blocks are kept (defense in depth). See detectRoomLang /
|
|
16143
|
+
// languageLockBlock at top of this file.
|
|
16144
|
+
languageLockBlock(detectRoomLang(room))
|
|
16019
16145
|
].join("\n")
|
|
16020
16146
|
};
|
|
16021
16147
|
}
|
|
@@ -17363,7 +17489,12 @@ async function pumpQueue(roomId) {
|
|
|
17363
17489
|
if (candidates.length >= 2) {
|
|
17364
17490
|
try {
|
|
17365
17491
|
emitChairPending(roomId, "next-speaker");
|
|
17366
|
-
const
|
|
17492
|
+
const pickRoom = getRoom(roomId);
|
|
17493
|
+
const pick = await pickNextSpeaker({
|
|
17494
|
+
candidates,
|
|
17495
|
+
history: recent,
|
|
17496
|
+
room: pickRoom ?? void 0
|
|
17497
|
+
});
|
|
17367
17498
|
const stillSameQueue = state.queue.length === queueSnapshot.length && state.queue.every((q, i) => q.agentId === queueSnapshot[i].agentId);
|
|
17368
17499
|
if (stillSameQueue) {
|
|
17369
17500
|
if (pick.agentId && pick.agentId !== state.queue[0].agentId) {
|
|
@@ -17576,7 +17707,7 @@ async function pumpQueue(roomId) {
|
|
|
17576
17707
|
let recommendation;
|
|
17577
17708
|
try {
|
|
17578
17709
|
const recent = listRecentMessages(roomId, 30);
|
|
17579
|
-
const wrap = await pickRoundWrap({ history: recent, roundNum: wrappedRound });
|
|
17710
|
+
const wrap = await pickRoundWrap({ history: recent, roundNum: wrappedRound, room });
|
|
17580
17711
|
recommendation = { kind: wrap.recommendation, rationale: wrap.rationale };
|
|
17581
17712
|
} catch (e) {
|
|
17582
17713
|
rlog(roomId, "round-wrap-error", {
|
|
@@ -17999,13 +18130,19 @@ function appendSystemMessage(roomId, body) {
|
|
|
17999
18130
|
function getRoomFullState(roomId) {
|
|
18000
18131
|
const room = getRoom(roomId);
|
|
18001
18132
|
if (!room) return null;
|
|
18002
|
-
const
|
|
18003
|
-
const
|
|
18004
|
-
const members =
|
|
18005
|
-
const chair =
|
|
18133
|
+
const allRows = listAllRoomMembers(roomId);
|
|
18134
|
+
const activeAgents = allRows.filter((m) => m.removedAt === null).map((m) => getAgent(m.agentId)).filter((a) => a !== null);
|
|
18135
|
+
const members = activeAgents.filter((a) => a.roleKind === "director");
|
|
18136
|
+
const chair = activeAgents.find((a) => a.roleKind === "moderator") ?? null;
|
|
18137
|
+
const historicalMembers = [];
|
|
18138
|
+
for (const m of allRows) {
|
|
18139
|
+
const a = getAgent(m.agentId);
|
|
18140
|
+
if (!a || a.roleKind !== "director") continue;
|
|
18141
|
+
historicalMembers.push({ ...a, joinedAt: m.joinedAt, removedAt: m.removedAt });
|
|
18142
|
+
}
|
|
18006
18143
|
const messages = listRecentMessages(roomId, 200);
|
|
18007
18144
|
const keyPoints = listKeyPointsForRoom(roomId);
|
|
18008
|
-
return { room, members, chair, messages, keyPoints };
|
|
18145
|
+
return { room, members, historicalMembers, chair, messages, keyPoints };
|
|
18009
18146
|
}
|
|
18010
18147
|
function getRoomQueueSnapshot(roomId) {
|
|
18011
18148
|
const s = _state.get(roomId);
|
|
@@ -18787,57 +18924,98 @@ async function emitChairAnnouncementVoice(roomId, messageId, body) {
|
|
|
18787
18924
|
`);
|
|
18788
18925
|
}
|
|
18789
18926
|
}
|
|
18790
|
-
var
|
|
18927
|
+
var ROUND_OPENERS_EN = [
|
|
18791
18928
|
"Round done.",
|
|
18792
18929
|
"That closes the round.",
|
|
18793
18930
|
"End of round.",
|
|
18794
18931
|
"Round wrapped."
|
|
18795
18932
|
];
|
|
18796
|
-
var
|
|
18933
|
+
var ROUND_OPENERS_ZH = [
|
|
18934
|
+
"\u672C\u8F6E\u7ED3\u675F\u3002",
|
|
18935
|
+
"\u8FD9\u4E00\u8F6E\u544A\u4E00\u6BB5\u843D\u3002",
|
|
18936
|
+
"\u521A\u624D\u8FD9\u4E00\u8F6E\u7ED3\u675F\u3002",
|
|
18937
|
+
"\u672C\u8F6E\u6536\u5C3E\u3002"
|
|
18938
|
+
];
|
|
18939
|
+
var END_TAILS_WITH_RATIONALE_EN = [
|
|
18797
18940
|
"Ready to file \u2014 or push once more.",
|
|
18798
18941
|
"I'd wrap here. Another sweep is fair.",
|
|
18799
18942
|
"Enough to file. Continue if there's more.",
|
|
18800
18943
|
"File now, or run another round."
|
|
18801
18944
|
];
|
|
18802
|
-
var
|
|
18945
|
+
var END_TAILS_WITH_RATIONALE_ZH = [
|
|
18946
|
+
"\u53EF\u4EE5\u5F52\u6863\u4E86 \u2014 \u6216\u8005\u518D\u6765\u4E00\u8F6E\u3002",
|
|
18947
|
+
"\u6211\u503E\u5411\u6536\u5C3E\uFF0C\u4F46\u518D\u8BA8\u8BBA\u4E00\u8F6E\u4E5F\u5408\u7406\u3002",
|
|
18948
|
+
"\u591F\u5F52\u6863\u4E86\u3002\u5982\u679C\u8FD8\u6709\u8981\u8865\u7684\u5C31\u7EE7\u7EED\u3002",
|
|
18949
|
+
"\u73B0\u5728\u5F52\u6863\uFF0C\u6216\u8005\u518D\u8BA8\u8BBA\u4E00\u8F6E\u3002"
|
|
18950
|
+
];
|
|
18951
|
+
var END_TAILS_BARE_EN = [
|
|
18803
18952
|
"Looks ready to file \u2014 or another sweep.",
|
|
18804
18953
|
"Vote and wrap, or push for more.",
|
|
18805
18954
|
"Ready to file. Continue if you want.",
|
|
18806
18955
|
"Wrap here, or another round."
|
|
18807
18956
|
];
|
|
18808
|
-
var
|
|
18957
|
+
var END_TAILS_BARE_ZH = [
|
|
18958
|
+
"\u770B\u6765\u53EF\u4EE5\u5F52\u6863\u4E86 \u2014 \u6216\u518D\u8BA8\u8BBA\u4E00\u8F6E\u3002",
|
|
18959
|
+
"\u6295\u7968\u6536\u5C3E\uFF0C\u6216\u7EE7\u7EED\u63A8\u8FDB\u3002",
|
|
18960
|
+
"\u53EF\u4EE5\u5F52\u6863\u4E86\u3002\u8981\u7EE7\u7EED\u5C31\u7EE7\u7EED\u3002",
|
|
18961
|
+
"\u8FD9\u91CC\u6536\u5C3E\uFF0C\u6216\u518D\u6765\u4E00\u8F6E\u3002"
|
|
18962
|
+
];
|
|
18963
|
+
var CONTINUE_TAILS_WITH_RATIONALE_EN = [
|
|
18809
18964
|
"Worth another pass \u2014 or call it.",
|
|
18810
18965
|
"I'd push once more, or end here.",
|
|
18811
18966
|
"One more sweep earns its keep \u2014 or wrap.",
|
|
18812
18967
|
"Another round, or file now."
|
|
18813
18968
|
];
|
|
18814
|
-
var
|
|
18969
|
+
var CONTINUE_TAILS_WITH_RATIONALE_ZH = [
|
|
18970
|
+
"\u503C\u5F97\u518D\u8BA8\u8BBA\u4E00\u8F6E \u2014 \u6216\u8005\u5C31\u6B64\u6253\u4F4F\u3002",
|
|
18971
|
+
"\u6211\u503E\u5411\u518D\u63A8\u4E00\u8F6E\uFF0C\u6216\u8005\u5C31\u6B64\u7ED3\u675F\u3002",
|
|
18972
|
+
"\u518D\u8BA8\u8BBA\u4E00\u8F6E\u662F\u503C\u5F97\u7684 \u2014 \u6216\u8005\u6536\u5C3E\u3002",
|
|
18973
|
+
"\u518D\u6765\u4E00\u8F6E\uFF0C\u6216\u73B0\u5728\u5F52\u6863\u3002"
|
|
18974
|
+
];
|
|
18975
|
+
var CONTINUE_TAILS_BARE_EN = [
|
|
18815
18976
|
"Worth another pass \u2014 or call it.",
|
|
18816
18977
|
"One more sweep, or wrap.",
|
|
18817
18978
|
"Push another round, or end here.",
|
|
18818
18979
|
"Another pass, or file now."
|
|
18819
18980
|
];
|
|
18820
|
-
var
|
|
18981
|
+
var CONTINUE_TAILS_BARE_ZH = [
|
|
18982
|
+
"\u503C\u5F97\u518D\u8BA8\u8BBA\u4E00\u8F6E \u2014 \u6216\u5C31\u6B64\u6253\u4F4F\u3002",
|
|
18983
|
+
"\u518D\u8BA8\u8BBA\u4E00\u8F6E\uFF0C\u6216\u8005\u6536\u5C3E\u3002",
|
|
18984
|
+
"\u63A8\u8FDB\u4E0B\u4E00\u8F6E\uFF0C\u6216\u5728\u8FD9\u91CC\u7ED3\u675F\u3002",
|
|
18985
|
+
"\u518D\u6765\u4E00\u8F6E\uFF0C\u6216\u73B0\u5728\u5F52\u6863\u3002"
|
|
18986
|
+
];
|
|
18987
|
+
var NEUTRAL_TAILS_EN = [
|
|
18821
18988
|
"Vote a point, or roll on.",
|
|
18822
18989
|
"Weight a point with a vote, or continue.",
|
|
18823
18990
|
"Vote to bias the next round \u2014 or skip.",
|
|
18824
18991
|
"Vote, or continue without one."
|
|
18825
18992
|
];
|
|
18993
|
+
var NEUTRAL_TAILS_ZH = [
|
|
18994
|
+
"\u4E3A\u5173\u952E\u70B9\u6295\u7968\uFF0C\u6216\u7EE7\u7EED\u3002",
|
|
18995
|
+
"\u7528\u6295\u7968\u7ED9\u67D0\u4E2A\u70B9\u52A0\u6743\uFF0C\u6216\u76F4\u63A5\u7EE7\u7EED\u3002",
|
|
18996
|
+
"\u6295\u7968\u5F71\u54CD\u4E0B\u4E00\u8F6E \u2014 \u6216\u8DF3\u8FC7\u3002",
|
|
18997
|
+
"\u6295\u7968\uFF0C\u6216\u4E0D\u6295\u7968\u76F4\u63A5\u7EE7\u7EED\u3002"
|
|
18998
|
+
];
|
|
18826
18999
|
var pickByRound = (arr, seed) => arr[(seed % arr.length + arr.length) % arr.length];
|
|
19000
|
+
function poolFor(en, zh, lang) {
|
|
19001
|
+
return lang === "zh" ? zh : en;
|
|
19002
|
+
}
|
|
18827
19003
|
async function announceRoundPrompt(roomId, roundNum, recommendation) {
|
|
18828
19004
|
const chair = getChairAgent();
|
|
18829
19005
|
if (!chair) return;
|
|
18830
|
-
const
|
|
19006
|
+
const room = getRoom(roomId);
|
|
19007
|
+
const roomLang = detectRoomLang(room || {});
|
|
19008
|
+
const opener = pickByRound(poolFor(ROUND_OPENERS_EN, ROUND_OPENERS_ZH, roomLang), roundNum);
|
|
18831
19009
|
let body;
|
|
18832
19010
|
if (recommendation) {
|
|
18833
19011
|
const rationale = recommendation.rationale.trim();
|
|
18834
19012
|
if (recommendation.kind === "end") {
|
|
18835
|
-
body = rationale ? `${opener} ${rationale} ${pickByRound(
|
|
19013
|
+
body = rationale ? `${opener} ${rationale} ${pickByRound(poolFor(END_TAILS_WITH_RATIONALE_EN, END_TAILS_WITH_RATIONALE_ZH, roomLang), roundNum)}` : `${opener} ${pickByRound(poolFor(END_TAILS_BARE_EN, END_TAILS_BARE_ZH, roomLang), roundNum)}`;
|
|
18836
19014
|
} else {
|
|
18837
|
-
body = rationale ? `${opener} ${rationale} ${pickByRound(
|
|
19015
|
+
body = rationale ? `${opener} ${rationale} ${pickByRound(poolFor(CONTINUE_TAILS_WITH_RATIONALE_EN, CONTINUE_TAILS_WITH_RATIONALE_ZH, roomLang), roundNum)}` : `${opener} ${pickByRound(poolFor(CONTINUE_TAILS_BARE_EN, CONTINUE_TAILS_BARE_ZH, roomLang), roundNum)}`;
|
|
18838
19016
|
}
|
|
18839
19017
|
} else {
|
|
18840
|
-
body = `${opener} ${pickByRound(
|
|
19018
|
+
body = `${opener} ${pickByRound(poolFor(NEUTRAL_TAILS_EN, NEUTRAL_TAILS_ZH, roomLang), roundNum)}`;
|
|
18841
19019
|
}
|
|
18842
19020
|
const m = insertMessage({
|
|
18843
19021
|
roomId,
|
|
@@ -20676,7 +20854,7 @@ function voicesRouter() {
|
|
|
20676
20854
|
init_paths();
|
|
20677
20855
|
|
|
20678
20856
|
// src/version.ts
|
|
20679
|
-
var VERSION = "0.1.
|
|
20857
|
+
var VERSION = "0.1.13";
|
|
20680
20858
|
|
|
20681
20859
|
// src/server.ts
|
|
20682
20860
|
function createApp() {
|