ei-tui 0.1.4 → 0.1.6
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/README.md +6 -2
- package/package.json +3 -3
- package/src/cli/README.md +2 -0
- package/src/core/handlers/index.ts +106 -16
- package/src/core/orchestrators/ceremony.ts +3 -3
- package/src/core/orchestrators/extraction-chunker.ts +3 -3
- package/src/core/processor.ts +74 -33
- package/src/core/queue-processor.ts +3 -26
- package/src/core/state/personas.ts +13 -1
- package/src/core/state/queue.ts +79 -13
- package/src/core/state-manager.ts +35 -3
- package/src/core/types.ts +19 -6
- package/src/integrations/opencode/importer.ts +1 -1
- package/src/prompts/heartbeat/check.ts +27 -13
- package/src/prompts/heartbeat/ei.ts +5 -4
- package/src/prompts/human/item-update.ts +20 -8
- package/src/prompts/message-utils.ts +38 -1
- package/src/prompts/response/index.ts +13 -6
- package/src/prompts/response/sections.ts +90 -12
- package/src/prompts/response/types.ts +10 -0
- package/tui/README.md +12 -1
- package/tui/src/commands/dlq.ts +75 -0
- package/tui/src/commands/queue.ts +77 -0
- package/tui/src/components/MessageList.tsx +12 -2
- package/tui/src/components/PromptInput.tsx +7 -1
- package/tui/src/components/StatusBar.tsx +12 -5
- package/tui/src/context/ei.tsx +33 -3
- package/tui/src/context/keyboard.tsx +2 -2
- package/tui/src/util/yaml-serializers.ts +70 -8
package/README.md
CHANGED
|
@@ -92,7 +92,11 @@ More information can be found in the [Web Readme](web/README.md)
|
|
|
92
92
|
|
|
93
93
|
### TUI
|
|
94
94
|
|
|
95
|
-
```
|
|
95
|
+
```bash
|
|
96
|
+
# Install Bun (if you don't have it)
|
|
97
|
+
curl -fsSL https://bun.sh/install | bash
|
|
98
|
+
|
|
99
|
+
# Install Ei
|
|
96
100
|
npm install -g ei-tui
|
|
97
101
|
```
|
|
98
102
|
|
|
@@ -126,7 +130,7 @@ This project is separated into five (5) logical parts:
|
|
|
126
130
|
|
|
127
131
|
## Requirements
|
|
128
132
|
|
|
129
|
-
- [Bun](https://bun.sh) runtime (>=1.0.0)
|
|
133
|
+
- [Bun](https://bun.sh) runtime (>=1.0.0) — install with `curl -fsSL https://bun.sh/install | bash`
|
|
130
134
|
- A local LLM (LM Studio, Ollama, etc.) OR API access to a cloud provider (Anthropic, OpenAI, Bedrock, your uncle's LLM farm, etc.)
|
|
131
135
|
|
|
132
136
|
## LM Studio Setup
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ei-tui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"author": "Flare576",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
|
-
"url": "git+https://github.com/
|
|
7
|
+
"url": "git+https://github.com/Flare576/ei.git"
|
|
8
8
|
},
|
|
9
9
|
"engines": {
|
|
10
10
|
"bun": ">=1.0.0"
|
|
@@ -64,4 +64,4 @@
|
|
|
64
64
|
"solid-js": "1.9.9",
|
|
65
65
|
"yaml": "^2.8.2"
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|
package/src/cli/README.md
CHANGED
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
PersonaTopicMatchResult,
|
|
24
24
|
PersonaTopicUpdateResult,
|
|
25
25
|
} from "../../prompts/persona/types.js";
|
|
26
|
+
import type { PersonaResponseResult } from "../../prompts/response/index.js";
|
|
26
27
|
|
|
27
28
|
import type {
|
|
28
29
|
PersonaExpireResult,
|
|
@@ -106,20 +107,66 @@ function handlePersonaResponse(response: LLMResponse, state: StateManager): void
|
|
|
106
107
|
// the messages were "seen" and processed
|
|
107
108
|
state.messages_markPendingAsRead(personaId);
|
|
108
109
|
|
|
110
|
+
// Structured JSON path: queue-processor parsed valid JSON into `parsed`
|
|
111
|
+
if (response.parsed !== undefined) {
|
|
112
|
+
const result = response.parsed as PersonaResponseResult;
|
|
113
|
+
|
|
114
|
+
if (!result.should_respond) {
|
|
115
|
+
const reason = result.reason;
|
|
116
|
+
if (reason) {
|
|
117
|
+
console.log(`[handlePersonaResponse] ${personaDisplayName} chose silence: ${reason}`);
|
|
118
|
+
const silentMessage: Message = {
|
|
119
|
+
id: crypto.randomUUID(),
|
|
120
|
+
role: "system",
|
|
121
|
+
silence_reason: reason,
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
read: false,
|
|
124
|
+
context_status: ContextStatus.Default,
|
|
125
|
+
};
|
|
126
|
+
state.messages_append(personaId, silentMessage);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(`[handlePersonaResponse] ${personaDisplayName} chose not to respond (no reason given)`);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Build message with structured fields
|
|
134
|
+
const verbal = result.verbal_response || undefined;
|
|
135
|
+
const action = result.action_response || undefined;
|
|
136
|
+
|
|
137
|
+
if (!verbal && !action) {
|
|
138
|
+
console.log(`[handlePersonaResponse] ${personaDisplayName} JSON had should_respond=true but no content fields`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const message: Message = {
|
|
143
|
+
id: crypto.randomUUID(),
|
|
144
|
+
role: "system",
|
|
145
|
+
verbal_response: verbal,
|
|
146
|
+
action_response: action,
|
|
147
|
+
timestamp: new Date().toISOString(),
|
|
148
|
+
read: false,
|
|
149
|
+
context_status: ContextStatus.Default,
|
|
150
|
+
};
|
|
151
|
+
state.messages_append(personaId, message);
|
|
152
|
+
console.log(`[handlePersonaResponse] Appended structured response to ${personaDisplayName}`);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Legacy plain-text fallback
|
|
109
157
|
if (!response.content) {
|
|
110
|
-
console.log(`[handlePersonaResponse]
|
|
158
|
+
console.log(`[handlePersonaResponse] ${personaDisplayName} chose not to respond (no reason given)`);
|
|
111
159
|
return;
|
|
112
160
|
}
|
|
113
161
|
|
|
114
162
|
const message: Message = {
|
|
115
163
|
id: crypto.randomUUID(),
|
|
116
164
|
role: "system",
|
|
117
|
-
|
|
165
|
+
verbal_response: response.content ?? undefined,
|
|
118
166
|
timestamp: new Date().toISOString(),
|
|
119
167
|
read: false,
|
|
120
168
|
context_status: ContextStatus.Default,
|
|
121
169
|
};
|
|
122
|
-
|
|
123
170
|
state.messages_append(personaId, message);
|
|
124
171
|
console.log(`[handlePersonaResponse] Appended response to ${personaDisplayName}`);
|
|
125
172
|
}
|
|
@@ -150,7 +197,7 @@ function handleHeartbeatCheck(response: LLMResponse, state: StateManager): void
|
|
|
150
197
|
const message: Message = {
|
|
151
198
|
id: crypto.randomUUID(),
|
|
152
199
|
role: "system",
|
|
153
|
-
|
|
200
|
+
verbal_response: result.message,
|
|
154
201
|
timestamp: now,
|
|
155
202
|
read: false,
|
|
156
203
|
context_status: ContextStatus.Default,
|
|
@@ -179,10 +226,10 @@ function handleEiHeartbeat(response: LLMResponse, state: StateManager): void {
|
|
|
179
226
|
return;
|
|
180
227
|
}
|
|
181
228
|
|
|
182
|
-
const sendMessage = (
|
|
229
|
+
const sendMessage = (verbal_response: string) => state.messages_append("ei", {
|
|
183
230
|
id: crypto.randomUUID(),
|
|
184
231
|
role: "system",
|
|
185
|
-
|
|
232
|
+
verbal_response,
|
|
186
233
|
timestamp: now,
|
|
187
234
|
read: false,
|
|
188
235
|
context_status: ContextStatus.Default,
|
|
@@ -731,6 +778,18 @@ async function handleHumanItemUpdate(response: LLMResponse, state: StateManager)
|
|
|
731
778
|
console.log(`[handleHumanItemUpdate] ${isNewItem ? "Created" : "Updated"} ${candidateType} "${result.name}"`);
|
|
732
779
|
}
|
|
733
780
|
|
|
781
|
+
/**
|
|
782
|
+
* Returns the combined display text of a message for quote indexing.
|
|
783
|
+
* Mirrors the rendering logic used in the frontends.
|
|
784
|
+
*/
|
|
785
|
+
function getMessageText(message: Message): string {
|
|
786
|
+
const parts: string[] = [];
|
|
787
|
+
if (message.action_response) parts.push(`_${message.action_response}_`);
|
|
788
|
+
if (message.verbal_response) parts.push(message.verbal_response);
|
|
789
|
+
return parts.join('\n\n');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
|
|
734
793
|
async function validateAndStoreQuotes(
|
|
735
794
|
candidates: Array<{ text: string; reason: string }> | undefined,
|
|
736
795
|
messages: Message[],
|
|
@@ -744,22 +803,53 @@ async function validateAndStoreQuotes(
|
|
|
744
803
|
for (const candidate of candidates) {
|
|
745
804
|
let found = false;
|
|
746
805
|
for (const message of messages) {
|
|
747
|
-
const
|
|
806
|
+
const msgText = getMessageText(message);
|
|
807
|
+
const start = msgText.indexOf(candidate.text);
|
|
748
808
|
if (start !== -1) {
|
|
749
809
|
const end = start + candidate.text.length;
|
|
750
810
|
|
|
811
|
+
// Check for ANY overlapping quote in this message (not just exact match)
|
|
751
812
|
const existing = state.human_quote_getForMessage(message.id);
|
|
752
|
-
const
|
|
813
|
+
const overlapping = existing.find(q =>
|
|
814
|
+
q.start !== null && q.end !== null &&
|
|
815
|
+
start < q.end && end > q.start // ranges overlap
|
|
816
|
+
);
|
|
753
817
|
|
|
754
|
-
if (
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
818
|
+
if (overlapping) {
|
|
819
|
+
// Merge: expand to the union of both ranges
|
|
820
|
+
const mergedStart = Math.min(start, overlapping.start!);
|
|
821
|
+
const mergedEnd = Math.max(end, overlapping.end!);
|
|
822
|
+
const mergedText = msgText.slice(mergedStart, mergedEnd);
|
|
823
|
+
|
|
824
|
+
// Merge data_item_ids and persona_groups (deduplicated)
|
|
825
|
+
const mergedDataItemIds = overlapping.data_item_ids.includes(dataItemId)
|
|
826
|
+
? overlapping.data_item_ids
|
|
827
|
+
: [...overlapping.data_item_ids, dataItemId];
|
|
828
|
+
const group = personaGroup || "General";
|
|
829
|
+
const mergedGroups = overlapping.persona_groups.includes(group)
|
|
830
|
+
? overlapping.persona_groups
|
|
831
|
+
: [...overlapping.persona_groups, group];
|
|
832
|
+
|
|
833
|
+
// Only recompute embedding if the text actually changed
|
|
834
|
+
let embedding = overlapping.embedding;
|
|
835
|
+
if (mergedText !== overlapping.text) {
|
|
836
|
+
try {
|
|
837
|
+
const embeddingService = getEmbeddingService();
|
|
838
|
+
embedding = await embeddingService.embed(mergedText);
|
|
839
|
+
} catch (err) {
|
|
840
|
+
console.warn(`[extraction] Failed to recompute embedding for merged quote: "${mergedText.slice(0, 30)}..."`, err);
|
|
841
|
+
}
|
|
762
842
|
}
|
|
843
|
+
|
|
844
|
+
state.human_quote_update(overlapping.id, {
|
|
845
|
+
start: mergedStart,
|
|
846
|
+
end: mergedEnd,
|
|
847
|
+
text: mergedText,
|
|
848
|
+
data_item_ids: mergedDataItemIds,
|
|
849
|
+
persona_groups: mergedGroups,
|
|
850
|
+
embedding,
|
|
851
|
+
});
|
|
852
|
+
console.log(`[extraction] Merged overlapping quote: "${mergedText.slice(0, 50)}..." (${mergedStart}-${mergedEnd})`);
|
|
763
853
|
found = true;
|
|
764
854
|
break;
|
|
765
855
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep, MESSAGE_MIN_COUNT, MESSAGE_MAX_AGE_DAYS, type CeremonyConfig, type PersonaTopic, type Topic } from "../types.js";
|
|
1
|
+
import { LLMRequestType, LLMPriority, LLMNextStep, MESSAGE_MIN_COUNT, MESSAGE_MAX_AGE_DAYS, type CeremonyConfig, type PersonaTopic, type Topic, type Message } from "../types.js";
|
|
2
2
|
import type { StateManager } from "../state-manager.js";
|
|
3
3
|
import { applyDecayToValue } from "../utils/index.js";
|
|
4
4
|
import {
|
|
@@ -384,12 +384,12 @@ export function queueExplorePhase(personaId: string, state: StateManager): void
|
|
|
384
384
|
});
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
-
function extractConversationThemes(messages:
|
|
387
|
+
function extractConversationThemes(messages: Message[]): string[] {
|
|
388
388
|
const humanMessages = messages.filter(m => m.role === "human");
|
|
389
389
|
if (humanMessages.length === 0) return [];
|
|
390
390
|
|
|
391
391
|
const words = humanMessages
|
|
392
|
-
.map(m => m.
|
|
392
|
+
.map(m => (m.verbal_response ?? '').toLowerCase())
|
|
393
393
|
.join(" ")
|
|
394
394
|
.split(/\s+/)
|
|
395
395
|
.filter(w => w.length > 4);
|
|
@@ -12,7 +12,7 @@ function estimateTokens(text: string): number {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
function estimateMessageTokens(messages: Message[]): number {
|
|
15
|
-
return messages.reduce((sum, msg) => sum + estimateTokens(msg.
|
|
15
|
+
return messages.reduce((sum, msg) => sum + estimateTokens(msg.verbal_response ?? '') + 4, 0);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function fitMessagesFromEnd(messages: Message[], maxTokens: number): Message[] {
|
|
@@ -20,7 +20,7 @@ function fitMessagesFromEnd(messages: Message[], maxTokens: number): Message[] {
|
|
|
20
20
|
let tokens = 0;
|
|
21
21
|
|
|
22
22
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
23
|
-
const msgTokens = estimateTokens(messages[i].
|
|
23
|
+
const msgTokens = estimateTokens(messages[i].verbal_response ?? '') + 4;
|
|
24
24
|
if (tokens + msgTokens > maxTokens) break;
|
|
25
25
|
result.unshift(messages[i]);
|
|
26
26
|
tokens += msgTokens;
|
|
@@ -39,7 +39,7 @@ function pullMessagesFromStart(
|
|
|
39
39
|
let i = startIndex;
|
|
40
40
|
|
|
41
41
|
while (i < messages.length) {
|
|
42
|
-
const msgTokens = estimateTokens(messages[i].
|
|
42
|
+
const msgTokens = estimateTokens(messages[i].verbal_response ?? '') + 4;
|
|
43
43
|
if (tokens + msgTokens > maxTokens && pulled.length > 0) break;
|
|
44
44
|
pulled.push(messages[i]);
|
|
45
45
|
tokens += msgTokens;
|
package/src/core/processor.ts
CHANGED
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
import { EI_WELCOME_MESSAGE, EI_PERSONA_DEFINITION } from "../templates/welcome.js";
|
|
57
57
|
import { getEmbeddingService, findTopK, needsEmbeddingUpdate, needsQuoteEmbeddingUpdate, computeDataItemEmbedding, computeQuoteEmbedding } from "./embedding-service.js";
|
|
58
58
|
import { ContextStatus as ContextStatusEnum } from "./types.js";
|
|
59
|
+
import { buildChatMessageContent } from "../prompts/message-utils.js";
|
|
59
60
|
|
|
60
61
|
// =============================================================================
|
|
61
62
|
// EMBEDDING STRIPPING - Remove embeddings from data items before returning to FE
|
|
@@ -124,6 +125,7 @@ export class Processor {
|
|
|
124
125
|
private currentRequest: LLMRequest | null = null;
|
|
125
126
|
private isTUI = false;
|
|
126
127
|
private lastOpenCodeSync = 0;
|
|
128
|
+
private lastDLQTrim = 0;
|
|
127
129
|
private openCodeImportInProgress = false;
|
|
128
130
|
private pendingConflict: StateConflictData | null = null;
|
|
129
131
|
|
|
@@ -232,7 +234,7 @@ export class Processor {
|
|
|
232
234
|
const welcomeMessage: Message = {
|
|
233
235
|
id: crypto.randomUUID(),
|
|
234
236
|
role: "system",
|
|
235
|
-
|
|
237
|
+
verbal_response: EI_WELCOME_MESSAGE,
|
|
236
238
|
timestamp: new Date().toISOString(),
|
|
237
239
|
read: false,
|
|
238
240
|
context_status: ContextStatusEnum.Always,
|
|
@@ -334,31 +336,36 @@ export class Processor {
|
|
|
334
336
|
await this.checkScheduledTasks();
|
|
335
337
|
|
|
336
338
|
if (this.queueProcessor.getState() === "idle") {
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
this.
|
|
339
|
+
const retryAfter = this.stateManager.queue_nextItemRetryAfter();
|
|
340
|
+
const isBackingOff = retryAfter !== null && retryAfter > new Date().toISOString();
|
|
341
|
+
|
|
342
|
+
if (!isBackingOff) {
|
|
343
|
+
const request = this.stateManager.queue_claimHighest();
|
|
344
|
+
if (request) {
|
|
345
|
+
const personaId = request.data.personaId as string | undefined;
|
|
346
|
+
const personaDisplayName = request.data.personaDisplayName as string | undefined;
|
|
347
|
+
const personaSuffix = personaDisplayName ? ` [${personaDisplayName}]` : "";
|
|
348
|
+
console.log(`[Processor ${this.instanceId}] processing request: ${request.next_step}${personaSuffix}`);
|
|
349
|
+
this.currentRequest = request;
|
|
350
|
+
|
|
351
|
+
if (personaId && request.next_step === LLMNextStep.HandlePersonaResponse) {
|
|
352
|
+
this.interface.onMessageProcessing?.(personaId);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
this.queueProcessor.start(request, async (response) => {
|
|
356
|
+
this.currentRequest = null;
|
|
357
|
+
await this.handleResponse(response);
|
|
358
|
+
const nextState = this.stateManager.queue_isPaused() ? "paused" : "idle";
|
|
359
|
+
// the processor state is set in the caller, so this needs a bit of delay
|
|
360
|
+
setTimeout(() => this.interface.onQueueStateChanged?.(nextState), 0);
|
|
361
|
+
}, {
|
|
362
|
+
accounts: this.stateManager.getHuman().settings?.accounts,
|
|
363
|
+
messageFetcher: (pName) => this.fetchMessagesForLLM(pName),
|
|
364
|
+
rawMessageFetcher: (pName) => this.stateManager.messages_get(pName),
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
this.interface.onQueueStateChanged?.("busy");
|
|
347
368
|
}
|
|
348
|
-
|
|
349
|
-
this.queueProcessor.start(request, async (response) => {
|
|
350
|
-
this.currentRequest = null;
|
|
351
|
-
await this.handleResponse(response);
|
|
352
|
-
const nextState = this.stateManager.queue_isPaused() ? "paused" : "idle";
|
|
353
|
-
// the processor state is set in the caller, so this needs a bit of delay
|
|
354
|
-
setTimeout(() => this.interface.onQueueStateChanged?.(nextState), 0);
|
|
355
|
-
}, {
|
|
356
|
-
accounts: this.stateManager.getHuman().settings?.accounts,
|
|
357
|
-
messageFetcher: (pName) => this.fetchMessagesForLLM(pName),
|
|
358
|
-
rawMessageFetcher: (pName) => this.stateManager.messages_get(pName),
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
this.interface.onQueueStateChanged?.("busy");
|
|
362
369
|
}
|
|
363
370
|
}
|
|
364
371
|
|
|
@@ -407,6 +414,15 @@ export class Processor {
|
|
|
407
414
|
}
|
|
408
415
|
}
|
|
409
416
|
}
|
|
417
|
+
// DLQ rolloff — once per day
|
|
418
|
+
const MS_PER_DAY = 86_400_000;
|
|
419
|
+
if (now - this.lastDLQTrim >= MS_PER_DAY) {
|
|
420
|
+
this.lastDLQTrim = now;
|
|
421
|
+
const trimmed = this.stateManager.queue_trimDLQ();
|
|
422
|
+
if (trimmed > 0) {
|
|
423
|
+
console.log(`[Processor] DLQ trim: removed ${trimmed} expired items`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
410
426
|
}
|
|
411
427
|
|
|
412
428
|
private async checkAndSyncOpenCode(human: HumanEntity, now: number): Promise<void> {
|
|
@@ -484,10 +500,17 @@ export class Processor {
|
|
|
484
500
|
contextWindowHours
|
|
485
501
|
);
|
|
486
502
|
|
|
487
|
-
return filteredHistory
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
503
|
+
return filteredHistory
|
|
504
|
+
.reduce<import("./types.js").ChatMessage[]>((acc, m) => {
|
|
505
|
+
const content = buildChatMessageContent(m);
|
|
506
|
+
if (content.length > 0) {
|
|
507
|
+
acc.push({
|
|
508
|
+
role: m.role === "human" ? "user" : "assistant",
|
|
509
|
+
content,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
return acc;
|
|
513
|
+
}, []);
|
|
491
514
|
}
|
|
492
515
|
|
|
493
516
|
private async queueHeartbeatCheck(personaId: string): Promise<void> {
|
|
@@ -542,7 +565,7 @@ export class Processor {
|
|
|
542
565
|
const items: EiHeartbeatItem[] = [];
|
|
543
566
|
|
|
544
567
|
const unverifiedFacts = human.facts
|
|
545
|
-
.filter(f => f.validated === ValidationLevel.None && f.learned_by !== "
|
|
568
|
+
.filter(f => f.validated === ValidationLevel.None && f.learned_by !== "Ei")
|
|
546
569
|
.slice(0, 5);
|
|
547
570
|
for (const fact of unverifiedFacts) {
|
|
548
571
|
const quote = human.quotes.find(q => q.data_item_ids.includes(fact.id));
|
|
@@ -932,7 +955,7 @@ export class Processor {
|
|
|
932
955
|
.map(m => m.id);
|
|
933
956
|
if (pendingIds.length === 0) return "";
|
|
934
957
|
const removed = this.stateManager.messages_remove(personaId, pendingIds);
|
|
935
|
-
const recalledContent = removed.map(m => m.
|
|
958
|
+
const recalledContent = removed.map(m => m.verbal_response ?? '').join("\n\n");
|
|
936
959
|
this.interface.onMessageAdded?.(personaId);
|
|
937
960
|
this.interface.onMessageRecalled?.(personaId, recalledContent);
|
|
938
961
|
return recalledContent;
|
|
@@ -953,7 +976,7 @@ export class Processor {
|
|
|
953
976
|
const message: Message = {
|
|
954
977
|
id: crypto.randomUUID(),
|
|
955
978
|
role: "human",
|
|
956
|
-
content,
|
|
979
|
+
verbal_response: content,
|
|
957
980
|
timestamp: new Date().toISOString(),
|
|
958
981
|
read: false,
|
|
959
982
|
context_status: "default" as ContextStatus,
|
|
@@ -1469,13 +1492,31 @@ export class Processor {
|
|
|
1469
1492
|
return {
|
|
1470
1493
|
state: this.stateManager.queue_isPaused()
|
|
1471
1494
|
? "paused"
|
|
1472
|
-
: this.
|
|
1495
|
+
: this.stateManager.queue_hasProcessingItem()
|
|
1473
1496
|
? "busy"
|
|
1474
1497
|
: "idle",
|
|
1475
1498
|
pending_count: this.stateManager.queue_length(),
|
|
1499
|
+
dlq_count: this.stateManager.queue_dlqLength(),
|
|
1476
1500
|
};
|
|
1477
1501
|
}
|
|
1478
1502
|
|
|
1503
|
+
pauseQueue(): void {
|
|
1504
|
+
this.stateManager.queue_pause();
|
|
1505
|
+
this.queueProcessor.abort();
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
getQueueActiveItems(): LLMRequest[] {
|
|
1509
|
+
return this.stateManager.queue_getAllActiveItems();
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
getDLQItems(): LLMRequest[] {
|
|
1513
|
+
return this.stateManager.queue_getDLQItems();
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
updateQueueItem(id: string, updates: Partial<LLMRequest>): boolean {
|
|
1517
|
+
return this.stateManager.queue_updateItem(id, updates);
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1479
1520
|
async clearQueue(): Promise<number> {
|
|
1480
1521
|
this.queueProcessor.abort();
|
|
1481
1522
|
return this.stateManager.queue_clear();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { callLLMRaw, parseJSONResponse
|
|
1
|
+
import { LLMRequest, LLMResponse, LLMRequestType, ProviderAccount, ChatMessage, Message } from "./types.js";
|
|
2
|
+
import { callLLMRaw, parseJSONResponse } from "./llm-client.js";
|
|
3
3
|
import { hydratePromptPlaceholders } from "../prompts/message-utils.js";
|
|
4
4
|
|
|
5
5
|
type QueueProcessorState = "idle" | "busy";
|
|
@@ -133,9 +133,8 @@ export class QueueProcessor {
|
|
|
133
133
|
): LLMResponse {
|
|
134
134
|
switch (request.type) {
|
|
135
135
|
case "json" as LLMRequestType:
|
|
136
|
-
return this.handleJSONResponse(request, content, finishReason);
|
|
137
136
|
case "response" as LLMRequestType:
|
|
138
|
-
return this.
|
|
137
|
+
return this.handleJSONResponse(request, content, finishReason);
|
|
139
138
|
case "raw" as LLMRequestType:
|
|
140
139
|
default:
|
|
141
140
|
return {
|
|
@@ -172,26 +171,4 @@ export class QueueProcessor {
|
|
|
172
171
|
}
|
|
173
172
|
}
|
|
174
173
|
|
|
175
|
-
private handleConversationResponse(
|
|
176
|
-
request: LLMRequest,
|
|
177
|
-
content: string,
|
|
178
|
-
finishReason: string | null
|
|
179
|
-
): LLMResponse {
|
|
180
|
-
const cleaned = cleanResponseContent(content);
|
|
181
|
-
|
|
182
|
-
const noMessagePatterns = [
|
|
183
|
-
/^no\s*(new\s*)?(message|response)/i,
|
|
184
|
-
/^nothing\s+to\s+(say|add)/i,
|
|
185
|
-
/^\[no\s+message\]/i,
|
|
186
|
-
];
|
|
187
|
-
|
|
188
|
-
const isNoMessage = noMessagePatterns.some((p) => p.test(cleaned));
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
request,
|
|
192
|
-
success: true,
|
|
193
|
-
content: isNoMessage ? null : cleaned,
|
|
194
|
-
finish_reason: finishReason ?? undefined,
|
|
195
|
-
};
|
|
196
174
|
}
|
|
197
|
-
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
import type { PersonaEntity, Message, ContextStatus } from "../types.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Migration: If a persisted message has the old `content` field but no `verbal_response`,
|
|
5
|
+
* move content → verbal_response. Runs on every load (no-op for already-migrated data).
|
|
6
|
+
*/
|
|
7
|
+
function migrateMessage(msg: Message & { content?: string }): Message {
|
|
8
|
+
if (msg.content !== undefined && msg.verbal_response === undefined) {
|
|
9
|
+
const { content, ...rest } = msg;
|
|
10
|
+
return { ...rest, verbal_response: content };
|
|
11
|
+
}
|
|
12
|
+
return msg;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
export interface PersonaData {
|
|
4
16
|
entity: PersonaEntity;
|
|
5
17
|
messages: Message[];
|
|
@@ -13,7 +25,7 @@ export class PersonaState {
|
|
|
13
25
|
this.personas = new Map(
|
|
14
26
|
Object.entries(personas).map(([id, data]) => [
|
|
15
27
|
id,
|
|
16
|
-
{ entity: data.entity, messages: data.messages },
|
|
28
|
+
{ entity: data.entity, messages: data.messages.map(migrateMessage) },
|
|
17
29
|
])
|
|
18
30
|
);
|
|
19
31
|
}
|