ei-tui 0.9.1 → 0.9.2
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/handlers/heartbeat.ts +2 -2
- package/src/core/handlers/human-extraction.ts +9 -0
- package/src/core/message-manager.ts +4 -2
- package/src/core/orchestrators/ceremony.ts +33 -1
- package/src/core/orchestrators/human-extraction.ts +2 -0
- package/src/core/orchestrators/index.ts +1 -0
- package/src/core/processor.ts +38 -1
- package/tui/src/commands/persona.tsx +3 -4
- package/tui/src/commands/reflect.tsx +2 -8
- package/tui/src/context/ei.tsx +12 -0
package/package.json
CHANGED
|
@@ -135,9 +135,9 @@ export function handleReflectionCritic(response: LLMResponse, state: StateManage
|
|
|
135
135
|
if (personRecord) {
|
|
136
136
|
state.human_person_upsert({
|
|
137
137
|
...personRecord,
|
|
138
|
-
description:
|
|
138
|
+
description: "",
|
|
139
139
|
});
|
|
140
|
-
console.log(`[ReflectionCritic ${personaDisplayName}] Person record description
|
|
140
|
+
console.log(`[ReflectionCritic ${personaDisplayName}] Person record description cleared — ready for fresh evidence after reflection`);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const persona = state.persona_getById(personaId);
|
|
@@ -298,6 +298,15 @@ export async function handleHumanPersonScan(response: LLMResponse, state: StateM
|
|
|
298
298
|
}
|
|
299
299
|
}
|
|
300
300
|
|
|
301
|
+
if (matchedPerson && response.request.data.reflection_progress === 1) {
|
|
302
|
+
const linkedPersonaId = matchedPerson.identifiers
|
|
303
|
+
?.find(i => i.type === "Ei Persona")?.value;
|
|
304
|
+
if (linkedPersonaId) {
|
|
305
|
+
console.log(`[handleHumanPersonScan] Skipping update for "${candidate.name}" — scan marked as reflection drain (reflection_progress=1)`);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
301
310
|
const matchResult: ItemMatchResult = { matched_guid: matchedPerson?.id ?? null };
|
|
302
311
|
queuePersonUpdate(matchResult, {
|
|
303
312
|
...context,
|
|
@@ -253,6 +253,8 @@ export function checkAndQueueHumanExtraction(
|
|
|
253
253
|
const unextractedPeople = sm.messages_getUnextracted(personaId, "p", undefined, "exclude");
|
|
254
254
|
const peopleThreshold = Math.min(EXTRACTION_TAPER_CAP, human.people.length);
|
|
255
255
|
if (unextractedPeople.length > 0 && unextractedPeople.length >= peopleThreshold) {
|
|
256
|
+
const personaForScan = sm.persona_getById(personaId);
|
|
257
|
+
const personScanOptions = personaForScan?.pending_update ? { reflection_progress: 1 } : undefined;
|
|
256
258
|
const context: ExtractionContext = {
|
|
257
259
|
personaId,
|
|
258
260
|
personaDisplayName,
|
|
@@ -260,9 +262,9 @@ export function checkAndQueueHumanExtraction(
|
|
|
260
262
|
messages_analyze: unextractedPeople,
|
|
261
263
|
extraction_flag: "p",
|
|
262
264
|
};
|
|
263
|
-
queuePersonScan(context, sm);
|
|
265
|
+
queuePersonScan(context, sm, personScanOptions);
|
|
264
266
|
console.log(
|
|
265
|
-
`[Processor] Human Seed extraction: people (threshold: ${peopleThreshold}, unextracted: ${unextractedPeople.length})`
|
|
267
|
+
`[Processor] Human Seed extraction: people (threshold: ${peopleThreshold}, unextracted: ${unextractedPeople.length}${personScanOptions ? ", reflection_progress=1" : ""})`
|
|
266
268
|
);
|
|
267
269
|
}
|
|
268
270
|
}
|
|
@@ -139,7 +139,10 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
139
139
|
messages_analyze: unextractedPeople,
|
|
140
140
|
extraction_flag: "p",
|
|
141
141
|
};
|
|
142
|
-
|
|
142
|
+
const personScanOptions = persona.pending_update
|
|
143
|
+
? { ...options, reflection_progress: 1 }
|
|
144
|
+
: options;
|
|
145
|
+
queuePersonScan(context, state, personScanOptions);
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
const totalUnextracted = unextractedFacts.length + unextractedTopics.length + unextractedPeople.length;
|
|
@@ -445,6 +448,35 @@ const REWRITE_DESCRIPTION_THRESHOLD = 750;
|
|
|
445
448
|
* Phase 2 items enqueue at Normal priority, naturally processing before more
|
|
446
449
|
* Low-priority Phase 1 scans.
|
|
447
450
|
*/
|
|
451
|
+
/**
|
|
452
|
+
* Forces an unconditional, threshold-bypassing Person scan on Apply/Dismiss.
|
|
453
|
+
* Cannot be replaced by checkAndQueueHumanExtraction — that function gates on
|
|
454
|
+
* MIN(10, people_count) and would silently skip messages if the threshold isn't
|
|
455
|
+
* met, leaving reflection-era noise unprocessed and ungated.
|
|
456
|
+
*/
|
|
457
|
+
export function queueReflectionDrain(personaId: string, state: StateManager): void {
|
|
458
|
+
const persona = state.persona_getById(personaId);
|
|
459
|
+
if (!persona) return;
|
|
460
|
+
|
|
461
|
+
const allMessages = state.messages_get(personaId);
|
|
462
|
+
const unextractedPeople = state.messages_getUnextracted(personaId, "p");
|
|
463
|
+
|
|
464
|
+
if (unextractedPeople.length === 0) {
|
|
465
|
+
console.log(`[reflection:drain] No unextracted messages for ${persona.display_name} — drain complete`);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const context: ExtractionContext = {
|
|
470
|
+
personaId,
|
|
471
|
+
personaDisplayName: persona.display_name,
|
|
472
|
+
messages_context: allMessages.filter(m => m.p === true),
|
|
473
|
+
messages_analyze: unextractedPeople,
|
|
474
|
+
extraction_flag: "p",
|
|
475
|
+
};
|
|
476
|
+
queuePersonScan(context, state, { reflection_progress: 1 });
|
|
477
|
+
console.log(`[reflection:drain] Queued Person scan for ${persona.display_name} (${unextractedPeople.length} messages) — clears on completion`);
|
|
478
|
+
}
|
|
479
|
+
|
|
448
480
|
export function queueRewritePhase(state: StateManager): void {
|
|
449
481
|
const human = state.getHuman();
|
|
450
482
|
const rewriteModel = human.settings?.rewrite_model;
|
|
@@ -67,6 +67,8 @@ export interface ExtractionContext {
|
|
|
67
67
|
export interface ExtractionOptions {
|
|
68
68
|
/** Ceremony phase number (1=Dedup, 2=Expose) */
|
|
69
69
|
ceremony_progress?: number;
|
|
70
|
+
/** Set to 1 on scans queued while there is a Pending Reflection. */
|
|
71
|
+
reflection_progress?: number;
|
|
70
72
|
/** Override model for extraction LLM calls */
|
|
71
73
|
extraction_model?: string;
|
|
72
74
|
/**
|
package/src/core/processor.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { ContextStatus as ContextStatusEnum, RoomMode } from "./types.js";
|
|
|
39
39
|
import { registerReadMemoryExecutor, registerFileReadExecutor } from "./tools/index.js";
|
|
40
40
|
import { createReadMemoryExecutor } from "./tools/builtin/read-memory.js";
|
|
41
41
|
import { EI_WELCOME_MESSAGE, EI_PERSONA_DEFINITION } from "../templates/welcome.js";
|
|
42
|
-
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction, queueTargetedPersonUpdate, queueTargetedTopicUpdate } from "./orchestrators/index.js";
|
|
42
|
+
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueReflectionDrain, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction, queueTargetedPersonUpdate, queueTargetedTopicUpdate } from "./orchestrators/index.js";
|
|
43
43
|
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
44
44
|
import { DEFAULT_SEED_TRAITS } from "./constants/seed-traits.js";
|
|
45
45
|
|
|
@@ -1755,6 +1755,43 @@ const toolNextSteps = new Set([
|
|
|
1755
1755
|
if (ok) this.interface.onPersonaUpdated?.(personaId);
|
|
1756
1756
|
}
|
|
1757
1757
|
|
|
1758
|
+
async finalizeReflection(
|
|
1759
|
+
personaId: string,
|
|
1760
|
+
action: "apply" | "dismiss",
|
|
1761
|
+
identity?: { short_description?: string; long_description: string; traits: NonNullable<PersonaEntity["pending_update"]>["traits"]; topics: NonNullable<PersonaEntity["pending_update"]>["topics"] }
|
|
1762
|
+
): Promise<void> {
|
|
1763
|
+
const persona = this.stateManager.persona_getById(personaId);
|
|
1764
|
+
if (!persona) return;
|
|
1765
|
+
|
|
1766
|
+
const source = identity ?? (persona.pending_update ? {
|
|
1767
|
+
short_description: persona.pending_update.short_description,
|
|
1768
|
+
long_description: persona.pending_update.long_description,
|
|
1769
|
+
traits: persona.pending_update.traits,
|
|
1770
|
+
topics: persona.pending_update.topics,
|
|
1771
|
+
} : null);
|
|
1772
|
+
|
|
1773
|
+
const updates: Partial<PersonaEntity> = { pending_update: undefined };
|
|
1774
|
+
|
|
1775
|
+
if (action === "apply" && source) {
|
|
1776
|
+
updates.short_description = source.short_description;
|
|
1777
|
+
updates.long_description = source.long_description;
|
|
1778
|
+
updates.traits = source.traits.map(t => ({
|
|
1779
|
+
...t,
|
|
1780
|
+
id: t.id?.startsWith("pending-") ? crypto.randomUUID() : t.id,
|
|
1781
|
+
}));
|
|
1782
|
+
updates.topics = source.topics.map(t => ({
|
|
1783
|
+
...t,
|
|
1784
|
+
id: t.id?.startsWith("pending-") ? crypto.randomUUID() : t.id,
|
|
1785
|
+
}));
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
const ok = await updatePersona(this.stateManager, personaId, updates);
|
|
1789
|
+
if (ok) {
|
|
1790
|
+
queueReflectionDrain(personaId, this.stateManager);
|
|
1791
|
+
this.interface.onPersonaUpdated?.(personaId);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1758
1795
|
async updateRoom(roomId: string, updates: Partial<RoomEntity>): Promise<void> {
|
|
1759
1796
|
const ok = this.stateManager.updateRoom(roomId, updates);
|
|
1760
1797
|
if (ok) this.interface.onRoomUpdated?.(roomId);
|
|
@@ -233,7 +233,7 @@ export const personaCommand: Command = {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
if (reviewResult.content === null) {
|
|
236
|
-
await ctx.ei.
|
|
236
|
+
await ctx.ei.finalizeReflection(personaId, "dismiss");
|
|
237
237
|
ctx.showNotification(`Dismissed pending changes for ${persona.display_name}`, "info");
|
|
238
238
|
return;
|
|
239
239
|
}
|
|
@@ -267,17 +267,16 @@ export const personaCommand: Command = {
|
|
|
267
267
|
return;
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
await ctx.ei.
|
|
270
|
+
await ctx.ei.finalizeReflection(personaId, "dismiss");
|
|
271
271
|
ctx.showNotification(`Dismissed pending changes for ${persona.display_name}`, "info");
|
|
272
272
|
return;
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
await ctx.ei.
|
|
275
|
+
await ctx.ei.finalizeReflection(personaId, "apply", {
|
|
276
276
|
long_description: previewParsed.long_description,
|
|
277
277
|
short_description: previewParsed.short_description,
|
|
278
278
|
traits: previewParsed.traits,
|
|
279
279
|
topics: previewParsed.topics,
|
|
280
|
-
pending_update: undefined,
|
|
281
280
|
});
|
|
282
281
|
ctx.showNotification(`Applied changes to ${persona.display_name}`, "info");
|
|
283
282
|
return;
|
|
@@ -327,13 +327,7 @@ export const reflectCommand: Command = {
|
|
|
327
327
|
topics: persona.pending_update!.topics,
|
|
328
328
|
};
|
|
329
329
|
|
|
330
|
-
await ctx.ei.
|
|
331
|
-
long_description: source.long_description,
|
|
332
|
-
short_description: source.short_description,
|
|
333
|
-
traits: source.traits,
|
|
334
|
-
topics: source.topics,
|
|
335
|
-
pending_update: undefined,
|
|
336
|
-
});
|
|
330
|
+
await ctx.ei.finalizeReflection(personaId, "apply", source);
|
|
337
331
|
|
|
338
332
|
if (fs.existsSync(folderPath)) {
|
|
339
333
|
fs.rmSync(folderPath, { recursive: true, force: true });
|
|
@@ -363,7 +357,7 @@ export const reflectCommand: Command = {
|
|
|
363
357
|
return;
|
|
364
358
|
}
|
|
365
359
|
|
|
366
|
-
await ctx.ei.
|
|
360
|
+
await ctx.ei.finalizeReflection(personaId, "dismiss");
|
|
367
361
|
const folderPath = getReflectFolder(persona);
|
|
368
362
|
if (fs.existsSync(folderPath)) {
|
|
369
363
|
fs.rmSync(folderPath, { recursive: true, force: true });
|
package/tui/src/context/ei.tsx
CHANGED
|
@@ -80,6 +80,7 @@ export interface EiContextValue {
|
|
|
80
80
|
deletePersona: (personaId: string) => Promise<void>;
|
|
81
81
|
setContextBoundary: (personaId: string, timestamp: string | null) => Promise<void>;
|
|
82
82
|
updatePersona: (personaId: string, updates: Partial<PersonaEntity>) => Promise<void>;
|
|
83
|
+
finalizeReflection: (personaId: string, action: "apply" | "dismiss", identity?: { short_description?: string; long_description: string; traits: NonNullable<PersonaEntity["pending_update"]>["traits"]; topics: NonNullable<PersonaEntity["pending_update"]>["topics"] }) => Promise<void>;
|
|
83
84
|
getPersona: (personaId: string) => Promise<PersonaEntity | null>;
|
|
84
85
|
resolvePersonaName: (nameOrAlias: string) => Promise<string | null>;
|
|
85
86
|
getHuman: () => Promise<HumanEntity>;
|
|
@@ -360,6 +361,16 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
360
361
|
await refreshPersonas();
|
|
361
362
|
};
|
|
362
363
|
|
|
364
|
+
const finalizeReflection = async (
|
|
365
|
+
personaId: string,
|
|
366
|
+
action: "apply" | "dismiss",
|
|
367
|
+
identity?: { short_description?: string; long_description: string; traits: NonNullable<PersonaEntity["pending_update"]>["traits"]; topics: NonNullable<PersonaEntity["pending_update"]>["topics"] }
|
|
368
|
+
) => {
|
|
369
|
+
if (!processor) return;
|
|
370
|
+
await processor.finalizeReflection(personaId, action, identity);
|
|
371
|
+
await refreshPersonas();
|
|
372
|
+
};
|
|
373
|
+
|
|
363
374
|
const getPersona = async (personaId: string) => {
|
|
364
375
|
if (!processor) return null;
|
|
365
376
|
return processor.getPersona(personaId);
|
|
@@ -937,6 +948,7 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
937
948
|
deletePersona,
|
|
938
949
|
setContextBoundary,
|
|
939
950
|
updatePersona,
|
|
951
|
+
finalizeReflection,
|
|
940
952
|
getPersona,
|
|
941
953
|
resolvePersonaName,
|
|
942
954
|
getHuman,
|