ei-tui 1.1.0 → 1.3.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 +16 -0
- package/package.json +2 -23
- package/src/cli/README.md +12 -2
- package/src/cli/mcp.ts +12 -4
- package/src/cli/retrieval.ts +162 -0
- package/src/cli.ts +7 -1
- package/src/core/handlers/dedup.ts +4 -15
- package/src/core/handlers/document-segmentation.ts +5 -7
- 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 +48 -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/heartbeat-manager.ts +10 -0
- package/src/core/llm-client.ts +13 -3
- package/src/core/message-manager.ts +2 -4
- package/src/core/orchestrators/ceremony.ts +45 -22
- package/src/core/orchestrators/human-extraction.ts +10 -1
- package/src/core/processor.ts +275 -7
- 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/builtin/fetch-message.ts +27 -1
- package/src/core/tools/builtin/find-memory.ts +11 -3
- package/src/core/tools/index.ts +3 -3
- 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/core/types/llm.ts +0 -9
- package/src/core/utils/message-id.ts +114 -0
- package/src/integrations/claude-code/importer.ts +12 -5
- package/src/integrations/cursor/importer.ts +12 -5
- package/src/integrations/document/importer.ts +1 -1
- package/src/integrations/document/unsource.ts +11 -14
- package/src/integrations/opencode/importer.ts +19 -6
- package/src/integrations/opencode/json-reader.ts +65 -0
- package/src/integrations/opencode/sqlite-reader.ts +33 -0
- package/src/integrations/opencode/types.ts +8 -0
- 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/heartbeat/check.ts +5 -2
- package/src/prompts/heartbeat/ei.ts +7 -0
- package/src/prompts/heartbeat/types.ts +5 -0
- package/src/prompts/index.ts +3 -0
- package/src/prompts/response/sections.ts +30 -16
- package/src/prompts/room/sections.ts +28 -6
- package/src/prompts/synthesis/index.ts +101 -0
- package/src/prompts/synthesis/types.ts +26 -0
- package/src/prompts/trait-utils.ts +33 -0
- package/tui/README.md +2 -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/help-content.ts +11 -0
- 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
|
@@ -12,8 +12,7 @@ export function handlePersonaTopicRating(response: LLMResponse, state: StateMana
|
|
|
12
12
|
const personaId = response.request.data.personaId as string;
|
|
13
13
|
const personaDisplayName = response.request.data.personaDisplayName as string;
|
|
14
14
|
if (!personaId || !personaDisplayName) {
|
|
15
|
-
|
|
16
|
-
return;
|
|
15
|
+
throw new Error("[handlePersonaTopicRating] Missing personaId or personaDisplayName in request data");
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
const result = response.parsed as PersonaTopicRatingResult | undefined;
|
|
@@ -35,8 +34,7 @@ export function handlePersonaTopicRating(response: LLMResponse, state: StateMana
|
|
|
35
34
|
|
|
36
35
|
const persona = state.persona_getById(personaId);
|
|
37
36
|
if (!persona) {
|
|
38
|
-
|
|
39
|
-
return;
|
|
37
|
+
throw new Error(`[handlePersonaTopicRating] Persona not found: ${personaDisplayName}`);
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
const now = new Date().toISOString();
|
|
@@ -20,6 +20,8 @@ import { getEmbeddingService, getItemEmbeddingText } from "../embedding-service.
|
|
|
20
20
|
|
|
21
21
|
import { searchHumanData } from "../human-data-manager.js";
|
|
22
22
|
|
|
23
|
+
const MIN_REWRITE_FLOOR = 750;
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* handleRewriteScan — Phase 1 of Rewrite.
|
|
25
27
|
* LLM returns an array of subject strings found in the bloated item.
|
|
@@ -31,20 +33,25 @@ export async function handleRewriteScan(response: LLMResponse, state: StateManag
|
|
|
31
33
|
const rewriteModel = response.request.data.rewriteModel as string;
|
|
32
34
|
|
|
33
35
|
if (!itemId || !itemType) {
|
|
34
|
-
|
|
35
|
-
return;
|
|
36
|
+
throw new Error("[handleRewriteScan] Missing itemId or itemType in request data");
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
const subjects = response.parsed as RewriteScanResult | undefined;
|
|
39
40
|
if (!subjects || !Array.isArray(subjects) || subjects.length === 0) {
|
|
40
|
-
console.log(`[handleRewriteScan] No extra subjects found for ${itemType} "${itemId}" —
|
|
41
|
+
console.log(`[handleRewriteScan] No extra subjects found for ${itemType} "${itemId}" — setting rewrite_length_floor`);
|
|
41
42
|
const human = state.getHuman();
|
|
42
43
|
if (itemType === "topic") {
|
|
43
44
|
const topic = human.topics.find(t => t.id === itemId);
|
|
44
|
-
if (topic) state.human_topic_upsert({
|
|
45
|
+
if (topic) state.human_topic_upsert({
|
|
46
|
+
...topic,
|
|
47
|
+
rewrite_length_floor: Math.max(MIN_REWRITE_FLOOR, Math.ceil((topic.description?.length ?? 0) * 1.1)),
|
|
48
|
+
});
|
|
45
49
|
} else if (itemType === "person") {
|
|
46
50
|
const person = human.people.find(p => p.id === itemId);
|
|
47
|
-
if (person) state.human_person_upsert({
|
|
51
|
+
if (person) state.human_person_upsert({
|
|
52
|
+
...person,
|
|
53
|
+
rewrite_length_floor: Math.max(MIN_REWRITE_FLOOR, Math.ceil((person.description?.length ?? 0) * 1.1)),
|
|
54
|
+
});
|
|
48
55
|
}
|
|
49
56
|
return;
|
|
50
57
|
}
|
|
@@ -111,8 +118,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
111
118
|
const itemType = response.request.data.itemType as RewriteItemType;
|
|
112
119
|
|
|
113
120
|
if (!itemId || !itemType) {
|
|
114
|
-
|
|
115
|
-
return;
|
|
121
|
+
throw new Error("[handleRewriteRewrite] Missing itemId or itemType in request data");
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
const result = response.parsed as RewriteResult | undefined;
|
|
@@ -171,6 +177,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
171
177
|
console.warn(`[handleRewriteRewrite] Failed to compute embedding for existing ${resolvedType} "${item.name}":`, err);
|
|
172
178
|
}
|
|
173
179
|
|
|
180
|
+
const existingFloor = Math.max(MIN_REWRITE_FLOOR, Math.ceil(item.description.length * 1.1));
|
|
174
181
|
switch (resolvedType) {
|
|
175
182
|
case "topic": {
|
|
176
183
|
const existing = human.topics.find(t => t.id === item.id)!;
|
|
@@ -181,6 +188,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
181
188
|
sentiment: item.sentiment ?? existing.sentiment,
|
|
182
189
|
last_updated: now,
|
|
183
190
|
embedding,
|
|
191
|
+
rewrite_length_floor: existingFloor,
|
|
184
192
|
});
|
|
185
193
|
break;
|
|
186
194
|
}
|
|
@@ -193,6 +201,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
193
201
|
sentiment: item.sentiment ?? existing.sentiment,
|
|
194
202
|
last_updated: now,
|
|
195
203
|
embedding,
|
|
204
|
+
rewrite_length_floor: existingFloor,
|
|
196
205
|
});
|
|
197
206
|
break;
|
|
198
207
|
}
|
|
@@ -216,6 +225,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
216
225
|
console.warn(`[handleRewriteRewrite] Failed to compute embedding for new ${item.type} "${item.name}":`, err);
|
|
217
226
|
}
|
|
218
227
|
|
|
228
|
+
const newFloor = Math.max(MIN_REWRITE_FLOOR, Math.ceil(item.description.length * 1.1));
|
|
219
229
|
const baseFields = {
|
|
220
230
|
id: crypto.randomUUID(),
|
|
221
231
|
name: item.name,
|
|
@@ -227,6 +237,7 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
227
237
|
persona_groups: unionGroups,
|
|
228
238
|
interested_personas: unionPersonas,
|
|
229
239
|
embedding,
|
|
240
|
+
rewrite_length_floor: newFloor,
|
|
230
241
|
};
|
|
231
242
|
|
|
232
243
|
switch (item.type) {
|
|
@@ -267,10 +278,16 @@ export async function handleRewriteRewrite(response: LLMResponse, state: StateMa
|
|
|
267
278
|
const updatedHuman = state.getHuman();
|
|
268
279
|
if (itemType === "topic") {
|
|
269
280
|
const original = updatedHuman.topics.find(t => t.id === itemId);
|
|
270
|
-
if (original) state.human_topic_upsert({
|
|
281
|
+
if (original) state.human_topic_upsert({
|
|
282
|
+
...original,
|
|
283
|
+
rewrite_length_floor: Math.max(MIN_REWRITE_FLOOR, Math.ceil((original.description?.length ?? 0) * 1.1)),
|
|
284
|
+
});
|
|
271
285
|
} else if (itemType === "person") {
|
|
272
286
|
const original = updatedHuman.people.find(p => p.id === itemId);
|
|
273
|
-
if (original) state.human_person_upsert({
|
|
287
|
+
if (original) state.human_person_upsert({
|
|
288
|
+
...original,
|
|
289
|
+
rewrite_length_floor: Math.max(MIN_REWRITE_FLOOR, Math.ceil((original.description?.length ?? 0) * 1.1)),
|
|
290
|
+
});
|
|
274
291
|
}
|
|
275
292
|
|
|
276
293
|
console.log(`[handleRewriteRewrite] Complete for ${itemType} "${itemId}": ${existingCount} existing updated, ${newCount} new created`);
|
|
@@ -17,8 +17,7 @@ export function handleRoomResponse(response: LLMResponse, state: StateManager):
|
|
|
17
17
|
const parentMessageId = response.request.data.parentMessageId as string | null ?? null;
|
|
18
18
|
|
|
19
19
|
if (!roomId || !personaId) {
|
|
20
|
-
|
|
21
|
-
return;
|
|
20
|
+
throw new Error("[handleRoomResponse] Missing roomId or personaId in request data");
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
const now = new Date().toISOString();
|
|
@@ -111,19 +110,16 @@ export async function handleRoomJudge(response: LLMResponse, state: StateManager
|
|
|
111
110
|
const judgeDisplayName = response.request.data.judgePersonaDisplayName as string;
|
|
112
111
|
|
|
113
112
|
if (!roomId) {
|
|
114
|
-
|
|
115
|
-
return;
|
|
113
|
+
throw new Error("[handleRoomJudge] Missing roomId in request data");
|
|
116
114
|
}
|
|
117
115
|
|
|
118
116
|
if (!response.parsed) {
|
|
119
|
-
|
|
120
|
-
return;
|
|
117
|
+
throw new Error(`[handleRoomJudge] No parsed result from judge ${judgeDisplayName}`);
|
|
121
118
|
}
|
|
122
119
|
|
|
123
120
|
const result = response.parsed as RoomJudgeResult;
|
|
124
121
|
if (!result.winner_message_id) {
|
|
125
|
-
|
|
126
|
-
return;
|
|
122
|
+
throw new Error(`[handleRoomJudge] Judge ${judgeDisplayName} returned no winner_message_id`);
|
|
127
123
|
}
|
|
128
124
|
|
|
129
125
|
const judgePersonaId = response.request.data.judgePersonaId as string;
|
|
@@ -131,16 +127,14 @@ export async function handleRoomJudge(response: LLMResponse, state: StateManager
|
|
|
131
127
|
const allMessages = state.getRoomMessages(roomId);
|
|
132
128
|
const winner = allMessages.find(m => m.id === result.winner_message_id);
|
|
133
129
|
if (!winner) {
|
|
134
|
-
|
|
135
|
-
return;
|
|
130
|
+
throw new Error(`[handleRoomJudge] Winner message ${result.winner_message_id} not found in room ${roomId}`);
|
|
136
131
|
}
|
|
137
132
|
|
|
138
133
|
const verdictParentId = winner.parent_id;
|
|
139
134
|
|
|
140
135
|
const ok = state.setRoomActiveNode(roomId, result.winner_message_id);
|
|
141
136
|
if (!ok) {
|
|
142
|
-
|
|
143
|
-
return;
|
|
137
|
+
throw new Error(`[handleRoomJudge] Could not set active node ${result.winner_message_id} in room ${roomId}`);
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
const losers = allMessages
|
|
@@ -168,6 +168,16 @@ export async function queueEiHeartbeat(
|
|
|
168
168
|
});
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
// Ei's own pending reflection — separate item type so she can introspect on herself
|
|
172
|
+
const eiPersona = personas.find((p) => p.id === "ei");
|
|
173
|
+
if (eiPersona?.pending_update?.critique) {
|
|
174
|
+
items.push({
|
|
175
|
+
id: eiPersona.id,
|
|
176
|
+
type: "Self Reflection Alert",
|
|
177
|
+
critique: eiPersona.pending_update.critique,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
171
181
|
const personasWithPendingUpdate = personas.filter(
|
|
172
182
|
(p) => !p.is_archived && !p.is_paused && !p.is_static && p.id !== "ei" && p.pending_update?.critique
|
|
173
183
|
);
|
package/src/core/llm-client.ts
CHANGED
|
@@ -76,7 +76,17 @@ export interface LLMRawResponse {
|
|
|
76
76
|
|
|
77
77
|
let llmCallCount = 0;
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
function resolveApiKey(raw: string | undefined): string {
|
|
80
|
+
if (!raw || !raw.startsWith("$")) return raw ?? "";
|
|
81
|
+
const varName = raw.slice(1);
|
|
82
|
+
const resolved =
|
|
83
|
+
(typeof Bun !== "undefined" && (Bun as { env: Record<string, string> }).env?.[varName]) ||
|
|
84
|
+
(typeof process !== "undefined" && process.env?.[varName]);
|
|
85
|
+
if (!resolved) {
|
|
86
|
+
throw new Error(`Provider API key references env var $${varName}, but it is not set.`);
|
|
87
|
+
}
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
80
90
|
|
|
81
91
|
function isGuid(str: string): boolean {
|
|
82
92
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(str);
|
|
@@ -90,7 +100,7 @@ function buildResolvedModel(account: ProviderAccount, model: ModelConfig): Resol
|
|
|
90
100
|
config: {
|
|
91
101
|
name: account.name,
|
|
92
102
|
baseURL: account.url,
|
|
93
|
-
apiKey: account.api_key
|
|
103
|
+
apiKey: resolveApiKey(account.api_key),
|
|
94
104
|
},
|
|
95
105
|
extraHeaders: account.extra_headers,
|
|
96
106
|
};
|
|
@@ -171,7 +181,7 @@ export function resolveModel(modelSpec?: string, accounts?: ProviderAccount[]):
|
|
|
171
181
|
config: {
|
|
172
182
|
name: matchingAccount.name,
|
|
173
183
|
baseURL: matchingAccount.url,
|
|
174
|
-
apiKey: matchingAccount.api_key
|
|
184
|
+
apiKey: resolveApiKey(matchingAccount.api_key),
|
|
175
185
|
},
|
|
176
186
|
extraHeaders: matchingAccount.extra_headers,
|
|
177
187
|
};
|
|
@@ -255,8 +255,6 @@ export function checkAndQueueHumanExtraction(
|
|
|
255
255
|
const unextractedPeople = sm.messages_getUnextracted(personaId, "p", undefined, "exclude");
|
|
256
256
|
const peopleThreshold = Math.min(EXTRACTION_TAPER_CAP, human.people.length);
|
|
257
257
|
if (unextractedPeople.length > 0 && unextractedPeople.length >= peopleThreshold) {
|
|
258
|
-
const personaForScan = sm.persona_getById(personaId);
|
|
259
|
-
const personScanOptions = personaForScan?.pending_update ? { reflection_progress: 1 } : undefined;
|
|
260
258
|
const context: ExtractionContext = {
|
|
261
259
|
personaId,
|
|
262
260
|
channelDisplayName: personaDisplayName,
|
|
@@ -264,9 +262,9 @@ export function checkAndQueueHumanExtraction(
|
|
|
264
262
|
messages_analyze: unextractedPeople,
|
|
265
263
|
extraction_flag: "p",
|
|
266
264
|
};
|
|
267
|
-
queuePersonScan(context, sm
|
|
265
|
+
queuePersonScan(context, sm);
|
|
268
266
|
console.log(
|
|
269
|
-
`[Processor] Human Seed extraction: people (threshold: ${peopleThreshold}, unextracted: ${unextractedPeople.length}
|
|
267
|
+
`[Processor] Human Seed extraction: people (threshold: ${peopleThreshold}, unextracted: ${unextractedPeople.length})`
|
|
270
268
|
);
|
|
271
269
|
}
|
|
272
270
|
}
|
|
@@ -137,10 +137,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
|
|
|
137
137
|
messages_analyze: unextractedPeople,
|
|
138
138
|
extraction_flag: "p",
|
|
139
139
|
};
|
|
140
|
-
|
|
141
|
-
? { ...options, reflection_progress: 1 }
|
|
142
|
-
: options;
|
|
143
|
-
queuePersonScan(context, state, personScanOptions);
|
|
140
|
+
queuePersonScan(context, state, options);
|
|
144
141
|
}
|
|
145
142
|
|
|
146
143
|
const totalUnextracted = unextractedFacts.length + unextractedTopics.length + unextractedPeople.length;
|
|
@@ -267,14 +264,6 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
267
264
|
!p.is_static
|
|
268
265
|
);
|
|
269
266
|
|
|
270
|
-
const eiIndex = activePersonas.findIndex(p =>
|
|
271
|
-
(p.aliases?.[0] ?? "").toLowerCase() === "ei"
|
|
272
|
-
);
|
|
273
|
-
|
|
274
|
-
// Ei's topics don't change
|
|
275
|
-
if (eiIndex > -1) {
|
|
276
|
-
activePersonas.splice(eiIndex, 1);
|
|
277
|
-
}
|
|
278
267
|
// Decay phase: apply decay + prune for ALL active personas
|
|
279
268
|
for (const persona of activePersonas) {
|
|
280
269
|
applyDecayPhase(persona.id, state);
|
|
@@ -287,7 +276,7 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
287
276
|
// Person Rewrite phase (phase 4): scan bloated Person records, extract Topics from them.
|
|
288
277
|
// Gated via ceremony_progress so Topic Rewrite can run after — Topics created here
|
|
289
278
|
// need to be visible before Topic Rewrite snapshots the threshold.
|
|
290
|
-
queuePersonRewritePhase(state);
|
|
279
|
+
queuePersonRewritePhase(state, { ceremonyProgress: 4 });
|
|
291
280
|
|
|
292
281
|
// Zero-work guard: if no person rewrites queued, advance to topic rewrite immediately
|
|
293
282
|
if (!state.queue_hasPendingCeremonies()) {
|
|
@@ -482,7 +471,7 @@ export function queueReflectionDrain(personaId: string, state: StateManager): vo
|
|
|
482
471
|
messages_analyze: unextractedPeople,
|
|
483
472
|
extraction_flag: "p",
|
|
484
473
|
};
|
|
485
|
-
queuePersonScan(context, state
|
|
474
|
+
queuePersonScan(context, state);
|
|
486
475
|
console.log(`[reflection:drain] Queued Person scan for ${persona.display_name} (${unextractedPeople.length} messages) — clears on completion`);
|
|
487
476
|
}
|
|
488
477
|
|
|
@@ -490,7 +479,7 @@ function getRewriteModel(state: StateManager): string | undefined {
|
|
|
490
479
|
return state.getHuman().settings?.rewrite_model;
|
|
491
480
|
}
|
|
492
481
|
|
|
493
|
-
export function queuePersonRewritePhase(state: StateManager): void {
|
|
482
|
+
export function queuePersonRewritePhase(state: StateManager, options?: { ceremonyProgress?: number }): void {
|
|
494
483
|
const rewriteModel = getRewriteModel(state);
|
|
495
484
|
if (!rewriteModel) {
|
|
496
485
|
console.log("[ceremony:rewrite] rewrite_model not set — skipping person rewrite phase");
|
|
@@ -498,13 +487,30 @@ export function queuePersonRewritePhase(state: StateManager): void {
|
|
|
498
487
|
}
|
|
499
488
|
|
|
500
489
|
const human = state.getHuman();
|
|
501
|
-
const
|
|
490
|
+
const allCandidates = human.people.filter(person => {
|
|
502
491
|
const isPersonaLinked = (person.identifiers ?? []).some(
|
|
503
492
|
i => i.type.toLowerCase() === 'ei persona'
|
|
504
493
|
);
|
|
505
494
|
return !isPersonaLinked
|
|
506
|
-
&& (person.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD
|
|
507
|
-
|
|
495
|
+
&& (person.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const alreadyChecked = allCandidates.filter(p => {
|
|
499
|
+
const descLen = p.description?.length ?? 0;
|
|
500
|
+
return p.rewrite_length_floor !== undefined && descLen < p.rewrite_length_floor;
|
|
501
|
+
});
|
|
502
|
+
if (alreadyChecked.length > 0) {
|
|
503
|
+
for (const person of alreadyChecked) {
|
|
504
|
+
console.log(
|
|
505
|
+
`[ceremony:rewrite] Person "${person.name}" is ${person.description?.length ?? 0} chars ` +
|
|
506
|
+
`(floor: ${person.rewrite_length_floor}) — already reviewed, skipping`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const personsToScan = allCandidates.filter(p => {
|
|
512
|
+
if (p.rewrite_length_floor === undefined) return true;
|
|
513
|
+
return (p.description?.length ?? 0) >= p.rewrite_length_floor;
|
|
508
514
|
});
|
|
509
515
|
|
|
510
516
|
if (personsToScan.length === 0) {
|
|
@@ -527,7 +533,7 @@ export function queuePersonRewritePhase(state: StateManager): void {
|
|
|
527
533
|
itemId: person.id,
|
|
528
534
|
itemType: "person" as RewriteItemType,
|
|
529
535
|
rewriteModel,
|
|
530
|
-
ceremony_progress:
|
|
536
|
+
...(options?.ceremonyProgress !== undefined && { ceremony_progress: options.ceremonyProgress }),
|
|
531
537
|
},
|
|
532
538
|
});
|
|
533
539
|
}
|
|
@@ -543,11 +549,28 @@ export function queueTopicRewritePhase(state: StateManager): void {
|
|
|
543
549
|
}
|
|
544
550
|
|
|
545
551
|
const human = state.getHuman();
|
|
546
|
-
const
|
|
552
|
+
const allCandidateTopics = human.topics.filter(topic =>
|
|
547
553
|
(topic.description?.length ?? 0) > REWRITE_DESCRIPTION_THRESHOLD
|
|
548
|
-
&& !topic.rewrite_checked
|
|
549
554
|
);
|
|
550
555
|
|
|
556
|
+
const alreadyCheckedTopics = allCandidateTopics.filter(t => {
|
|
557
|
+
const descLen = t.description?.length ?? 0;
|
|
558
|
+
return t.rewrite_length_floor !== undefined && descLen < t.rewrite_length_floor;
|
|
559
|
+
});
|
|
560
|
+
if (alreadyCheckedTopics.length > 0) {
|
|
561
|
+
for (const topic of alreadyCheckedTopics) {
|
|
562
|
+
console.log(
|
|
563
|
+
`[ceremony:rewrite] Topic "${topic.name}" is ${topic.description?.length ?? 0} chars ` +
|
|
564
|
+
`(floor: ${topic.rewrite_length_floor}) — already reviewed, skipping`
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const topicsToScan = allCandidateTopics.filter(t => {
|
|
570
|
+
if (t.rewrite_length_floor === undefined) return true;
|
|
571
|
+
return (t.description?.length ?? 0) >= t.rewrite_length_floor;
|
|
572
|
+
});
|
|
573
|
+
|
|
551
574
|
if (topicsToScan.length === 0) {
|
|
552
575
|
console.log("[ceremony:rewrite] No topics above threshold — skipping topic rewrite phase");
|
|
553
576
|
return;
|
|
@@ -592,7 +615,7 @@ function queueEventSummaryForAll(state: StateManager, options?: ExtractionOption
|
|
|
592
615
|
|
|
593
616
|
function queueReflectionPhase(state: StateManager): void {
|
|
594
617
|
const personas = state.persona_getAll().filter(p =>
|
|
595
|
-
!p.is_paused && !p.is_archived && !p.is_static
|
|
618
|
+
!p.is_paused && !p.is_archived && !p.is_static
|
|
596
619
|
);
|
|
597
620
|
|
|
598
621
|
let queued = 0;
|
|
@@ -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),
|