ei-tui 1.1.0 → 1.2.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/package.json +2 -23
- package/src/core/handlers/dedup.ts +4 -15
- package/src/core/handlers/document-segmentation.ts +2 -3
- package/src/core/handlers/heartbeat.ts +5 -10
- package/src/core/handlers/human-matching.ts +8 -0
- package/src/core/handlers/index.ts +2 -0
- package/src/core/handlers/knowledge-synthesis.ts +50 -0
- package/src/core/handlers/persona-generation.ts +4 -8
- package/src/core/handlers/persona-response.ts +3 -4
- package/src/core/handlers/persona-topics.ts +2 -4
- package/src/core/handlers/rewrite.ts +26 -9
- package/src/core/handlers/rooms.ts +6 -12
- package/src/core/llm-client.ts +13 -3
- package/src/core/message-manager.ts +2 -4
- package/src/core/orchestrators/ceremony.ts +44 -13
- package/src/core/orchestrators/human-extraction.ts +10 -1
- package/src/core/processor.ts +155 -0
- package/src/core/queue-manager.ts +10 -0
- package/src/core/state-manager.ts +35 -0
- package/src/core/tools/builtin/fetch-memory.ts +6 -6
- package/src/core/tools/index.ts +1 -1
- package/src/core/tools/types.ts +1 -1
- package/src/core/types/data-items.ts +1 -1
- package/src/core/types/entities.ts +7 -1
- package/src/core/types/enums.ts +1 -0
- package/src/core/types/integrations.ts +3 -1
- package/src/integrations/claude-code/importer.ts +6 -0
- package/src/integrations/cursor/importer.ts +6 -0
- package/src/integrations/document/unsource.ts +5 -3
- package/src/integrations/opencode/importer.ts +13 -1
- package/src/integrations/persona-history/importer.ts +9 -0
- package/src/prompts/ceremony/people-rewrite.ts +2 -2
- package/src/prompts/ceremony/topic-rewrite.ts +2 -2
- package/src/prompts/index.ts +3 -0
- package/src/prompts/synthesis/index.ts +101 -0
- package/src/prompts/synthesis/types.ts +26 -0
- package/tui/src/commands/generate.tsx +98 -0
- package/tui/src/commands/unsource.tsx +17 -10
- package/tui/src/components/GeneratedDocsOverlay.tsx +136 -0
- package/tui/src/components/PromptInput.tsx +2 -0
- package/tui/src/context/ei.tsx +49 -2
- package/tui/src/util/logger.ts +22 -2
- package/tui/src/util/provider-detection.ts +5 -2
- package/tui/src/util/yaml-provider.ts +2 -8
|
@@ -195,6 +195,15 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
|
|
|
195
195
|
|
|
196
196
|
if (chunks.length === 0) return 0;
|
|
197
197
|
|
|
198
|
+
// If the persona has a pending_update (reflection in progress), gate person
|
|
199
|
+
// scans so handleHumanPersonScan won't queue updates for persona-linked people.
|
|
200
|
+
// This prevents importers and other callers from bypassing the reflection lock
|
|
201
|
+
// — they don't know about pending_update, so we enforce it here centrally.
|
|
202
|
+
const persona = state.persona_getById(context.personaId);
|
|
203
|
+
const effectiveOptions: ExtractionOptions | undefined = persona?.pending_update
|
|
204
|
+
? { ...options, reflection_progress: 1 }
|
|
205
|
+
: options;
|
|
206
|
+
|
|
198
207
|
// Pre-mark messages before enqueuing — prevents duplicate scans if the
|
|
199
208
|
// queue check fires again during LLM latency (100ms loop × 5s call = 50 dupes)
|
|
200
209
|
for (const chunk of chunks) {
|
|
@@ -225,7 +234,7 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
|
|
|
225
234
|
user: prompt.user,
|
|
226
235
|
next_step: LLMNextStep.HandleHumanPersonScan,
|
|
227
236
|
data: {
|
|
228
|
-
...
|
|
237
|
+
...effectiveOptions,
|
|
229
238
|
personaId: chunk.personaId,
|
|
230
239
|
personaDisplayName: chunk.channelDisplayName,
|
|
231
240
|
analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
|
package/src/core/processor.ts
CHANGED
|
@@ -44,6 +44,7 @@ import { EI_WELCOME_MESSAGE, EI_PERSONA_DEFINITION } from "../templates/welcome.
|
|
|
44
44
|
import { EMMETT_PERSONA_DEFINITION } from "../templates/emmett.js";
|
|
45
45
|
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueReflectionDrain, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction, queueTargetedPersonUpdate, queueTargetedTopicUpdate } from "./orchestrators/index.js";
|
|
46
46
|
import { finishDocumentBatch } from "./handlers/document-segmentation.js";
|
|
47
|
+
import { buildSynthesisPrompt } from "../prompts/synthesis/index.js";
|
|
47
48
|
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
48
49
|
import { DEFAULT_SEED_TRAITS } from "./constants/seed-traits.js";
|
|
49
50
|
|
|
@@ -335,6 +336,140 @@ export class Processor {
|
|
|
335
336
|
return executeUnsource(preview, this.stateManager);
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
async generateDocument(subject: string): Promise<{ slug: string }> {
|
|
340
|
+
this.bootstrapEmmett();
|
|
341
|
+
const slugBase = subject
|
|
342
|
+
.toLowerCase()
|
|
343
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
344
|
+
.slice(0, 40);
|
|
345
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
346
|
+
const slug = `${slugBase}_${timestamp}`;
|
|
347
|
+
|
|
348
|
+
const primary = await this.searchHumanData(subject, { limit: 20 });
|
|
349
|
+
if (
|
|
350
|
+
primary.facts.length === 0 &&
|
|
351
|
+
primary.topics.length === 0 &&
|
|
352
|
+
primary.people.length === 0 &&
|
|
353
|
+
primary.quotes.length === 0
|
|
354
|
+
) {
|
|
355
|
+
throw new Error(`No knowledge found about '${subject}'`);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const seenQuoteIds = new Set<string>();
|
|
359
|
+
const seenItemIds = new Set<string>(
|
|
360
|
+
[...primary.topics, ...primary.people, ...primary.facts].map(i => i.id)
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const MAX_QUOTES_PER_ENTITY = 3;
|
|
364
|
+
|
|
365
|
+
const enrichTopic = (topic: import("../prompts/synthesis/types.js").EnrichedTopic["topic"]) => {
|
|
366
|
+
const linked = this.stateManager.human_quote_getForDataItem(topic.id)
|
|
367
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
368
|
+
.slice(0, MAX_QUOTES_PER_ENTITY);
|
|
369
|
+
linked.forEach(q => seenQuoteIds.add(q.id));
|
|
370
|
+
return { topic, quotes: linked };
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const enrichPerson = (person: import("../prompts/synthesis/types.js").EnrichedPerson["person"]) => {
|
|
374
|
+
const linked = this.stateManager.human_quote_getForDataItem(person.id)
|
|
375
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp))
|
|
376
|
+
.slice(0, MAX_QUOTES_PER_ENTITY);
|
|
377
|
+
linked.forEach(q => seenQuoteIds.add(q.id));
|
|
378
|
+
return { person, quotes: linked };
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const enrichedTopics = primary.topics.map(enrichTopic);
|
|
382
|
+
const enrichedPeople = primary.people.map(enrichPerson);
|
|
383
|
+
|
|
384
|
+
const human = this.stateManager.getHuman();
|
|
385
|
+
const allItems = [...human.facts, ...human.topics, ...human.people];
|
|
386
|
+
|
|
387
|
+
const MAX_SECONDARY_ENTITIES = 10;
|
|
388
|
+
|
|
389
|
+
const secondaryTopics: typeof enrichedTopics = [];
|
|
390
|
+
const secondaryPeople: typeof enrichedPeople = [];
|
|
391
|
+
const secondaryFacts: typeof primary.facts = [];
|
|
392
|
+
|
|
393
|
+
outer: for (const quote of [...enrichedTopics.flatMap(e => e.quotes), ...enrichedPeople.flatMap(e => e.quotes)]) {
|
|
394
|
+
for (const itemId of quote.data_item_ids) {
|
|
395
|
+
if (secondaryTopics.length + secondaryPeople.length + secondaryFacts.length >= MAX_SECONDARY_ENTITIES) break outer;
|
|
396
|
+
if (seenItemIds.has(itemId)) continue;
|
|
397
|
+
seenItemIds.add(itemId);
|
|
398
|
+
const item = allItems.find(i => i.id === itemId);
|
|
399
|
+
if (!item) continue;
|
|
400
|
+
if (human.topics.find(t => t.id === itemId)) {
|
|
401
|
+
secondaryTopics.push(enrichTopic(item as typeof primary.topics[0]));
|
|
402
|
+
} else if (human.people.find(p => p.id === itemId)) {
|
|
403
|
+
secondaryPeople.push(enrichPerson(item as typeof primary.people[0]));
|
|
404
|
+
} else if (human.facts.find(f => f.id === itemId)) {
|
|
405
|
+
secondaryFacts.push(item as typeof primary.facts[0]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const standaloneQuotes = primary.quotes.filter(q => !seenQuoteIds.has(q.id));
|
|
411
|
+
|
|
412
|
+
const allLoadedFacts = [...primary.facts, ...secondaryFacts];
|
|
413
|
+
const allLoadedTopics = [...enrichedTopics, ...secondaryTopics];
|
|
414
|
+
const allLoadedPeople = [...enrichedPeople, ...secondaryPeople];
|
|
415
|
+
|
|
416
|
+
const loadedEntityNames = new Map<string, string>();
|
|
417
|
+
for (const f of allLoadedFacts) loadedEntityNames.set(f.id, f.name);
|
|
418
|
+
for (const { topic } of allLoadedTopics) loadedEntityNames.set(topic.id, topic.name);
|
|
419
|
+
for (const { person } of allLoadedPeople) loadedEntityNames.set(person.id, person.name);
|
|
420
|
+
|
|
421
|
+
const prompt = buildSynthesisPrompt({
|
|
422
|
+
subject,
|
|
423
|
+
facts: allLoadedFacts,
|
|
424
|
+
topics: allLoadedTopics,
|
|
425
|
+
people: allLoadedPeople,
|
|
426
|
+
standaloneQuotes,
|
|
427
|
+
loadedEntityNames,
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const model = this.stateManager.getHuman().settings?.rewrite_model
|
|
431
|
+
?? this.stateManager.getHuman().settings?.default_model;
|
|
432
|
+
|
|
433
|
+
this.stateManager.queue_enqueue({
|
|
434
|
+
type: LLMRequestType.Raw,
|
|
435
|
+
priority: LLMPriority.Normal,
|
|
436
|
+
system: prompt.system,
|
|
437
|
+
user: prompt.user,
|
|
438
|
+
next_step: LLMNextStep.HandleKnowledgeSynthesis,
|
|
439
|
+
model,
|
|
440
|
+
data: { slug, subject },
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
return { slug };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
checkGenerationModel(): { model: string; isRewriteModel: boolean } {
|
|
447
|
+
const settings = this.stateManager.getHuman().settings;
|
|
448
|
+
if (settings?.rewrite_model) {
|
|
449
|
+
return { model: settings.rewrite_model, isRewriteModel: true };
|
|
450
|
+
}
|
|
451
|
+
return { model: settings?.default_model ?? "unknown", isRewriteModel: false };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async getGeneratedDocumentContent(slug: string): Promise<string | null> {
|
|
455
|
+
const messages = this.stateManager.messages_get("emmet");
|
|
456
|
+
const target = `generate:document:${slug}`;
|
|
457
|
+
const message = messages.find(m => m.source_tag === target);
|
|
458
|
+
return message?.content ?? null;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async reRunDocument(slug: string): Promise<{ slug: string }> {
|
|
462
|
+
const docs = this.stateManager.getHuman().settings?.document?.processed_documents ?? {};
|
|
463
|
+
const entry = docs[slug];
|
|
464
|
+
if (!entry || entry.type !== "generated" || !entry.subject) {
|
|
465
|
+
throw new Error(`No generated document found for slug "${slug}"`);
|
|
466
|
+
}
|
|
467
|
+
const subject = entry.subject;
|
|
468
|
+
const preview = this.getUnsourcePreview(`generate:document:${slug}`);
|
|
469
|
+
await this.executeUnsource(preview);
|
|
470
|
+
return this.generateDocument(subject);
|
|
471
|
+
}
|
|
472
|
+
|
|
338
473
|
/**
|
|
339
474
|
* Seed built-in tool providers and tools if they don't exist yet.
|
|
340
475
|
* Called on every startup (after state load/restore) — safe to call repeatedly.
|
|
@@ -1087,6 +1222,7 @@ const toolNextSteps = new Set([
|
|
|
1087
1222
|
LLMNextStep.HandleEiHeartbeat,
|
|
1088
1223
|
LLMNextStep.HandleToolContinuation,
|
|
1089
1224
|
LLMNextStep.HandleDedupCurate,
|
|
1225
|
+
LLMNextStep.HandleKnowledgeSynthesis,
|
|
1090
1226
|
]);
|
|
1091
1227
|
const toolPersonaId =
|
|
1092
1228
|
personaId ??
|
|
@@ -1101,9 +1237,17 @@ const toolNextSteps = new Set([
|
|
|
1101
1237
|
(request.next_step === LLMNextStep.HandleToolContinuation &&
|
|
1102
1238
|
request.data.originalNextStep === LLMNextStep.HandleDedupCurate);
|
|
1103
1239
|
|
|
1240
|
+
const isSynthesisRequest = request.next_step === LLMNextStep.HandleKnowledgeSynthesis ||
|
|
1241
|
+
(request.next_step === LLMNextStep.HandleToolContinuation &&
|
|
1242
|
+
request.data.originalNextStep === LLMNextStep.HandleKnowledgeSynthesis);
|
|
1243
|
+
|
|
1104
1244
|
let tools: ToolDefinition[] = [];
|
|
1105
1245
|
if (isDedupRequest) {
|
|
1106
1246
|
tools = SYSTEM_TOOLS.filter(t => t.name === "find_memory");
|
|
1247
|
+
} else if (isSynthesisRequest) {
|
|
1248
|
+
tools = SYSTEM_TOOLS.filter(t =>
|
|
1249
|
+
t.name === "find_memory" || t.name === "fetch_memory" || t.name === "fetch_message"
|
|
1250
|
+
);
|
|
1107
1251
|
} else if (toolNextSteps.has(request.next_step) && toolPersonaId) {
|
|
1108
1252
|
tools = [...SYSTEM_TOOLS, ...this.stateManager.tools_getForPersona(toolPersonaId, this.isTUI)];
|
|
1109
1253
|
}
|
|
@@ -1760,6 +1904,17 @@ const toolNextSteps = new Set([
|
|
|
1760
1904
|
this.interface.onHumanUpdated?.();
|
|
1761
1905
|
}
|
|
1762
1906
|
}
|
|
1907
|
+
|
|
1908
|
+
const isSynthesisCompletion =
|
|
1909
|
+
response.request.next_step === LLMNextStep.HandleKnowledgeSynthesis ||
|
|
1910
|
+
(response.request.next_step === LLMNextStep.HandleToolContinuation &&
|
|
1911
|
+
response.request.data.originalNextStep === LLMNextStep.HandleKnowledgeSynthesis);
|
|
1912
|
+
if (isSynthesisCompletion) {
|
|
1913
|
+
const slug = response.request.data.slug as string;
|
|
1914
|
+
const hasContent = slug && this.stateManager.messages_get("emmet")
|
|
1915
|
+
.some(m => m.source_tag === `generate:document:${slug}`);
|
|
1916
|
+
if (hasContent) this.interface.onDocumentGenerated?.(slug);
|
|
1917
|
+
}
|
|
1763
1918
|
} catch (err) {
|
|
1764
1919
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1765
1920
|
const result = this.stateManager.queue_fail(response.request.id, errorMsg);
|
|
@@ -51,6 +51,15 @@ export async function getQueueStatus(sm: StateManager): Promise<QueueStatus> {
|
|
|
51
51
|
}
|
|
52
52
|
const extracting_documents = extractingSet.size > 0 ? Array.from(extractingSet) : undefined;
|
|
53
53
|
|
|
54
|
+
const generatingSet: string[] = [];
|
|
55
|
+
for (const item of activeItems) {
|
|
56
|
+
if (item.next_step === LLMNextStep.HandleKnowledgeSynthesis) {
|
|
57
|
+
const slug = (item.data as { slug?: string }).slug;
|
|
58
|
+
if (slug) generatingSet.push(slug);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const generating_documents = generatingSet.length > 0 ? generatingSet : undefined;
|
|
62
|
+
|
|
54
63
|
return {
|
|
55
64
|
state: sm.queue_isPaused()
|
|
56
65
|
? "paused"
|
|
@@ -62,6 +71,7 @@ export async function getQueueStatus(sm: StateManager): Promise<QueueStatus> {
|
|
|
62
71
|
embedding_warning: sm.embedding_getWarning() || undefined,
|
|
63
72
|
pending_documents,
|
|
64
73
|
extracting_documents,
|
|
74
|
+
generating_documents,
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -71,6 +71,7 @@ export class StateManager {
|
|
|
71
71
|
this.migrateProviderModel();
|
|
72
72
|
this.migrateThemes();
|
|
73
73
|
this.migrateFfaParentIds();
|
|
74
|
+
this.migrateDocumentSettings();
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
/**
|
|
@@ -586,6 +587,40 @@ export class StateManager {
|
|
|
586
587
|
}
|
|
587
588
|
}
|
|
588
589
|
|
|
590
|
+
private migrateDocumentSettings(): void {
|
|
591
|
+
const human = this.humanState.get();
|
|
592
|
+
const doc = human.settings?.document;
|
|
593
|
+
if (!doc) return;
|
|
594
|
+
|
|
595
|
+
let migrated = false;
|
|
596
|
+
|
|
597
|
+
const existing = doc.processed_documents ?? {};
|
|
598
|
+
for (const [key, value] of Object.entries(existing)) {
|
|
599
|
+
if (typeof value === "string") {
|
|
600
|
+
(existing as Record<string, unknown>)[key] = { created_at: value, type: "imported" };
|
|
601
|
+
migrated = true;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const legacy = (doc as Record<string, unknown>).generated_documents as
|
|
606
|
+
| Record<string, { subject: string; created_at: string }>
|
|
607
|
+
| undefined;
|
|
608
|
+
if (legacy) {
|
|
609
|
+
for (const [slug, record] of Object.entries(legacy)) {
|
|
610
|
+
existing[slug] = { created_at: record.created_at, type: "generated", subject: record.subject };
|
|
611
|
+
}
|
|
612
|
+
delete (doc as Record<string, unknown>).generated_documents;
|
|
613
|
+
migrated = true;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (migrated) {
|
|
617
|
+
doc.processed_documents = existing as import("./types/entities.js").DocumentSettings["processed_documents"];
|
|
618
|
+
this.humanState.set(human);
|
|
619
|
+
this.scheduleSave();
|
|
620
|
+
console.log("[StateManager] Migrated document settings to unified processed_documents schema");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
589
624
|
getHuman(): HumanEntity {
|
|
590
625
|
return this.humanState.get();
|
|
591
626
|
}
|
|
@@ -4,20 +4,20 @@ import type { Fact, Topic, Person, Quote, HumanEntity } from "../../types.js";
|
|
|
4
4
|
type GetHuman = () => HumanEntity;
|
|
5
5
|
|
|
6
6
|
function cleanFact(f: Fact): Record<string, unknown> {
|
|
7
|
-
const { embedding,
|
|
8
|
-
void embedding; void
|
|
7
|
+
const { embedding, persona_groups, ...rest } = f;
|
|
8
|
+
void embedding; void persona_groups;
|
|
9
9
|
return rest;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
function cleanTopic(t: Topic): Record<string, unknown> {
|
|
13
|
-
const { embedding,
|
|
14
|
-
void embedding; void
|
|
13
|
+
const { embedding, rewrite_length_floor, persona_groups, last_ei_asked, ...rest } = t;
|
|
14
|
+
void embedding; void rewrite_length_floor; void persona_groups; void last_ei_asked;
|
|
15
15
|
return rest;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function cleanPerson(p: Person): Record<string, unknown> {
|
|
19
|
-
const { embedding,
|
|
20
|
-
void embedding; void
|
|
19
|
+
const { embedding, rewrite_length_floor, persona_groups, last_ei_asked, ...rest } = p;
|
|
20
|
+
void embedding; void rewrite_length_floor; void persona_groups; void last_ei_asked;
|
|
21
21
|
return rest;
|
|
22
22
|
}
|
|
23
23
|
|
package/src/core/tools/index.ts
CHANGED
package/src/core/tools/types.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/** A single tool call the LLM wants to make (from the API response). */
|
|
7
7
|
export interface ToolCall {
|
|
8
8
|
id: string; // call_abc123 — must be echoed back in tool result message
|
|
9
|
-
name: string; // snake_case tool name ("web_search", "
|
|
9
|
+
name: string; // snake_case tool name ("web_search", "find_memory")
|
|
10
10
|
arguments: Record<string, unknown>; // Parsed from JSON string in the API response
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -18,7 +18,7 @@ export interface DataItemBase {
|
|
|
18
18
|
sources?: string[]; // Namespaced source identifiers — where items were learned from. Format: "provider:id" (e.g., "opencode:ses_abc123", "cursor:composerId"). Grow-only union.
|
|
19
19
|
persona_groups?: string[];
|
|
20
20
|
embedding?: number[];
|
|
21
|
-
|
|
21
|
+
rewrite_length_floor?: number; // Set after every rewrite scan: ceil(description.length * 1.1). Item is skipped by ceremony until description grows past this floor. Preserved across extraction upserts — only cleared when description exceeds it.
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface Fact extends DataItemBase {
|
|
@@ -20,9 +20,15 @@ export interface OpenCodeSettings {
|
|
|
20
20
|
processed_sessions?: Record<string, string>; // sessionId → ISO timestamp of last import
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
export interface DocumentRecord {
|
|
24
|
+
created_at: string;
|
|
25
|
+
type: "imported" | "generated";
|
|
26
|
+
subject?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
export interface DocumentSettings {
|
|
24
30
|
extraction_model?: string;
|
|
25
|
-
processed_documents?: Record<string,
|
|
31
|
+
processed_documents?: Record<string, DocumentRecord>;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export interface CeremonyConfig {
|
package/src/core/types/enums.ts
CHANGED
|
@@ -53,6 +53,7 @@ export enum LLMNextStep {
|
|
|
53
53
|
HandleTopicValidate = "handleTopicValidate",
|
|
54
54
|
HandleReflectionCritic = "handleReflectionCritic",
|
|
55
55
|
HandleDocumentSegmentation = "handleDocumentSegmentation",
|
|
56
|
+
HandleKnowledgeSynthesis = "handleKnowledgeSynthesis",
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export enum ProviderType {
|
|
@@ -29,7 +29,7 @@ export interface ToolProvider {
|
|
|
29
29
|
export interface ToolDefinition {
|
|
30
30
|
id: string; // UUID
|
|
31
31
|
provider_id: string; // FK → ToolProvider.id (required)
|
|
32
|
-
name: string; // Snake_case machine name ("web_search", "
|
|
32
|
+
name: string; // Snake_case machine name ("web_search", "find_memory")
|
|
33
33
|
display_name: string; // Human label
|
|
34
34
|
description: string; // What the LLM reads to decide whether to call this tool
|
|
35
35
|
input_schema: Record<string, unknown>; // JSON Schema for parameters the LLM can pass
|
|
@@ -77,6 +77,7 @@ export interface QueueStatus {
|
|
|
77
77
|
embedding_warning?: boolean;
|
|
78
78
|
pending_documents?: Array<{ batchId: string; filename: string; count: number }>;
|
|
79
79
|
extracting_documents?: string[];
|
|
80
|
+
generating_documents?: string[];
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
export interface EiError {
|
|
@@ -121,6 +122,7 @@ export interface Ei_Interface {
|
|
|
121
122
|
onRoomMessageAdded?: (roomId: string) => void;
|
|
122
123
|
onRoomMessageQueued?: (roomId: string) => void;
|
|
123
124
|
onRoomMessageProcessing?: (roomId: string) => void;
|
|
125
|
+
onDocumentGenerated?: (slug: string) => void;
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
// =============================================================================
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
queueAllScans,
|
|
12
12
|
type ExtractionContext,
|
|
13
13
|
} from "../../core/orchestrators/human-extraction.js";
|
|
14
|
+
import {
|
|
15
|
+
queuePersonRewritePhase,
|
|
16
|
+
queueTopicRewritePhase,
|
|
17
|
+
} from "../../core/orchestrators/ceremony.js";
|
|
14
18
|
import { isProcessRunning } from "../process-check.js";
|
|
15
19
|
import { getMachineId } from "../machine-id.js";
|
|
16
20
|
|
|
@@ -268,6 +272,8 @@ export async function importClaudeCodeSessions(
|
|
|
268
272
|
sources: [`claudecode:${getMachineId()}:${targetSession.id}`],
|
|
269
273
|
};
|
|
270
274
|
|
|
275
|
+
queuePersonRewritePhase(stateManager);
|
|
276
|
+
queueTopicRewritePhase(stateManager);
|
|
271
277
|
const ccSettings = stateManager.getHuman().settings?.claudeCode;
|
|
272
278
|
queueAllScans(context, stateManager, {
|
|
273
279
|
extraction_model: ccSettings?.extraction_model,
|
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
queueAllScans,
|
|
14
14
|
type ExtractionContext,
|
|
15
15
|
} from "../../core/orchestrators/human-extraction.js";
|
|
16
|
+
import {
|
|
17
|
+
queuePersonRewritePhase,
|
|
18
|
+
queueTopicRewritePhase,
|
|
19
|
+
} from "../../core/orchestrators/ceremony.js";
|
|
16
20
|
|
|
17
21
|
export interface CursorImportResult {
|
|
18
22
|
sessionsProcessed: number;
|
|
@@ -227,6 +231,8 @@ export async function importCursorSessions(
|
|
|
227
231
|
sources: [`cursor:${getMachineId()}:${targetSession.id}`],
|
|
228
232
|
};
|
|
229
233
|
|
|
234
|
+
queuePersonRewritePhase(stateManager);
|
|
235
|
+
queueTopicRewritePhase(stateManager);
|
|
230
236
|
queueAllScans(context, stateManager, { external_filter: "only" });
|
|
231
237
|
result.extractionScansQueued += 4;
|
|
232
238
|
}
|
|
@@ -150,13 +150,15 @@ export async function executeUnsource(
|
|
|
150
150
|
stateManager.messages_remove("emmet", sourceMessageIds);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const
|
|
153
|
+
const key = preview.sourceTag.startsWith("import:document:")
|
|
154
154
|
? preview.sourceTag.slice("import:document:".length)
|
|
155
|
-
: preview.sourceTag
|
|
155
|
+
: preview.sourceTag.startsWith("generate:document:")
|
|
156
|
+
? preview.sourceTag.slice("generate:document:".length)
|
|
157
|
+
: preview.sourceTag;
|
|
156
158
|
|
|
157
159
|
const human = stateManager.getHuman();
|
|
158
160
|
if (human.settings?.document?.processed_documents) {
|
|
159
|
-
delete human.settings.document.processed_documents[
|
|
161
|
+
delete human.settings.document.processed_documents[key];
|
|
160
162
|
stateManager.setHuman(human);
|
|
161
163
|
}
|
|
162
164
|
|
|
@@ -8,6 +8,10 @@ import {
|
|
|
8
8
|
queueAllScans,
|
|
9
9
|
type ExtractionContext,
|
|
10
10
|
} from "../../core/orchestrators/human-extraction.js";
|
|
11
|
+
import {
|
|
12
|
+
queuePersonRewritePhase,
|
|
13
|
+
queueTopicRewritePhase,
|
|
14
|
+
} from "../../core/orchestrators/ceremony.js";
|
|
11
15
|
import { isProcessRunning } from "../process-check.js";
|
|
12
16
|
import { getMachineId } from "../machine-id.js";
|
|
13
17
|
|
|
@@ -195,6 +199,7 @@ export async function importOpenCodeSessions(
|
|
|
195
199
|
|
|
196
200
|
const cutoffIso = processedSessions[targetSession.id] ?? null;
|
|
197
201
|
const cutoffMs = cutoffIso ? new Date(cutoffIso).getTime() : null;
|
|
202
|
+
let anyPersonaHasChanges = false;
|
|
198
203
|
|
|
199
204
|
for (const [, { persona, msgs: agentMsgs, isNew, agentName }] of byPersonaId) {
|
|
200
205
|
if (isNew) {
|
|
@@ -252,6 +257,7 @@ export async function importOpenCodeSessions(
|
|
|
252
257
|
};
|
|
253
258
|
|
|
254
259
|
if (!signal?.aborted) {
|
|
260
|
+
anyPersonaHasChanges = true;
|
|
255
261
|
const openCodeSettings = stateManager.getHuman().settings?.opencode;
|
|
256
262
|
queueAllScans(context, stateManager, {
|
|
257
263
|
extraction_model: openCodeSettings?.extraction_model,
|
|
@@ -264,7 +270,13 @@ export async function importOpenCodeSessions(
|
|
|
264
270
|
|
|
265
271
|
result.sessionsProcessed = 1;
|
|
266
272
|
|
|
267
|
-
// ─── Step 6:
|
|
273
|
+
// ─── Step 6: Queue rewrite checks if any persona had new messages ─────
|
|
274
|
+
if (anyPersonaHasChanges && !signal?.aborted) {
|
|
275
|
+
queuePersonRewritePhase(stateManager);
|
|
276
|
+
queueTopicRewritePhase(stateManager);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ─── Step 7: Advance extraction state ────────────────────────────────
|
|
268
280
|
updateExtractionState(stateManager, targetSession);
|
|
269
281
|
|
|
270
282
|
console.log(
|
|
@@ -6,6 +6,10 @@ import {
|
|
|
6
6
|
queueFactFind,
|
|
7
7
|
type ExtractionContext,
|
|
8
8
|
} from "../../core/orchestrators/human-extraction.js";
|
|
9
|
+
import {
|
|
10
|
+
queuePersonRewritePhase,
|
|
11
|
+
queueTopicRewritePhase,
|
|
12
|
+
} from "../../core/orchestrators/ceremony.js";
|
|
9
13
|
|
|
10
14
|
export interface PersonaHistoryImportResult {
|
|
11
15
|
daysQueued: number;
|
|
@@ -141,6 +145,11 @@ export async function importPersonaHistory(
|
|
|
141
145
|
|
|
142
146
|
result.daysQueued = 1;
|
|
143
147
|
|
|
148
|
+
if (result.scansQueued > 0) {
|
|
149
|
+
queuePersonRewritePhase(stateManager);
|
|
150
|
+
queueTopicRewritePhase(stateManager);
|
|
151
|
+
}
|
|
152
|
+
|
|
144
153
|
const isLastDay = currentDate >= today;
|
|
145
154
|
advanceProgress(stateManager, currentDate, isLastDay);
|
|
146
155
|
|
|
@@ -45,7 +45,7 @@ Rules:
|
|
|
45
45
|
- Be specific: "React performance patterns" beats "technical stuff"
|
|
46
46
|
- If the record is clean — everything in it passes the test — return an empty array
|
|
47
47
|
|
|
48
|
-
Return a raw JSON array of strings. No markdown fencing, no commentary.
|
|
48
|
+
Return a raw JSON array of strings. No markdown fencing, no commentary. Thinking text WILL break the parser.
|
|
49
49
|
|
|
50
50
|
Example — a Person named "Nicholas" whose description includes sprint ticket numbers:
|
|
51
51
|
["CMIDP sprint ticket assignments", "ASU Data Lake access provisioning details"]`;
|
|
@@ -60,7 +60,7 @@ Example — a Person named "Nicholas" whose description includes sprint ticket n
|
|
|
60
60
|
|
|
61
61
|
---
|
|
62
62
|
|
|
63
|
-
Return a raw JSON array of subject phrases found in this Person record that don't belong there. Return [] if the record is clean.`;
|
|
63
|
+
Return a raw JSON array of subject phrases found in this Person record that don't belong there. Return [] if the record is clean. Thinking text WILL break the parser.`;
|
|
64
64
|
|
|
65
65
|
return { system, user };
|
|
66
66
|
}
|
|
@@ -35,7 +35,7 @@ Rules:
|
|
|
35
35
|
- Be specific: "TypeScript coding conventions" beats "technical preferences"
|
|
36
36
|
- If the record is cohesive and on-topic despite its length, return an empty array
|
|
37
37
|
${technicalGuidance}
|
|
38
|
-
Return a raw JSON array of strings. No markdown fencing, no commentary.
|
|
38
|
+
Return a raw JSON array of strings. No markdown fencing, no commentary. Thinking text WILL break the parser.
|
|
39
39
|
|
|
40
40
|
Example — a Topic named "Software Engineering" whose description also discusses vim keybindings, git conventions, and AI tooling:
|
|
41
41
|
["vim keybindings and editor configuration", "git and GitHub workflow conventions", "AI coding assistant preferences"]`;
|
|
@@ -51,7 +51,7 @@ Example — a Topic named "Software Engineering" whose description also discusse
|
|
|
51
51
|
]
|
|
52
52
|
\`\`\`
|
|
53
53
|
|
|
54
|
-
Respond with raw JSON array only.`;
|
|
54
|
+
Respond with raw JSON array only. Thinking text WILL break the parser.`;
|
|
55
55
|
|
|
56
56
|
const user = `${payload}
|
|
57
57
|
|