ei-tui 0.9.3 → 1.0.0
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 +22 -3
- package/package.json +8 -1
- package/src/README.md +10 -26
- package/src/core/context-utils.ts +2 -2
- package/src/core/handlers/document-segmentation.ts +113 -0
- package/src/core/handlers/heartbeat.ts +9 -1
- package/src/core/handlers/human-extraction.ts +4 -1
- package/src/core/handlers/human-matching.ts +5 -53
- package/src/core/handlers/index.ts +3 -51
- package/src/core/handlers/persona-generation.ts +1 -28
- package/src/core/handlers/rewrite.ts +13 -9
- package/src/core/handlers/utils.ts +2 -9
- package/src/core/heartbeat-manager.ts +5 -5
- package/src/core/llm-client.ts +11 -1
- package/src/core/message-manager.ts +26 -23
- package/src/core/orchestrators/ceremony.ts +87 -49
- package/src/core/orchestrators/extraction-chunker.ts +3 -3
- package/src/core/orchestrators/human-extraction.ts +22 -18
- package/src/core/orchestrators/index.ts +0 -1
- package/src/core/orchestrators/persona-topics.ts +1 -1
- package/src/core/orchestrators/room-extraction.ts +5 -5
- package/src/core/persona-manager.ts +4 -0
- package/src/core/processor.ts +98 -22
- package/src/core/prompt-context-builder.ts +7 -6
- package/src/core/queue-manager.ts +35 -0
- package/src/core/state/personas.ts +1 -17
- package/src/core/state/queue.ts +9 -1
- package/src/core/state-manager.ts +4 -66
- package/src/core/types/entities.ts +17 -3
- package/src/core/types/enums.ts +1 -2
- package/src/core/types/integrations.ts +2 -0
- package/src/core/types/llm.ts +9 -0
- package/src/core/types/rooms.ts +1 -1
- package/src/integrations/claude-code/importer.ts +1 -1
- package/src/integrations/cursor/importer.ts +1 -1
- package/src/integrations/document/chunker.ts +88 -0
- package/src/integrations/document/importer.ts +82 -0
- package/src/integrations/document/index.ts +2 -0
- package/src/integrations/document/invoice.ts +63 -0
- package/src/integrations/document/types.ts +16 -0
- package/src/integrations/document/unsource.ts +164 -0
- package/src/integrations/opencode/importer.ts +1 -1
- package/src/integrations/persona-history/importer.ts +197 -0
- package/src/integrations/persona-history/index.ts +3 -0
- package/src/integrations/persona-history/types.ts +7 -0
- package/src/prompts/ceremony/dedup.ts +7 -3
- package/src/prompts/ceremony/index.ts +2 -11
- package/src/prompts/ceremony/people-rewrite.ts +190 -0
- package/src/prompts/ceremony/{rewrite.ts → topic-rewrite.ts} +103 -78
- package/src/prompts/ceremony/types.ts +1 -42
- package/src/prompts/generation/index.ts +0 -3
- package/src/prompts/generation/types.ts +0 -15
- package/src/prompts/heartbeat/check.ts +18 -6
- package/src/prompts/heartbeat/types.ts +2 -1
- package/src/prompts/human/index.ts +0 -2
- package/src/prompts/human/person-scan.ts +13 -4
- package/src/prompts/human/topic-scan.ts +16 -2
- package/src/prompts/human/topic-update.ts +36 -4
- package/src/prompts/human/types.ts +1 -16
- package/src/prompts/index.ts +0 -19
- package/src/prompts/reflection/index.ts +35 -5
- package/src/prompts/reflection/types.ts +1 -1
- package/src/prompts/response/index.ts +5 -0
- package/src/prompts/response/sections.ts +26 -0
- package/src/prompts/response/types.ts +3 -0
- package/src/storage/indexed.ts +4 -0
- package/src/storage/interface.ts +1 -0
- package/src/storage/local.ts +4 -0
- package/src/templates/emmett.ts +49 -0
- package/tui/README.md +22 -0
- package/tui/src/app.tsx +9 -6
- package/tui/src/commands/delete.tsx +7 -1
- package/tui/src/commands/import.tsx +30 -0
- package/tui/src/commands/registry.test.ts +10 -5
- package/tui/src/commands/unsource.tsx +115 -0
- package/tui/src/components/PromptInput.tsx +4 -0
- package/tui/src/components/WelcomeOverlay.tsx +58 -32
- package/tui/src/context/ei.tsx +80 -60
- package/tui/src/globals.d.ts +57 -0
- package/tui/src/index.tsx +14 -0
- package/tui/src/storage/file.ts +11 -5
- package/tui/src/util/e2e-flags.ts +4 -3
- package/tui/src/util/help-content.ts +20 -0
- package/tui/src/util/provider-detection.ts +251 -0
- package/tui/src/util/yaml-human.ts +7 -1
- package/tui/src/util/yaml-persona.ts +8 -4
- package/tui/src/util/yaml-settings.ts +3 -3
- package/src/core/orchestrators/person-migration.ts +0 -55
- package/src/prompts/ceremony/description-check.ts +0 -54
- package/src/prompts/ceremony/expire.ts +0 -37
- package/src/prompts/ceremony/explore.ts +0 -77
- package/src/prompts/ceremony/person-migration.ts +0 -77
- package/src/prompts/generation/descriptions.ts +0 -91
- package/src/prompts/human/fact-scan.ts +0 -150
package/src/core/llm-client.ts
CHANGED
|
@@ -274,7 +274,17 @@ export async function callLLMRaw(
|
|
|
274
274
|
};
|
|
275
275
|
|
|
276
276
|
if (modelConfig?.thinking_budget !== undefined) {
|
|
277
|
-
|
|
277
|
+
if (modelConfig.thinking_budget === 0) {
|
|
278
|
+
// Universal kill switch — works on Ollama, LM Studio, and all OpenAI-compat providers.
|
|
279
|
+
requestBody.reasoning_effort = "none";
|
|
280
|
+
} else {
|
|
281
|
+
// Pass both signals: providers that honor the token budget get it (Qwen3 via Ollama,
|
|
282
|
+
// Anthropic), providers that reduce thinking to on/off use reasoning_effort as the
|
|
283
|
+
// on-signal (Gemma4 via Ollama/LM Studio). Non-conflicting — each provider reads
|
|
284
|
+
// whichever field it understands.
|
|
285
|
+
requestBody.reasoning_effort = "high";
|
|
286
|
+
requestBody.think = { budget_tokens: modelConfig.thinking_budget };
|
|
287
|
+
}
|
|
278
288
|
}
|
|
279
289
|
|
|
280
290
|
if (options.tools && options.tools.length > 0) {
|
|
@@ -177,25 +177,27 @@ export async function sendMessage(
|
|
|
177
177
|
|
|
178
178
|
const history = sm.messages_get(persona.id);
|
|
179
179
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
180
|
+
if (!persona.is_static) {
|
|
181
|
+
const traitExtractionData: PersonaTraitExtractionPromptData = {
|
|
182
|
+
persona_name: persona.display_name,
|
|
183
|
+
current_traits: persona.traits,
|
|
184
|
+
messages_context: history.slice(-11, -1),
|
|
185
|
+
messages_analyze: [message],
|
|
186
|
+
};
|
|
187
|
+
const traitPrompt = buildPersonaTraitExtractionPrompt(traitExtractionData);
|
|
188
|
+
|
|
189
|
+
sm.queue_enqueue({
|
|
190
|
+
type: LLMRequestType.JSON,
|
|
191
|
+
priority: LLMPriority.Low,
|
|
192
|
+
system: traitPrompt.system,
|
|
193
|
+
user: traitPrompt.user,
|
|
194
|
+
next_step: LLMNextStep.HandlePersonaTraitExtraction,
|
|
195
|
+
model: getModelForPersona(persona.id),
|
|
196
|
+
data: { personaId: persona.id, personaDisplayName: persona.display_name },
|
|
197
|
+
});
|
|
197
198
|
|
|
198
|
-
|
|
199
|
+
checkAndQueueHumanExtraction(sm, persona.id, persona.display_name, history);
|
|
200
|
+
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
// =============================================================================
|
|
@@ -223,7 +225,7 @@ export function checkAndQueueHumanExtraction(
|
|
|
223
225
|
if (unextractedFacts.length > 0 && unextractedFacts.length >= factsThreshold) {
|
|
224
226
|
const context: ExtractionContext = {
|
|
225
227
|
personaId,
|
|
226
|
-
personaDisplayName,
|
|
228
|
+
channelDisplayName: personaDisplayName,
|
|
227
229
|
messages_context: history.filter((m) => m.f === true),
|
|
228
230
|
messages_analyze: unextractedFacts,
|
|
229
231
|
extraction_flag: "f",
|
|
@@ -239,7 +241,7 @@ export function checkAndQueueHumanExtraction(
|
|
|
239
241
|
if (unextractedTopics.length > 0 && unextractedTopics.length >= topicsThreshold) {
|
|
240
242
|
const context: ExtractionContext = {
|
|
241
243
|
personaId,
|
|
242
|
-
personaDisplayName,
|
|
244
|
+
channelDisplayName: personaDisplayName,
|
|
243
245
|
messages_context: history.filter((m) => m.t === true),
|
|
244
246
|
messages_analyze: unextractedTopics,
|
|
245
247
|
extraction_flag: "t",
|
|
@@ -257,7 +259,7 @@ export function checkAndQueueHumanExtraction(
|
|
|
257
259
|
const personScanOptions = personaForScan?.pending_update ? { reflection_progress: 1 } : undefined;
|
|
258
260
|
const context: ExtractionContext = {
|
|
259
261
|
personaId,
|
|
260
|
-
personaDisplayName,
|
|
262
|
+
channelDisplayName: personaDisplayName,
|
|
261
263
|
messages_context: history.filter((m) => m.p === true),
|
|
262
264
|
messages_analyze: unextractedPeople,
|
|
263
265
|
extraction_flag: "p",
|
|
@@ -282,8 +284,9 @@ export function fetchMessagesForLLM(
|
|
|
282
284
|
|
|
283
285
|
const human = sm.getHuman();
|
|
284
286
|
const history = sm.messages_get(personaId);
|
|
285
|
-
|
|
286
|
-
|
|
287
|
+
const contextWindowMs = persona.context_window_ms ?? human.settings?.default_context_window_ms ?? 28800000;
|
|
288
|
+
const MAX_RESPONSE_MESSAGES = 50;
|
|
289
|
+
const filteredHistory = filterMessagesForContext(history, persona.context_boundary, contextWindowMs).slice(-MAX_RESPONSE_MESSAGES);
|
|
287
290
|
|
|
288
291
|
const humanName = human.settings?.name_display
|
|
289
292
|
|| human.facts?.find(f => f.name === "Nickname/Preferred Name")?.description
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep, RoomMode, ContextStatus, type CeremonyConfig, type PersonaTopic, type Topic
|
|
1
|
+
import { LLMRequestType, LLMPriority, LLMNextStep, RoomMode, ContextStatus, type CeremonyConfig, type PersonaTopic, type Topic } from "../types.js";
|
|
2
2
|
import type { StateManager } from "../state-manager.js";
|
|
3
3
|
import { normalizeRoomMessages } from "../handlers/utils.js";
|
|
4
4
|
import { applyDecayToValue } from "../utils/index.js";
|
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
} from "./human-extraction.js";
|
|
13
13
|
import { queuePersonaTopicRating, type PersonaTopicContext, type PersonaTopicOptions } from "./persona-topics.js";
|
|
14
14
|
import { getRoomVisibleMessages, queueRoomHumanExtraction } from "./room-extraction.js";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { type RewriteItemType } from "../../prompts/ceremony/index.js";
|
|
16
|
+
import { buildPersonRewriteScanPrompt } from "../../prompts/ceremony/people-rewrite.js";
|
|
17
|
+
import { buildTopicRewriteScanPrompt } from "../../prompts/ceremony/topic-rewrite.js";
|
|
17
18
|
import { buildReflectionCriticPrompt } from "../../prompts/reflection/index.js";
|
|
18
19
|
import { getModelForPersona } from "../heartbeat-manager.js";
|
|
19
20
|
|
|
@@ -52,7 +53,7 @@ export function shouldStartCeremony(config: CeremonyConfig, state: StateManager,
|
|
|
52
53
|
* Start the ceremony by queuing Exposure scans for all active personas with recent activity.
|
|
53
54
|
*
|
|
54
55
|
* IMPORTANT: Sets last_ceremony FIRST to prevent re-triggering from the processor loop.
|
|
55
|
-
* The actual Decay →
|
|
56
|
+
* The actual Decay → Person Rewrite → Topic Rewrite phases happen later via handleCeremonyProgress
|
|
56
57
|
* once all exposure scans have completed.
|
|
57
58
|
*/
|
|
58
59
|
export function startCeremony(state: StateManager): void {
|
|
@@ -76,10 +77,6 @@ export function startCeremony(state: StateManager): void {
|
|
|
76
77
|
},
|
|
77
78
|
});
|
|
78
79
|
|
|
79
|
-
// PHASE 1: Person Migration
|
|
80
|
-
console.log("[ceremony] Starting Phase 1: Person Migration");
|
|
81
|
-
queuePersonMigration(state);
|
|
82
|
-
|
|
83
80
|
// Check if migration work was queued
|
|
84
81
|
if (!state.queue_hasPendingCeremonies()) {
|
|
85
82
|
// No migration work found → immediately advance to Expose phase
|
|
@@ -110,7 +107,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
110
107
|
if (unextractedFacts.length > 0) {
|
|
111
108
|
const context: ExtractionContext = {
|
|
112
109
|
personaId,
|
|
113
|
-
|
|
110
|
+
channelDisplayName: persona.display_name,
|
|
114
111
|
messages_context: allMessages.filter(m => m.f === true),
|
|
115
112
|
messages_analyze: unextractedFacts,
|
|
116
113
|
extraction_flag: "f",
|
|
@@ -123,7 +120,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
123
120
|
if (unextractedTopics.length > 0) {
|
|
124
121
|
const context: ExtractionContext = {
|
|
125
122
|
personaId,
|
|
126
|
-
|
|
123
|
+
channelDisplayName: persona.display_name,
|
|
127
124
|
messages_context: allMessages.filter(m => m.t === true),
|
|
128
125
|
messages_analyze: unextractedTopics,
|
|
129
126
|
extraction_flag: "t",
|
|
@@ -135,7 +132,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
135
132
|
if (unextractedPeople.length > 0) {
|
|
136
133
|
const context: ExtractionContext = {
|
|
137
134
|
personaId,
|
|
138
|
-
|
|
135
|
+
channelDisplayName: persona.display_name,
|
|
139
136
|
messages_context: allMessages.filter(m => m.p === true),
|
|
140
137
|
messages_analyze: unextractedPeople,
|
|
141
138
|
extraction_flag: "p",
|
|
@@ -172,7 +169,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
172
169
|
* AND at the end of startCeremony (for the zero-messages edge case).
|
|
173
170
|
*
|
|
174
171
|
* If any ceremony_progress items remain in the queue, does nothing — more work pending.
|
|
175
|
-
* Phase 1: Dedup → Phase 2: Expose → Phase 3: EventSummary → Decay →
|
|
172
|
+
* Phase 1: Dedup → Phase 2: Expose → Phase 3: EventSummary → Decay → Phase 4: Person Rewrite → Topic Rewrite (fire-and-forget)
|
|
176
173
|
*/
|
|
177
174
|
export function handleCeremonyProgress(state: StateManager, lastPhase: number): void {
|
|
178
175
|
if (state.queue_hasPendingCeremonies()) {
|
|
@@ -241,6 +238,12 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
241
238
|
return;
|
|
242
239
|
}
|
|
243
240
|
|
|
241
|
+
if (lastPhase === 4) {
|
|
242
|
+
console.log("[ceremony:progress] Person Rewrite complete, starting Topic Rewrite");
|
|
243
|
+
queueTopicRewritePhase(state);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
244
247
|
if (lastPhase === 2) {
|
|
245
248
|
console.log("[ceremony:progress] Expose complete, starting EventSummary phase");
|
|
246
249
|
const options: ExtractionOptions = { ceremony_progress: 3 };
|
|
@@ -254,7 +257,7 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
254
257
|
return;
|
|
255
258
|
}
|
|
256
259
|
|
|
257
|
-
// Phase 3 (EventSummary) complete → advance to Decay/Prune
|
|
260
|
+
// Phase 3 (EventSummary) complete → advance to Decay/Prune then Person Rewrite (phase 4)
|
|
258
261
|
console.log("[ceremony:progress] EventSummary complete, advancing to Decay");
|
|
259
262
|
|
|
260
263
|
const personas = state.persona_getAll();
|
|
@@ -281,8 +284,16 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
281
284
|
// Human ceremony: decay topics + people
|
|
282
285
|
runHumanCeremony(state);
|
|
283
286
|
|
|
284
|
-
// Rewrite phase
|
|
285
|
-
|
|
287
|
+
// Person Rewrite phase (phase 4): scan bloated Person records, extract Topics from them.
|
|
288
|
+
// Gated via ceremony_progress so Topic Rewrite can run after — Topics created here
|
|
289
|
+
// need to be visible before Topic Rewrite snapshots the threshold.
|
|
290
|
+
queuePersonRewritePhase(state);
|
|
291
|
+
|
|
292
|
+
// Zero-work guard: if no person rewrites queued, advance to topic rewrite immediately
|
|
293
|
+
if (!state.queue_hasPendingCeremonies()) {
|
|
294
|
+
console.log("[ceremony:progress] No person rewrite work, advancing to Topic Rewrite");
|
|
295
|
+
handleCeremonyProgress(state, 4);
|
|
296
|
+
}
|
|
286
297
|
|
|
287
298
|
// Reflection phase: fire-and-forget critic calls for persona person records above threshold
|
|
288
299
|
queueReflectionPhase(state);
|
|
@@ -446,15 +457,6 @@ export function runHumanCeremony(state: StateManager): void {
|
|
|
446
457
|
|
|
447
458
|
const REWRITE_DESCRIPTION_THRESHOLD = 750;
|
|
448
459
|
|
|
449
|
-
/**
|
|
450
|
-
* Queue Phase 1 "scan" for every human data item whose description exceeds the
|
|
451
|
-
* threshold. Gated on rewrite_model being set in HumanSettings.
|
|
452
|
-
*
|
|
453
|
-
* Fire-and-forget: no ceremony_progress, no blocking. Expire/Explore proceed
|
|
454
|
-
* immediately since they only touch persona topics (zero overlap with human data).
|
|
455
|
-
* Phase 2 items enqueue at Normal priority, naturally processing before more
|
|
456
|
-
* Low-priority Phase 1 scans.
|
|
457
|
-
*/
|
|
458
460
|
/**
|
|
459
461
|
* Forces an unconditional, threshold-bypassing Person scan on Apply/Dismiss.
|
|
460
462
|
* Cannot be replaced by checkAndQueueHumanExtraction — that function gates on
|
|
@@ -475,7 +477,7 @@ export function queueReflectionDrain(personaId: string, state: StateManager): vo
|
|
|
475
477
|
|
|
476
478
|
const context: ExtractionContext = {
|
|
477
479
|
personaId,
|
|
478
|
-
|
|
480
|
+
channelDisplayName: persona.display_name,
|
|
479
481
|
messages_context: allMessages.filter(m => m.p === true),
|
|
480
482
|
messages_analyze: unextractedPeople,
|
|
481
483
|
extraction_flag: "p",
|
|
@@ -484,41 +486,77 @@ export function queueReflectionDrain(personaId: string, state: StateManager): vo
|
|
|
484
486
|
console.log(`[reflection:drain] Queued Person scan for ${persona.display_name} (${unextractedPeople.length} messages) — clears on completion`);
|
|
485
487
|
}
|
|
486
488
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
function getRewriteModel(state: StateManager): string | undefined {
|
|
490
|
+
return state.getHuman().settings?.rewrite_model;
|
|
491
|
+
}
|
|
490
492
|
|
|
493
|
+
export function queuePersonRewritePhase(state: StateManager): void {
|
|
494
|
+
const rewriteModel = getRewriteModel(state);
|
|
491
495
|
if (!rewriteModel) {
|
|
492
|
-
console.log("[ceremony:rewrite] rewrite_model not set — skipping rewrite phase");
|
|
496
|
+
console.log("[ceremony:rewrite] rewrite_model not set — skipping person rewrite phase");
|
|
493
497
|
return;
|
|
494
498
|
}
|
|
495
499
|
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
for (const topic of human.topics) {
|
|
499
|
-
if ((topic.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD && !topic.rewrite_checked) {
|
|
500
|
-
itemsToScan.push({ item: topic, type: "topic" });
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
for (const person of human.people) {
|
|
500
|
+
const human = state.getHuman();
|
|
501
|
+
const personsToScan = human.people.filter(person => {
|
|
504
502
|
const isPersonaLinked = (person.identifiers ?? []).some(
|
|
505
503
|
i => i.type.toLowerCase() === 'ei persona'
|
|
506
504
|
);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
505
|
+
return !isPersonaLinked
|
|
506
|
+
&& (person.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD
|
|
507
|
+
&& !person.rewrite_checked;
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
if (personsToScan.length === 0) {
|
|
511
|
+
console.log("[ceremony:rewrite] No persons above threshold — skipping person rewrite phase");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
console.log(`[ceremony:rewrite] Found ${personsToScan.length} person(s) above ${REWRITE_DESCRIPTION_THRESHOLD} chars — queueing person rewrite scans`);
|
|
516
|
+
|
|
517
|
+
for (const person of personsToScan) {
|
|
518
|
+
const prompt = buildPersonRewriteScanPrompt({ item: person, itemType: "person" });
|
|
519
|
+
state.queue_enqueue({
|
|
520
|
+
type: LLMRequestType.JSON,
|
|
521
|
+
priority: LLMPriority.Low,
|
|
522
|
+
system: prompt.system,
|
|
523
|
+
user: prompt.user,
|
|
524
|
+
next_step: LLMNextStep.HandleRewriteScan,
|
|
525
|
+
model: rewriteModel,
|
|
526
|
+
data: {
|
|
527
|
+
itemId: person.id,
|
|
528
|
+
itemType: "person" as RewriteItemType,
|
|
529
|
+
rewriteModel,
|
|
530
|
+
ceremony_progress: 4,
|
|
531
|
+
},
|
|
532
|
+
});
|
|
510
533
|
}
|
|
511
534
|
|
|
512
|
-
|
|
513
|
-
|
|
535
|
+
console.log(`[ceremony:rewrite] Queued ${personsToScan.length} person rewrite scan(s)`);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function queueTopicRewritePhase(state: StateManager): void {
|
|
539
|
+
const rewriteModel = getRewriteModel(state);
|
|
540
|
+
if (!rewriteModel) {
|
|
541
|
+
console.log("[ceremony:rewrite] rewrite_model not set — skipping topic rewrite phase");
|
|
514
542
|
return;
|
|
515
543
|
}
|
|
516
544
|
|
|
517
|
-
|
|
545
|
+
const human = state.getHuman();
|
|
546
|
+
const topicsToScan = human.topics.filter(topic =>
|
|
547
|
+
(topic.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD
|
|
548
|
+
&& !topic.rewrite_checked
|
|
549
|
+
);
|
|
550
|
+
|
|
551
|
+
if (topicsToScan.length === 0) {
|
|
552
|
+
console.log("[ceremony:rewrite] No topics above threshold — skipping topic rewrite phase");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
518
555
|
|
|
519
|
-
|
|
520
|
-
const prompt = buildRewriteScanPrompt({ item, itemType: type });
|
|
556
|
+
console.log(`[ceremony:rewrite] Found ${topicsToScan.length} topic(s) above ${REWRITE_DESCRIPTION_THRESHOLD} chars — queueing topic rewrite scans`);
|
|
521
557
|
|
|
558
|
+
for (const topic of topicsToScan) {
|
|
559
|
+
const prompt = buildTopicRewriteScanPrompt({ item: topic, itemType: "topic" });
|
|
522
560
|
state.queue_enqueue({
|
|
523
561
|
type: LLMRequestType.JSON,
|
|
524
562
|
priority: LLMPriority.Low,
|
|
@@ -527,14 +565,14 @@ export function queueRewritePhase(state: StateManager): void {
|
|
|
527
565
|
next_step: LLMNextStep.HandleRewriteScan,
|
|
528
566
|
model: rewriteModel,
|
|
529
567
|
data: {
|
|
530
|
-
itemId:
|
|
531
|
-
itemType:
|
|
532
|
-
rewriteModel,
|
|
568
|
+
itemId: topic.id,
|
|
569
|
+
itemType: "topic" as RewriteItemType,
|
|
570
|
+
rewriteModel,
|
|
533
571
|
},
|
|
534
572
|
});
|
|
535
573
|
}
|
|
536
574
|
|
|
537
|
-
console.log(`[ceremony:rewrite] Queued ${
|
|
575
|
+
console.log(`[ceremony:rewrite] Queued ${topicsToScan.length} topic rewrite scan(s)`);
|
|
538
576
|
}
|
|
539
577
|
|
|
540
578
|
function queueEventSummaryForAll(state: StateManager, options?: ExtractionOptions): void {
|
|
@@ -64,7 +64,7 @@ export function chunkExtractionContext(
|
|
|
64
64
|
context: ExtractionContext,
|
|
65
65
|
maxTokens: number = DEFAULT_MAX_TOKENS
|
|
66
66
|
): ChunkedContextResult {
|
|
67
|
-
const { personaId, personaDisplayName, messages_context, messages_analyze } = context;
|
|
67
|
+
const { personaId, channelDisplayName: personaDisplayName, messages_context, messages_analyze } = context;
|
|
68
68
|
|
|
69
69
|
if (messages_analyze.length === 0) {
|
|
70
70
|
return {
|
|
@@ -85,7 +85,7 @@ export function chunkExtractionContext(
|
|
|
85
85
|
return {
|
|
86
86
|
chunks: [{
|
|
87
87
|
personaId,
|
|
88
|
-
personaDisplayName,
|
|
88
|
+
channelDisplayName: personaDisplayName,
|
|
89
89
|
messages_context: fittedContext,
|
|
90
90
|
messages_analyze,
|
|
91
91
|
}],
|
|
@@ -111,7 +111,7 @@ export function chunkExtractionContext(
|
|
|
111
111
|
|
|
112
112
|
chunks.push({
|
|
113
113
|
personaId,
|
|
114
|
-
personaDisplayName,
|
|
114
|
+
channelDisplayName: personaDisplayName,
|
|
115
115
|
messages_context: currentContext,
|
|
116
116
|
messages_analyze: pulled,
|
|
117
117
|
});
|
|
@@ -55,7 +55,7 @@ function buildParticipantContext(personaId: string, state: StateManager): Partic
|
|
|
55
55
|
|
|
56
56
|
export interface ExtractionContext {
|
|
57
57
|
personaId: string;
|
|
58
|
-
|
|
58
|
+
channelDisplayName: string;
|
|
59
59
|
messages_context: Message[];
|
|
60
60
|
messages_analyze: Message[];
|
|
61
61
|
extraction_flag?: "f" | "t" | "p" | "e";
|
|
@@ -118,7 +118,7 @@ export function queueFactFind(context: ExtractionContext, state: StateManager, o
|
|
|
118
118
|
|
|
119
119
|
for (const chunk of chunks) {
|
|
120
120
|
const prompt = buildFactFindPrompt({
|
|
121
|
-
persona_name: chunk.
|
|
121
|
+
persona_name: chunk.channelDisplayName,
|
|
122
122
|
missing_fact_names,
|
|
123
123
|
messages_context: chunk.messages_context,
|
|
124
124
|
messages_analyze: chunk.messages_analyze,
|
|
@@ -134,7 +134,7 @@ export function queueFactFind(context: ExtractionContext, state: StateManager, o
|
|
|
134
134
|
data: {
|
|
135
135
|
...options,
|
|
136
136
|
personaId: chunk.personaId,
|
|
137
|
-
personaDisplayName: chunk.
|
|
137
|
+
personaDisplayName: chunk.channelDisplayName,
|
|
138
138
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
|
139
139
|
extraction_flag: context.extraction_flag,
|
|
140
140
|
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
@@ -160,10 +160,11 @@ export function queueTopicScan(context: ExtractionContext, state: StateManager,
|
|
|
160
160
|
|
|
161
161
|
for (const chunk of chunks) {
|
|
162
162
|
const prompt = buildHumanTopicScanPrompt({
|
|
163
|
-
persona_name: chunk.
|
|
163
|
+
persona_name: chunk.channelDisplayName,
|
|
164
164
|
messages_context: chunk.messages_context,
|
|
165
165
|
messages_analyze: chunk.messages_analyze,
|
|
166
166
|
participant_context: buildParticipantContext(context.personaId, state),
|
|
167
|
+
technical_context: (context.sources?.length ?? 0) > 0,
|
|
167
168
|
});
|
|
168
169
|
|
|
169
170
|
state.queue_enqueue({
|
|
@@ -176,7 +177,7 @@ export function queueTopicScan(context: ExtractionContext, state: StateManager,
|
|
|
176
177
|
data: {
|
|
177
178
|
...options,
|
|
178
179
|
personaId: chunk.personaId,
|
|
179
|
-
personaDisplayName: chunk.
|
|
180
|
+
personaDisplayName: chunk.channelDisplayName,
|
|
180
181
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
|
181
182
|
extraction_flag: context.extraction_flag,
|
|
182
183
|
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
@@ -209,7 +210,7 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
|
|
|
209
210
|
|
|
210
211
|
for (const chunk of chunks) {
|
|
211
212
|
const prompt = buildHumanPersonScanPrompt({
|
|
212
|
-
persona_name: chunk.
|
|
213
|
+
persona_name: chunk.channelDisplayName,
|
|
213
214
|
messages_context: chunk.messages_context,
|
|
214
215
|
messages_analyze: chunk.messages_analyze,
|
|
215
216
|
participant_context: buildParticipantContext(context.personaId, state),
|
|
@@ -226,7 +227,7 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
|
|
|
226
227
|
data: {
|
|
227
228
|
...options,
|
|
228
229
|
personaId: chunk.personaId,
|
|
229
|
-
personaDisplayName: chunk.
|
|
230
|
+
personaDisplayName: chunk.channelDisplayName,
|
|
230
231
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
|
231
232
|
extraction_flag: context.extraction_flag,
|
|
232
233
|
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
@@ -273,8 +274,9 @@ export function queueDirectTopicUpdate(
|
|
|
273
274
|
existing_item: topic,
|
|
274
275
|
messages_context: chunk.messages_context,
|
|
275
276
|
messages_analyze: chunk.messages_analyze,
|
|
276
|
-
persona_name: chunk.
|
|
277
|
+
persona_name: chunk.channelDisplayName,
|
|
277
278
|
participant_context: buildParticipantContext(context.personaId, state),
|
|
279
|
+
technical_context: (context.sources?.length ?? 0) > 0,
|
|
278
280
|
});
|
|
279
281
|
|
|
280
282
|
state.queue_enqueue({
|
|
@@ -286,11 +288,12 @@ export function queueDirectTopicUpdate(
|
|
|
286
288
|
next_step: LLMNextStep.HandleTopicUpdate,
|
|
287
289
|
data: {
|
|
288
290
|
personaId: context.personaId,
|
|
289
|
-
personaDisplayName: context.
|
|
291
|
+
personaDisplayName: context.channelDisplayName,
|
|
290
292
|
isNewItem: false,
|
|
291
293
|
existingItemId: topic.id,
|
|
292
294
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
|
293
295
|
extraction_model: extractionModel,
|
|
296
|
+
sources: context.sources,
|
|
294
297
|
},
|
|
295
298
|
});
|
|
296
299
|
}
|
|
@@ -306,7 +309,7 @@ const EMBEDDING_MIN_SIMILARITY = 0.3;
|
|
|
306
309
|
* Higher than EMBEDDING_MIN_SIMILARITY (0.3) because we need near-duplicates,
|
|
307
310
|
* not just vague thematic overlap.
|
|
308
311
|
*/
|
|
309
|
-
export const VALIDATE_MIN_SIMILARITY = 0.
|
|
312
|
+
export const VALIDATE_MIN_SIMILARITY = 0.92;
|
|
310
313
|
|
|
311
314
|
/**
|
|
312
315
|
* Queue a topic match request using embedding-based similarity (topics only).
|
|
@@ -423,8 +426,9 @@ export function queueTopicUpdate(
|
|
|
423
426
|
new_topic_category: isNewItem ? context.candidateCategory : undefined,
|
|
424
427
|
messages_context: chunk.messages_context,
|
|
425
428
|
messages_analyze: chunk.messages_analyze,
|
|
426
|
-
persona_name: chunk.
|
|
429
|
+
persona_name: chunk.channelDisplayName,
|
|
427
430
|
participant_context: buildParticipantContext(primaryPersonaId, state),
|
|
431
|
+
technical_context: (context.sources?.length ?? 0) > 0,
|
|
428
432
|
});
|
|
429
433
|
|
|
430
434
|
state.queue_enqueue({
|
|
@@ -502,7 +506,7 @@ export function queueEventSummary(
|
|
|
502
506
|
|
|
503
507
|
const context: ExtractionContext = {
|
|
504
508
|
personaId,
|
|
505
|
-
|
|
509
|
+
channelDisplayName: persona.display_name,
|
|
506
510
|
messages_context,
|
|
507
511
|
messages_analyze: windowMessages,
|
|
508
512
|
extraction_flag: "e",
|
|
@@ -512,7 +516,7 @@ export function queueEventSummary(
|
|
|
512
516
|
|
|
513
517
|
for (const chunk of chunks) {
|
|
514
518
|
const prompt = buildEventScanPrompt({
|
|
515
|
-
persona_name: chunk.
|
|
519
|
+
persona_name: chunk.channelDisplayName,
|
|
516
520
|
messages_context: chunk.messages_context,
|
|
517
521
|
messages_analyze: chunk.messages_analyze,
|
|
518
522
|
participant_context: buildParticipantContext(personaId, state),
|
|
@@ -528,7 +532,7 @@ export function queueEventSummary(
|
|
|
528
532
|
data: {
|
|
529
533
|
...options,
|
|
530
534
|
personaId: chunk.personaId,
|
|
531
|
-
personaDisplayName: chunk.
|
|
535
|
+
personaDisplayName: chunk.channelDisplayName,
|
|
532
536
|
extraction_flag: "e",
|
|
533
537
|
message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
|
|
534
538
|
},
|
|
@@ -595,7 +599,7 @@ export function queuePersonUpdate(
|
|
|
595
599
|
new_person_relationship: isNewItem ? context.candidateRelationship : undefined,
|
|
596
600
|
messages_context: chunk.messages_context,
|
|
597
601
|
messages_analyze: chunk.messages_analyze,
|
|
598
|
-
persona_name: chunk.
|
|
602
|
+
persona_name: chunk.channelDisplayName,
|
|
599
603
|
participant_context: buildParticipantContext(primaryPersonaIdForUpdate, state),
|
|
600
604
|
known_identifier_types: userIdentifierTypes,
|
|
601
605
|
});
|
|
@@ -609,7 +613,7 @@ export function queuePersonUpdate(
|
|
|
609
613
|
next_step: LLMNextStep.HandlePersonUpdate,
|
|
610
614
|
data: {
|
|
611
615
|
personaId: context.personaId,
|
|
612
|
-
personaDisplayName: context.
|
|
616
|
+
personaDisplayName: context.channelDisplayName,
|
|
613
617
|
roomId: context.roomId,
|
|
614
618
|
isNewItem,
|
|
615
619
|
existingItemId: existingItem?.id,
|
|
@@ -673,7 +677,7 @@ export function queueTargetedPersonUpdate(
|
|
|
673
677
|
extraction_model?: string;
|
|
674
678
|
} = {
|
|
675
679
|
personaId: contextPersonaId,
|
|
676
|
-
|
|
680
|
+
channelDisplayName: displayName,
|
|
677
681
|
messages_context: [],
|
|
678
682
|
messages_analyze: allMessages,
|
|
679
683
|
candidateName: existingItem.name,
|
|
@@ -728,7 +732,7 @@ export function queueTargetedTopicUpdate(
|
|
|
728
732
|
const model = state.getHuman().settings?.default_model;
|
|
729
733
|
const context: ExtractionContext = {
|
|
730
734
|
personaId: contextPersonaId,
|
|
731
|
-
|
|
735
|
+
channelDisplayName: displayName,
|
|
732
736
|
messages_context: [],
|
|
733
737
|
messages_analyze: allMessages,
|
|
734
738
|
roomId,
|
|
@@ -38,7 +38,7 @@ export function queuePersonaTopicRating(
|
|
|
38
38
|
const { chunks } = chunkExtractionContext(
|
|
39
39
|
{
|
|
40
40
|
personaId: context.personaId,
|
|
41
|
-
|
|
41
|
+
channelDisplayName: context.personaDisplayName,
|
|
42
42
|
messages_context: context.messages_context,
|
|
43
43
|
messages_analyze: context.messages_analyze,
|
|
44
44
|
},
|
|
@@ -78,7 +78,7 @@ function queueRoomTopicScan(
|
|
|
78
78
|
): void {
|
|
79
79
|
const context: HumanExtractionContext = {
|
|
80
80
|
personaId: roomId,
|
|
81
|
-
|
|
81
|
+
channelDisplayName: roomDisplayName,
|
|
82
82
|
messages_context,
|
|
83
83
|
messages_analyze,
|
|
84
84
|
extraction_flag: "t",
|
|
@@ -122,7 +122,7 @@ function queueRoomPersonScan(
|
|
|
122
122
|
): void {
|
|
123
123
|
const context: HumanExtractionContext = {
|
|
124
124
|
personaId: roomId,
|
|
125
|
-
|
|
125
|
+
channelDisplayName: roomDisplayName,
|
|
126
126
|
messages_context,
|
|
127
127
|
messages_analyze,
|
|
128
128
|
extraction_flag: "p",
|
|
@@ -180,7 +180,7 @@ function queueRoomEventScan(
|
|
|
180
180
|
);
|
|
181
181
|
const context: HumanExtractionContext = {
|
|
182
182
|
personaId: roomId,
|
|
183
|
-
|
|
183
|
+
channelDisplayName: roomDisplayName,
|
|
184
184
|
messages_context,
|
|
185
185
|
messages_analyze: windowMessages,
|
|
186
186
|
extraction_flag: "e",
|
|
@@ -348,7 +348,7 @@ export function queuePersonaCapture(state: StateManager, personaId: string): voi
|
|
|
348
348
|
);
|
|
349
349
|
const context: HumanExtractionContext = {
|
|
350
350
|
personaId,
|
|
351
|
-
|
|
351
|
+
channelDisplayName: persona.display_name,
|
|
352
352
|
messages_context,
|
|
353
353
|
messages_analyze: unextractedT,
|
|
354
354
|
};
|
|
@@ -362,7 +362,7 @@ export function queuePersonaCapture(state: StateManager, personaId: string): voi
|
|
|
362
362
|
);
|
|
363
363
|
const context: HumanExtractionContext = {
|
|
364
364
|
personaId,
|
|
365
|
-
|
|
365
|
+
channelDisplayName: persona.display_name,
|
|
366
366
|
messages_context,
|
|
367
367
|
messages_analyze: unextractedP,
|
|
368
368
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RESERVED_PERSONA_NAMES,
|
|
3
3
|
isReservedPersonaName,
|
|
4
|
+
isReservedPersonaId,
|
|
4
5
|
type PersonaSummary,
|
|
5
6
|
type PersonaEntity,
|
|
6
7
|
type PersonaCreationInput,
|
|
@@ -110,6 +111,9 @@ export async function deletePersona(
|
|
|
110
111
|
personaId: string,
|
|
111
112
|
_deleteHumanData: boolean
|
|
112
113
|
): Promise<boolean> {
|
|
114
|
+
if (isReservedPersonaId(personaId)) {
|
|
115
|
+
throw new Error(`Cannot delete reserved persona "${personaId}". Use archive instead.`);
|
|
116
|
+
}
|
|
113
117
|
const persona = sm.persona_getById(personaId);
|
|
114
118
|
if (!persona) return false;
|
|
115
119
|
sm.persona_delete(personaId);
|