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 +1 -1
- package/src/core/processor.ts +25 -1
- package/src/core/prompt-context-builder.ts +5 -10
- package/src/core/room-manager.ts +8 -2
- package/src/prompts/response/index.ts +8 -2
- package/src/prompts/response/types.ts +2 -0
- package/src/prompts/room/index.ts +8 -2
- package/src/prompts/room/sections.ts +16 -0
- package/src/prompts/room/types.ts +3 -2
- package/tui/src/components/MessageList.tsx +1 -0
- package/tui/src/components/RoomMessageList.tsx +1 -0
package/package.json
CHANGED
package/src/core/processor.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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,
|
package/src/core/room-manager.ts
CHANGED
|
@@ -32,7 +32,11 @@ async function queueRoomPersonaResponses(
|
|
|
32
32
|
isTUI: boolean,
|
|
33
33
|
onRoomMessageQueued: (roomId: string) => void
|
|
34
34
|
): Promise<void> {
|
|
35
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
51
|
+
history: Message[];
|
|
51
52
|
isTUI: boolean;
|
|
52
53
|
tools?: ToolDefinition[];
|
|
53
54
|
}
|