ei-tui 0.5.2 → 0.5.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ei-tui",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "author": "Flare576",
5
5
  "repository": {
6
6
  "type": "git",
@@ -24,6 +24,7 @@ import {
24
24
  type ToolProvider,
25
25
  } from "./types.js";
26
26
  import { buildPersonaFromPersonPrompt } from "../prompts/index.js";
27
+ import { buildSiblingAwarenessSection } from "../prompts/room/index.js";
27
28
  import type { PersonaGenerationResult } from "../prompts/generation/types.js";
28
29
 
29
30
  import type { Storage } from "../storage/interface.js";
@@ -1024,8 +1025,9 @@ export class Processor {
1024
1025
  const isBackingOff = retryAfter !== null && retryAfter > new Date().toISOString();
1025
1026
 
1026
1027
  if (!isBackingOff) {
1027
- const request = this.stateManager.queue_claimHighest();
1028
+ let request = this.stateManager.queue_claimHighest();
1028
1029
  if (request) {
1030
+ request = this.augmentRoomRequest(request);
1029
1031
  const personaId = request.data.personaId as string | undefined;
1030
1032
  const personaDisplayName = request.data.personaDisplayName as string | undefined;
1031
1033
  const personaSuffix = personaDisplayName ? ` [${personaDisplayName}]` : "";
@@ -1412,6 +1414,28 @@ const toolNextSteps = new Set([
1412
1414
  });
1413
1415
  }
1414
1416
 
1417
+ private augmentRoomRequest(request: LLMRequest): LLMRequest {
1418
+ if (request.next_step !== LLMNextStep.HandleRoomResponse) return request;
1419
+
1420
+ const roomId = request.data.roomId as string | undefined;
1421
+ const parentMessageId = request.data.parentMessageId as string | undefined;
1422
+ const personaDisplayName = request.data.personaDisplayName as string | undefined;
1423
+
1424
+ if (!roomId || !parentMessageId || !personaDisplayName) return request;
1425
+
1426
+ const siblings = this.stateManager.getRoomChildren(roomId, parentMessageId)
1427
+ .filter((m: RoomMessage) => m.role === "persona" && m.verbal_response)
1428
+ .map((m: RoomMessage) => ({
1429
+ name: this.stateManager.persona_getById(m.persona_id ?? "")?.display_name ?? "Participant",
1430
+ verbal_response: m.verbal_response!,
1431
+ }));
1432
+
1433
+ if (siblings.length === 0) return request;
1434
+
1435
+ const siblingSection = buildSiblingAwarenessSection(siblings, personaDisplayName);
1436
+ return { ...request, system: request.system + "\n\n" + siblingSection };
1437
+ }
1438
+
1415
1439
  private classifyLLMError(error: string): string {
1416
1440
  const match = error.match(/\((\d{3})\)/);
1417
1441
  if (match) {
@@ -3,7 +3,8 @@ import { StateManager } from "./state-manager.js";
3
3
  import { getEmbeddingService, findTopK } from "./embedding-service.js";
4
4
  import type { ResponsePromptData, PromptOutput } from "../prompts/index.js";
5
5
  import { buildRoomResponsePrompt } from "../prompts/room/index.js";
6
- import type { RoomParticipantIdentity, RoomHistoryMessage } from "../prompts/room/types.js";
6
+ import type { RoomParticipantIdentity } from "../prompts/room/types.js";
7
+ import { normalizeRoomMessages } from "./handlers/utils.js";
7
8
 
8
9
  const QUOTE_LIMIT = 10;
9
10
  const DATA_ITEM_LIMIT = 15;
@@ -205,6 +206,7 @@ export async function buildResponsePromptData(
205
206
  traits: persona.traits,
206
207
  topics: persona.topics,
207
208
  interested_topics: persona.topics.filter(t => t.exposure_desired - t.exposure_current > 0.2),
209
+ include_message_timestamps: persona.include_message_timestamps,
208
210
  },
209
211
  human: filteredHuman,
210
212
  visible_personas: visiblePersonas,
@@ -231,15 +233,7 @@ export async function buildRoomResponsePromptData(
231
233
 
232
234
  const filteredHuman = await filterHumanDataByVisibility(human, respondingPersona, currentMessage);
233
235
 
234
- const history: RoomHistoryMessage[] = sourceMessages.map(m => ({
235
- speaker_name: m.role === "human"
236
- ? (human.settings?.name_display ?? "Human")
237
- : (sm.persona_getById(m.persona_id ?? "")?.display_name ?? m.persona_id ?? "Unknown"),
238
- speaker_id: m.role === "human" ? "human" : (m.persona_id ?? ""),
239
- verbal_response: m.verbal_response,
240
- action_response: m.action_response,
241
- silence_reason: m.silence_reason,
242
- }));
236
+ const history = normalizeRoomMessages(sourceMessages, sm);
243
237
 
244
238
  const otherParticipants: RoomParticipantIdentity[] = [];
245
239
  for (const pid of room.persona_ids) {
@@ -273,6 +267,7 @@ export async function buildRoomResponsePromptData(
273
267
  long_description: respondingPersona.long_description,
274
268
  traits: respondingPersona.traits,
275
269
  topics: respondingPersona.topics,
270
+ include_message_timestamps: respondingPersona.include_message_timestamps,
276
271
  },
277
272
  other_participants: otherParticipants,
278
273
  human: filteredHuman,
@@ -32,7 +32,11 @@ async function queueRoomPersonaResponses(
32
32
  isTUI: boolean,
33
33
  onRoomMessageQueued: (roomId: string) => void
34
34
  ): Promise<void> {
35
- for (const personaId of room.persona_ids) {
35
+ const personaIds = room.mode === RoomMode.FreeForAll
36
+ ? [...room.persona_ids].sort(() => Math.random() - 0.5)
37
+ : room.persona_ids;
38
+
39
+ for (const personaId of personaIds) {
36
40
  const persona = sm.persona_getById(personaId);
37
41
  if (!persona || persona.is_archived || persona.is_paused) continue;
38
42
  if (room.mode === RoomMode.MessagesAgainstPersona && room.judge_persona_id === personaId) continue;
@@ -186,7 +190,9 @@ export async function sendFfaMessage(
186
190
  .map(q => q.data.personaId as string)
187
191
  );
188
192
 
189
- for (const personaId of updatedRoom.persona_ids) {
193
+ const shuffledIds = [...updatedRoom.persona_ids].sort(() => Math.random() - 0.5);
194
+
195
+ for (const personaId of shuffledIds) {
190
196
  if (alreadyQueued.has(personaId)) continue;
191
197
  const persona = sm.persona_getById(personaId);
192
198
  if (!persona || persona.is_archived || persona.is_paused) continue;
@@ -54,6 +54,9 @@ Your role is unique among personas:
54
54
  const toolsSection = (data.tools && data.tools.length > 0) ? buildToolsSection() : "";
55
55
  const currentTime = formatCurrentTime();
56
56
  const conversationState = getConversationStateText(data.delay_ms);
57
+ const timestampNote = data.persona.include_message_timestamps
58
+ ? `\nNote: Timestamps are shown to help you understand time context — the user sees them too, no need to echo or reference them.`
59
+ : "";
57
60
 
58
61
  return `${identity}
59
62
 
@@ -71,7 +74,7 @@ ${priorities}
71
74
 
72
75
  ${responseFormat}${toolsSection ? `\n\n${toolsSection}` : ""}
73
76
 
74
- Current time: ${currentTime}
77
+ Current time: ${currentTime}${timestampNote}
75
78
  ${conversationState}
76
79
 
77
80
  ## Final Instructions
@@ -99,6 +102,9 @@ function buildStandardSystemPrompt(data: ResponsePromptData): string {
99
102
  const toolsSection = (data.tools && data.tools.length > 0) ? buildToolsSection() : "";
100
103
  const currentTime = formatCurrentTime();
101
104
  const conversationState = getConversationStateText(data.delay_ms);
105
+ const timestampNote = data.persona.include_message_timestamps
106
+ ? `\nNote: Timestamps are shown to help you understand time context — the user sees them too, no need to echo or reference them.`
107
+ : "";
102
108
 
103
109
  return `${identity}
104
110
 
@@ -115,7 +121,7 @@ ${priorities}
115
121
 
116
122
  ${responseFormat}${toolsSection ? `\n\n${toolsSection}` : ""}
117
123
 
118
- Current time: ${currentTime}
124
+ Current time: ${currentTime}${timestampNote}
119
125
  ${conversationState}
120
126
 
121
127
  ## Final Instructions
@@ -19,6 +19,8 @@ export interface ResponsePromptData {
19
19
  topics: PersonaTopic[];
20
20
  /** Pre-filtered: topics where exposure_desired - exposure_current > 0.2 */
21
21
  interested_topics: PersonaTopic[];
22
+ /** When true, each message has a timestamp prepended; include a note so the persona doesn't echo them */
23
+ include_message_timestamps?: boolean;
22
24
  };
23
25
  human: {
24
26
  facts: Fact[];
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type { RoomResponsePromptData, RoomJudgePromptData, PromptOutput } from "./types.js";
6
6
  import { formatCurrentTime } from "../../core/format-utils.js";
7
+ import { formatMessagesAsPlaceholders } from "../message-utils.js";
7
8
  import {
8
9
  buildRoomParticipantsSection,
9
10
  buildRoomHistorySection,
@@ -14,6 +15,8 @@ import {
14
15
  buildJudgeCandidatesSection,
15
16
  buildJudgeDecisionFormatSection,
16
17
  } from "./sections.js";
18
+
19
+ export { buildSiblingAwarenessSection } from "./sections.js";
17
20
  import {
18
21
  buildHumanSection,
19
22
  buildQuotesSection,
@@ -48,6 +51,9 @@ export function buildRoomResponsePrompt(data: RoomResponsePromptData): PromptOut
48
51
  const responseFormat = buildRoomResponseFormatSection();
49
52
  const toolsSection = tools && tools.length > 0 ? buildToolsSection() : "";
50
53
  const currentTime = formatCurrentTime();
54
+ const timestampNote = persona.include_message_timestamps
55
+ ? `Note: Timestamps are shown to help you understand time context — the user sees them too, no need to echo or reference them.`
56
+ : "";
51
57
 
52
58
  const system = [
53
59
  identity,
@@ -61,10 +67,10 @@ export function buildRoomResponsePrompt(data: RoomResponsePromptData): PromptOut
61
67
  guidelines,
62
68
  responseFormat,
63
69
  toolsSection,
64
- `Current time: ${currentTime}`,
70
+ `Current time: ${currentTime}${timestampNote ? `\n${timestampNote}` : ""}`,
65
71
  ].filter(Boolean).join("\n\n");
66
72
 
67
- const user = buildRoomHistorySection(history) +
73
+ const user = formatMessagesAsPlaceholders(history, name) +
68
74
  `\n\nRespond to the conversation above as ${name}. Call the \`submit_response\` tool with your response. If the tool is unavailable, use the JSON format in the Response Format section.`;
69
75
 
70
76
  return { system, user };
@@ -123,6 +123,22 @@ Rules:
123
123
  - If the \`submit_response\` tool is unavailable, return the JSON object directly as your entire reply — no prose, no preamble`;
124
124
  }
125
125
 
126
+ export function buildSiblingAwarenessSection(
127
+ siblings: Array<{ name: string; verbal_response: string }>,
128
+ personaName: string
129
+ ): string {
130
+ if (siblings.length === 0) return "";
131
+ const lines = siblings.map(s => `**${s.name}**: "${s.verbal_response}"`);
132
+ const header = siblings.length === 1
133
+ ? "## Another voice has already responded this round"
134
+ : "## Others have already responded this round";
135
+ return `${header}
136
+
137
+ ${lines.join("\n\n")}
138
+
139
+ Find the angle that's distinctly yours on this same moment — don't try to cover more ground, just be the version of this reaction that only *${personaName}* could give.`;
140
+ }
141
+
126
142
  export function buildJudgeCandidatesSection(candidates: RoomJudgeCandidate[]): string {
127
143
  const lines = candidates.map((c, i) => {
128
144
  const speaker = c.speaker_id === "human" ? "Human" : c.speaker_name;
@@ -2,7 +2,7 @@
2
2
  * Room Prompt Types
3
3
  */
4
4
 
5
- import type { Fact, PersonaTrait, Topic, Person, Quote, PersonaTopic, ToolDefinition } from "../../core/types.js";
5
+ import type { Fact, Message, PersonaTrait, Topic, Person, Quote, PersonaTopic, ToolDefinition } from "../../core/types.js";
6
6
  import type { RoomMode } from "../../core/types.js";
7
7
 
8
8
  export interface RoomParticipantIdentity {
@@ -35,6 +35,7 @@ export interface RoomResponsePromptData {
35
35
  long_description?: string;
36
36
  traits: PersonaTrait[];
37
37
  topics: PersonaTopic[];
38
+ include_message_timestamps?: boolean;
38
39
  };
39
40
  other_participants: RoomParticipantIdentity[];
40
41
  human: {
@@ -47,7 +48,7 @@ export interface RoomResponsePromptData {
47
48
  /** Pre-filtered: topics where exposure_desired - exposure_current > 0.2 */
48
49
  interested_topics: Topic[];
49
50
  };
50
- history: RoomHistoryMessage[];
51
+ history: Message[];
51
52
  isTUI: boolean;
52
53
  tools?: ToolDefinition[];
53
54
  }
@@ -99,6 +99,7 @@ export function MessageList() {
99
99
  backgroundColor="#0f1419"
100
100
  stickyScroll={true}
101
101
  stickyStart="bottom"
102
+ viewportCulling={true}
102
103
  >
103
104
  <For each={messagesWithQuotes()}>
104
105
  {(message, index) => {
@@ -126,6 +126,7 @@ export function RoomMessageList() {
126
126
  backgroundColor="#0f1419"
127
127
  stickyScroll={true}
128
128
  stickyStart="bottom"
129
+ viewportCulling={true}
129
130
  >
130
131
  <For each={displayMessagesWithQuotes()}>
131
132
  {(msg) => {