ei-tui 0.9.2 → 0.9.4

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.
Files changed (57) hide show
  1. package/package.json +17 -1
  2. package/src/README.md +1 -1
  3. package/src/cli/commands/personas.ts +11 -2
  4. package/src/cli/mcp.ts +2 -2
  5. package/src/cli/retrieval.ts +40 -7
  6. package/src/cli.ts +63 -72
  7. package/src/core/context-utils.ts +2 -2
  8. package/src/core/handlers/heartbeat.ts +9 -1
  9. package/src/core/handlers/human-extraction.ts +4 -1
  10. package/src/core/handlers/human-matching.ts +5 -53
  11. package/src/core/handlers/index.ts +1 -51
  12. package/src/core/handlers/persona-generation.ts +1 -28
  13. package/src/core/handlers/utils.ts +2 -9
  14. package/src/core/heartbeat-manager.ts +4 -4
  15. package/src/core/message-manager.ts +6 -5
  16. package/src/core/orchestrators/ceremony.ts +15 -13
  17. package/src/core/orchestrators/extraction-chunker.ts +3 -3
  18. package/src/core/orchestrators/human-extraction.ts +27 -39
  19. package/src/core/orchestrators/index.ts +0 -1
  20. package/src/core/orchestrators/persona-topics.ts +1 -1
  21. package/src/core/orchestrators/room-extraction.ts +45 -7
  22. package/src/core/processor.ts +8 -21
  23. package/src/core/prompt-context-builder.ts +68 -36
  24. package/src/core/state/personas.ts +1 -17
  25. package/src/core/state-manager.ts +0 -66
  26. package/src/core/types/entities.ts +2 -3
  27. package/src/core/types/enums.ts +0 -2
  28. package/src/core/types/rooms.ts +1 -1
  29. package/src/integrations/claude-code/importer.ts +1 -1
  30. package/src/integrations/cursor/importer.ts +1 -1
  31. package/src/integrations/opencode/importer.ts +1 -1
  32. package/src/prompts/ceremony/index.ts +0 -10
  33. package/src/prompts/ceremony/types.ts +1 -42
  34. package/src/prompts/generation/index.ts +0 -3
  35. package/src/prompts/generation/types.ts +0 -15
  36. package/src/prompts/heartbeat/check.ts +18 -6
  37. package/src/prompts/heartbeat/types.ts +2 -1
  38. package/src/prompts/human/index.ts +0 -2
  39. package/src/prompts/human/person-update.ts +6 -23
  40. package/src/prompts/human/types.ts +0 -16
  41. package/src/prompts/index.ts +0 -19
  42. package/src/prompts/reflection/index.ts +36 -4
  43. package/src/prompts/reflection/types.ts +1 -1
  44. package/src/prompts/response/index.ts +5 -0
  45. package/src/prompts/response/sections.ts +26 -0
  46. package/src/prompts/response/types.ts +3 -0
  47. package/tui/src/commands/registry.test.ts +10 -5
  48. package/tui/src/globals.d.ts +57 -0
  49. package/tui/src/util/yaml-persona.ts +8 -4
  50. package/tui/src/util/yaml-settings.ts +3 -3
  51. package/src/core/orchestrators/person-migration.ts +0 -55
  52. package/src/prompts/ceremony/description-check.ts +0 -54
  53. package/src/prompts/ceremony/expire.ts +0 -37
  54. package/src/prompts/ceremony/explore.ts +0 -77
  55. package/src/prompts/ceremony/person-migration.ts +0 -77
  56. package/src/prompts/generation/descriptions.ts +0 -91
  57. package/src/prompts/human/fact-scan.ts +0 -150
@@ -223,7 +223,7 @@ export function checkAndQueueHumanExtraction(
223
223
  if (unextractedFacts.length > 0 && unextractedFacts.length >= factsThreshold) {
224
224
  const context: ExtractionContext = {
225
225
  personaId,
226
- personaDisplayName,
226
+ channelDisplayName: personaDisplayName,
227
227
  messages_context: history.filter((m) => m.f === true),
228
228
  messages_analyze: unextractedFacts,
229
229
  extraction_flag: "f",
@@ -239,7 +239,7 @@ export function checkAndQueueHumanExtraction(
239
239
  if (unextractedTopics.length > 0 && unextractedTopics.length >= topicsThreshold) {
240
240
  const context: ExtractionContext = {
241
241
  personaId,
242
- personaDisplayName,
242
+ channelDisplayName: personaDisplayName,
243
243
  messages_context: history.filter((m) => m.t === true),
244
244
  messages_analyze: unextractedTopics,
245
245
  extraction_flag: "t",
@@ -257,7 +257,7 @@ export function checkAndQueueHumanExtraction(
257
257
  const personScanOptions = personaForScan?.pending_update ? { reflection_progress: 1 } : undefined;
258
258
  const context: ExtractionContext = {
259
259
  personaId,
260
- personaDisplayName,
260
+ channelDisplayName: personaDisplayName,
261
261
  messages_context: history.filter((m) => m.p === true),
262
262
  messages_analyze: unextractedPeople,
263
263
  extraction_flag: "p",
@@ -282,8 +282,9 @@ export function fetchMessagesForLLM(
282
282
 
283
283
  const human = sm.getHuman();
284
284
  const history = sm.messages_get(personaId);
285
- const contextWindowHours = persona.context_window_hours ?? human.settings?.default_context_window_hours ?? 8;
286
- const filteredHistory = filterMessagesForContext(history, persona.context_boundary, contextWindowHours);
285
+ const contextWindowMs = persona.context_window_ms ?? human.settings?.default_context_window_ms ?? 28800000;
286
+ const MAX_RESPONSE_MESSAGES = 50;
287
+ const filteredHistory = filterMessagesForContext(history, persona.context_boundary, contextWindowMs).slice(-MAX_RESPONSE_MESSAGES);
287
288
 
288
289
  const humanName = human.settings?.name_display
289
290
  || human.facts?.find(f => f.name === "Nickname/Preferred Name")?.description
@@ -11,7 +11,7 @@ import {
11
11
  type ExtractionOptions,
12
12
  } from "./human-extraction.js";
13
13
  import { queuePersonaTopicRating, type PersonaTopicContext, type PersonaTopicOptions } from "./persona-topics.js";
14
- import { queuePersonMigration } from "./person-migration.js";
14
+ import { getRoomVisibleMessages, queueRoomHumanExtraction } from "./room-extraction.js";
15
15
  import { buildRewriteScanPrompt, type RewriteItemType } from "../../prompts/ceremony/index.js";
16
16
  import { buildReflectionCriticPrompt } from "../../prompts/reflection/index.js";
17
17
  import { getModelForPersona } from "../heartbeat-manager.js";
@@ -75,10 +75,6 @@ export function startCeremony(state: StateManager): void {
75
75
  },
76
76
  });
77
77
 
78
- // PHASE 1: Person Migration
79
- console.log("[ceremony] Starting Phase 1: Person Migration");
80
- queuePersonMigration(state);
81
-
82
78
  // Check if migration work was queued
83
79
  if (!state.queue_hasPendingCeremonies()) {
84
80
  // No migration work found → immediately advance to Expose phase
@@ -109,7 +105,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
109
105
  if (unextractedFacts.length > 0) {
110
106
  const context: ExtractionContext = {
111
107
  personaId,
112
- personaDisplayName: persona.display_name,
108
+ channelDisplayName: persona.display_name,
113
109
  messages_context: allMessages.filter(m => m.f === true),
114
110
  messages_analyze: unextractedFacts,
115
111
  extraction_flag: "f",
@@ -122,7 +118,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
122
118
  if (unextractedTopics.length > 0) {
123
119
  const context: ExtractionContext = {
124
120
  personaId,
125
- personaDisplayName: persona.display_name,
121
+ channelDisplayName: persona.display_name,
126
122
  messages_context: allMessages.filter(m => m.t === true),
127
123
  messages_analyze: unextractedTopics,
128
124
  extraction_flag: "t",
@@ -134,7 +130,7 @@ function queueExposurePhase(personaId: string, state: StateManager, options?: Ex
134
130
  if (unextractedPeople.length > 0) {
135
131
  const context: ExtractionContext = {
136
132
  personaId,
137
- personaDisplayName: persona.display_name,
133
+ channelDisplayName: persona.display_name,
138
134
  messages_context: allMessages.filter(m => m.p === true),
139
135
  messages_analyze: unextractedPeople,
140
136
  extraction_flag: "p",
@@ -209,20 +205,26 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
209
205
  const rooms = state.getRoomList();
210
206
  for (const room of rooms) {
211
207
  if (room.mode === RoomMode.ChooseYourPath) continue;
208
+
209
+ // Human extraction (t/p) — straggler scan for messages that never hit the
210
+ // per-send threshold in checkAndQueueRoomExtraction
211
+ queueRoomHumanExtraction(state, room.id, 2);
212
+
213
+ // Persona topic rating — uses getRoomVisibleMessages so FFA rooms get all
214
+ // messages, not just the active path chain
215
+ const allRoomMessages = getRoomVisibleMessages(state, room.id);
212
216
  for (const personaId of room.persona_ids) {
213
217
  const shortId = personaId.slice(0, 8);
214
218
  const unprocessedRaw = state.getRoomUnextractedMessagesForPersona(room.id, shortId);
215
219
  if (unprocessedRaw.length === 0) continue;
216
220
  const personaForRoom = state.persona_getById(personaId);
217
221
  if (!personaForRoom) continue;
218
- const allRoomMessagesRaw = state.getRoomActivePath(room.id);
219
- const processedIds = new Set(allRoomMessagesRaw.filter(m => !!m.persona_extracted?.[shortId]).map(m => m.id));
220
- const allNormalized = normalizeRoomMessages(allRoomMessagesRaw, state);
222
+ const processedIds = new Set(allRoomMessages.filter(m => !!m.persona_extracted?.[shortId]).map(m => m.id));
221
223
  const unprocessedNormalized = normalizeRoomMessages(unprocessedRaw, state);
222
224
  const personaTopicContext: PersonaTopicContext = {
223
225
  personaId,
224
226
  personaDisplayName: personaForRoom.display_name,
225
- messages_context: allNormalized.filter(m => processedIds.has(m.id)),
227
+ messages_context: allRoomMessages.filter(m => processedIds.has(m.id)),
226
228
  messages_analyze: unprocessedNormalized,
227
229
  topics: personaForRoom.topics,
228
230
  };
@@ -468,7 +470,7 @@ export function queueReflectionDrain(personaId: string, state: StateManager): vo
468
470
 
469
471
  const context: ExtractionContext = {
470
472
  personaId,
471
- personaDisplayName: persona.display_name,
473
+ channelDisplayName: persona.display_name,
472
474
  messages_context: allMessages.filter(m => m.p === true),
473
475
  messages_analyze: unextractedPeople,
474
476
  extraction_flag: "p",
@@ -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
  });
@@ -12,7 +12,6 @@ import {
12
12
  type TopicScanCandidate,
13
13
  type ItemMatchResult,
14
14
  type ParticipantContext,
15
- type PersonaEntitySnapshot,
16
15
  } from "../../prompts/human/index.js";
17
16
  import { buildValidatePrompt } from "../../prompts/ceremony/dedup.js";
18
17
  import { normalizeRoomMessages } from "../handlers/utils.js";
@@ -56,7 +55,7 @@ function buildParticipantContext(personaId: string, state: StateManager): Partic
56
55
 
57
56
  export interface ExtractionContext {
58
57
  personaId: string;
59
- personaDisplayName: string;
58
+ channelDisplayName: string;
60
59
  messages_context: Message[];
61
60
  messages_analyze: Message[];
62
61
  extraction_flag?: "f" | "t" | "p" | "e";
@@ -100,24 +99,26 @@ function getExtractionMaxTokens(state: StateManager, options?: ExtractionOptions
100
99
  export function queueFactFind(context: ExtractionContext, state: StateManager, options?: ExtractionOptions): number {
101
100
  const human = state.getHuman();
102
101
  const extractionModel = options?.extraction_model;
103
- const missing_fact_names = human.facts
104
- .filter(f => !f.description || f.description === "")
105
- .map(f => f.name)
106
- .filter(name => BUILT_IN_FACT_NAMES.has(name));
107
-
108
- if (missing_fact_names.length === 0) return 0;
109
102
 
110
103
  const { chunks } = chunkExtractionContext(context, getExtractionMaxTokens(state, options));
111
104
 
112
- // Pre-mark messages before enqueuing prevents duplicate scans if the
113
- // queue check fires again during LLM latency (100ms loop × 5s call = 50 dupes)
105
+ // Always pre-mark f even when all facts are already known.
106
+ // Once we stop scanning for facts, messages must still be marked so the
107
+ // ceremony doesn't treat them as perpetually unprocessed.
114
108
  for (const chunk of chunks) {
115
109
  state.messages_markExtracted(chunk.personaId, chunk.messages_analyze.map(m => m.id), "f");
116
110
  }
117
111
 
112
+ const missing_fact_names = human.facts
113
+ .filter(f => !f.description || f.description === "")
114
+ .map(f => f.name)
115
+ .filter(name => BUILT_IN_FACT_NAMES.has(name));
116
+
117
+ if (missing_fact_names.length === 0) return 0;
118
+
118
119
  for (const chunk of chunks) {
119
120
  const prompt = buildFactFindPrompt({
120
- persona_name: chunk.personaDisplayName,
121
+ persona_name: chunk.channelDisplayName,
121
122
  missing_fact_names,
122
123
  messages_context: chunk.messages_context,
123
124
  messages_analyze: chunk.messages_analyze,
@@ -133,7 +134,7 @@ export function queueFactFind(context: ExtractionContext, state: StateManager, o
133
134
  data: {
134
135
  ...options,
135
136
  personaId: chunk.personaId,
136
- personaDisplayName: chunk.personaDisplayName,
137
+ personaDisplayName: chunk.channelDisplayName,
137
138
  analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
138
139
  extraction_flag: context.extraction_flag,
139
140
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
@@ -159,7 +160,7 @@ export function queueTopicScan(context: ExtractionContext, state: StateManager,
159
160
 
160
161
  for (const chunk of chunks) {
161
162
  const prompt = buildHumanTopicScanPrompt({
162
- persona_name: chunk.personaDisplayName,
163
+ persona_name: chunk.channelDisplayName,
163
164
  messages_context: chunk.messages_context,
164
165
  messages_analyze: chunk.messages_analyze,
165
166
  participant_context: buildParticipantContext(context.personaId, state),
@@ -175,7 +176,7 @@ export function queueTopicScan(context: ExtractionContext, state: StateManager,
175
176
  data: {
176
177
  ...options,
177
178
  personaId: chunk.personaId,
178
- personaDisplayName: chunk.personaDisplayName,
179
+ personaDisplayName: chunk.channelDisplayName,
179
180
  analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
180
181
  extraction_flag: context.extraction_flag,
181
182
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
@@ -208,7 +209,7 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
208
209
 
209
210
  for (const chunk of chunks) {
210
211
  const prompt = buildHumanPersonScanPrompt({
211
- persona_name: chunk.personaDisplayName,
212
+ persona_name: chunk.channelDisplayName,
212
213
  messages_context: chunk.messages_context,
213
214
  messages_analyze: chunk.messages_analyze,
214
215
  participant_context: buildParticipantContext(context.personaId, state),
@@ -225,7 +226,7 @@ export function queuePersonScan(context: ExtractionContext, state: StateManager,
225
226
  data: {
226
227
  ...options,
227
228
  personaId: chunk.personaId,
228
- personaDisplayName: chunk.personaDisplayName,
229
+ personaDisplayName: chunk.channelDisplayName,
229
230
  analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
230
231
  extraction_flag: context.extraction_flag,
231
232
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
@@ -272,7 +273,7 @@ export function queueDirectTopicUpdate(
272
273
  existing_item: topic,
273
274
  messages_context: chunk.messages_context,
274
275
  messages_analyze: chunk.messages_analyze,
275
- persona_name: chunk.personaDisplayName,
276
+ persona_name: chunk.channelDisplayName,
276
277
  participant_context: buildParticipantContext(context.personaId, state),
277
278
  });
278
279
 
@@ -285,7 +286,7 @@ export function queueDirectTopicUpdate(
285
286
  next_step: LLMNextStep.HandleTopicUpdate,
286
287
  data: {
287
288
  personaId: context.personaId,
288
- personaDisplayName: context.personaDisplayName,
289
+ personaDisplayName: context.channelDisplayName,
289
290
  isNewItem: false,
290
291
  existingItemId: topic.id,
291
292
  analyze_from_timestamp: getAnalyzeFromTimestamp(chunk),
@@ -422,7 +423,7 @@ export function queueTopicUpdate(
422
423
  new_topic_category: isNewItem ? context.candidateCategory : undefined,
423
424
  messages_context: chunk.messages_context,
424
425
  messages_analyze: chunk.messages_analyze,
425
- persona_name: chunk.personaDisplayName,
426
+ persona_name: chunk.channelDisplayName,
426
427
  participant_context: buildParticipantContext(primaryPersonaId, state),
427
428
  });
428
429
 
@@ -501,7 +502,7 @@ export function queueEventSummary(
501
502
 
502
503
  const context: ExtractionContext = {
503
504
  personaId,
504
- personaDisplayName: persona.display_name,
505
+ channelDisplayName: persona.display_name,
505
506
  messages_context,
506
507
  messages_analyze: windowMessages,
507
508
  extraction_flag: "e",
@@ -511,7 +512,7 @@ export function queueEventSummary(
511
512
 
512
513
  for (const chunk of chunks) {
513
514
  const prompt = buildEventScanPrompt({
514
- persona_name: chunk.personaDisplayName,
515
+ persona_name: chunk.channelDisplayName,
515
516
  messages_context: chunk.messages_context,
516
517
  messages_analyze: chunk.messages_analyze,
517
518
  participant_context: buildParticipantContext(personaId, state),
@@ -527,7 +528,7 @@ export function queueEventSummary(
527
528
  data: {
528
529
  ...options,
529
530
  personaId: chunk.personaId,
530
- personaDisplayName: chunk.personaDisplayName,
531
+ personaDisplayName: chunk.channelDisplayName,
531
532
  extraction_flag: "e",
532
533
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
533
534
  },
@@ -586,18 +587,6 @@ export function queuePersonUpdate(
586
587
 
587
588
  const primaryPersonaIdForUpdate = context.personaId.split("|")[0];
588
589
 
589
- const linkedPersonaId = !isNewItem
590
- ? (existingItem?.identifiers ?? []).find(i => i.type.toLowerCase() === 'ei persona')?.value
591
- : undefined;
592
- const linkedPersonaEntity = linkedPersonaId ? state.persona_getById(linkedPersonaId) : undefined;
593
- const personaEntitySnapshot: PersonaEntitySnapshot | undefined = linkedPersonaEntity
594
- ? {
595
- long_description: linkedPersonaEntity.long_description ?? '',
596
- traits: (linkedPersonaEntity.traits ?? []).map(t => ({ name: t.name, description: t.description })),
597
- topics: (linkedPersonaEntity.topics ?? []).map(t => ({ name: t.name, perspective: t.perspective })),
598
- }
599
- : undefined;
600
-
601
590
  for (const chunk of chunks) {
602
591
  const prompt = buildPersonUpdatePrompt({
603
592
  existing_item: existingItem,
@@ -606,10 +595,9 @@ export function queuePersonUpdate(
606
595
  new_person_relationship: isNewItem ? context.candidateRelationship : undefined,
607
596
  messages_context: chunk.messages_context,
608
597
  messages_analyze: chunk.messages_analyze,
609
- persona_name: chunk.personaDisplayName,
598
+ persona_name: chunk.channelDisplayName,
610
599
  participant_context: buildParticipantContext(primaryPersonaIdForUpdate, state),
611
600
  known_identifier_types: userIdentifierTypes,
612
- persona_entity: personaEntitySnapshot,
613
601
  });
614
602
 
615
603
  state.queue_enqueue({
@@ -621,7 +609,7 @@ export function queuePersonUpdate(
621
609
  next_step: LLMNextStep.HandlePersonUpdate,
622
610
  data: {
623
611
  personaId: context.personaId,
624
- personaDisplayName: context.personaDisplayName,
612
+ personaDisplayName: context.channelDisplayName,
625
613
  roomId: context.roomId,
626
614
  isNewItem,
627
615
  existingItemId: existingItem?.id,
@@ -685,7 +673,7 @@ export function queueTargetedPersonUpdate(
685
673
  extraction_model?: string;
686
674
  } = {
687
675
  personaId: contextPersonaId,
688
- personaDisplayName: displayName,
676
+ channelDisplayName: displayName,
689
677
  messages_context: [],
690
678
  messages_analyze: allMessages,
691
679
  candidateName: existingItem.name,
@@ -740,7 +728,7 @@ export function queueTargetedTopicUpdate(
740
728
  const model = state.getHuman().settings?.default_model;
741
729
  const context: ExtractionContext = {
742
730
  personaId: contextPersonaId,
743
- personaDisplayName: displayName,
731
+ channelDisplayName: displayName,
744
732
  messages_context: [],
745
733
  messages_analyze: allMessages,
746
734
  roomId,
@@ -24,7 +24,6 @@ export {
24
24
  runHumanCeremony,
25
25
  } from "./ceremony.js";
26
26
  export { queueUserDedupRequest } from "./dedup-phase.js";
27
- export { queuePersonMigration } from "./person-migration.js";
28
27
  export {
29
28
  queuePersonaTopicRating,
30
29
  type PersonaTopicContext,
@@ -38,7 +38,7 @@ export function queuePersonaTopicRating(
38
38
  const { chunks } = chunkExtractionContext(
39
39
  {
40
40
  personaId: context.personaId,
41
- personaDisplayName: context.personaDisplayName,
41
+ channelDisplayName: context.personaDisplayName,
42
42
  messages_context: context.messages_context,
43
43
  messages_analyze: context.messages_analyze,
44
44
  },
@@ -73,11 +73,12 @@ function queueRoomTopicScan(
73
73
  messages_context: Message[],
74
74
  messages_analyze: Message[],
75
75
  state: StateManager,
76
- participantContext: ParticipantContext
76
+ participantContext: ParticipantContext,
77
+ ceremony_progress?: number
77
78
  ): void {
78
79
  const context: HumanExtractionContext = {
79
80
  personaId: roomId,
80
- personaDisplayName: roomDisplayName,
81
+ channelDisplayName: roomDisplayName,
81
82
  messages_context,
82
83
  messages_analyze,
83
84
  extraction_flag: "t",
@@ -105,6 +106,7 @@ function queueRoomTopicScan(
105
106
  personaId: (state.getRoom(roomId)?.persona_ids ?? []).join("|"),
106
107
  personaDisplayName: roomDisplayName,
107
108
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
109
+ ...(ceremony_progress !== undefined ? { ceremony_progress } : {}),
108
110
  },
109
111
  });
110
112
  }
@@ -115,11 +117,12 @@ function queueRoomPersonScan(
115
117
  roomDisplayName: string,
116
118
  messages_context: Message[],
117
119
  messages_analyze: Message[],
118
- state: StateManager
120
+ state: StateManager,
121
+ ceremony_progress?: number
119
122
  ): void {
120
123
  const context: HumanExtractionContext = {
121
124
  personaId: roomId,
122
- personaDisplayName: roomDisplayName,
125
+ channelDisplayName: roomDisplayName,
123
126
  messages_context,
124
127
  messages_analyze,
125
128
  extraction_flag: "p",
@@ -146,6 +149,7 @@ function queueRoomPersonScan(
146
149
  personaId: (state.getRoom(roomId)?.persona_ids ?? []).join("|"),
147
150
  personaDisplayName: roomDisplayName,
148
151
  message_ids_to_mark: chunk.messages_analyze.map(m => m.id),
152
+ ...(ceremony_progress !== undefined ? { ceremony_progress } : {}),
149
153
  },
150
154
  });
151
155
  }
@@ -176,7 +180,7 @@ function queueRoomEventScan(
176
180
  );
177
181
  const context: HumanExtractionContext = {
178
182
  personaId: roomId,
179
- personaDisplayName: roomDisplayName,
183
+ channelDisplayName: roomDisplayName,
180
184
  messages_context,
181
185
  messages_analyze: windowMessages,
182
186
  extraction_flag: "e",
@@ -241,6 +245,40 @@ export function checkAndQueueRoomExtraction(state: StateManager, roomId: string)
241
245
  console.log(`[checkAndQueueRoomExtraction] Auto-triggered extraction for room ${roomDisplayName} (threshold: ${threshold})`);
242
246
  }
243
247
 
248
+ export function queueRoomHumanExtraction(state: StateManager, roomId: string, ceremony_progress?: number): void {
249
+ const room = state.getRoom(roomId);
250
+ if (!room || room.mode === RoomMode.ChooseYourPath) return;
251
+
252
+ const allVisible = getRoomVisibleMessages(state, roomId);
253
+ if (allVisible.length === 0) return;
254
+
255
+ const participantContext = buildRoomParticipantContext(roomId, state);
256
+ const roomDisplayName = room.display_name;
257
+
258
+ const unextractedT = allVisible.filter(m => !m.t);
259
+ const unextractedP = allVisible.filter(m => !m.p);
260
+
261
+ if (unextractedT.length === 0 && unextractedP.length === 0) return;
262
+
263
+ if (unextractedT.length > 0) {
264
+ const analyzeStart = unextractedT[0].timestamp;
265
+ const messages_contextT = allVisible.filter(
266
+ m => m.t === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
267
+ );
268
+ queueRoomTopicScan(roomId, roomDisplayName, messages_contextT, unextractedT, state, participantContext, ceremony_progress);
269
+ }
270
+
271
+ if (unextractedP.length > 0) {
272
+ const analyzeStart = unextractedP[0].timestamp;
273
+ const messages_contextP = allVisible.filter(
274
+ m => m.p === true && new Date(m.timestamp).getTime() < new Date(analyzeStart).getTime()
275
+ );
276
+ queueRoomPersonScan(roomId, roomDisplayName, messages_contextP, unextractedP, state, ceremony_progress);
277
+ }
278
+
279
+ console.log(`[queueRoomHumanExtraction] Queued human extraction for room "${roomDisplayName}" (t:${unextractedT.length}, p:${unextractedP.length})`);
280
+ }
281
+
244
282
  export function queueRoomCapture(state: StateManager, roomId: string): void {
245
283
  const room = state.getRoom(roomId);
246
284
  if (!room) return;
@@ -310,7 +348,7 @@ export function queuePersonaCapture(state: StateManager, personaId: string): voi
310
348
  );
311
349
  const context: HumanExtractionContext = {
312
350
  personaId,
313
- personaDisplayName: persona.display_name,
351
+ channelDisplayName: persona.display_name,
314
352
  messages_context,
315
353
  messages_analyze: unextractedT,
316
354
  };
@@ -324,7 +362,7 @@ export function queuePersonaCapture(state: StateManager, personaId: string): voi
324
362
  );
325
363
  const context: HumanExtractionContext = {
326
364
  personaId,
327
- personaDisplayName: persona.display_name,
365
+ channelDisplayName: persona.display_name,
328
366
  messages_context,
329
367
  messages_analyze: unextractedP,
330
368
  };
@@ -877,8 +877,8 @@ export class Processor {
877
877
  modified = true;
878
878
  }
879
879
 
880
- if (human.settings.default_context_window_hours == null) {
881
- human.settings.default_context_window_hours = 8;
880
+ if (human.settings.default_context_window_ms == null) {
881
+ human.settings.default_context_window_ms = 28800000;
882
882
  modified = true;
883
883
  }
884
884
 
@@ -1044,7 +1044,6 @@ const toolNextSteps = new Set([
1044
1044
  LLMNextStep.HandleEiHeartbeat,
1045
1045
  LLMNextStep.HandleToolContinuation,
1046
1046
  LLMNextStep.HandleDedupCurate,
1047
- LLMNextStep.HandlePersonIdentifierMigration,
1048
1047
  ]);
1049
1048
  const toolPersonaId =
1050
1049
  personaId ??
@@ -1059,13 +1058,8 @@ const toolNextSteps = new Set([
1059
1058
  (request.next_step === LLMNextStep.HandleToolContinuation &&
1060
1059
  request.data.originalNextStep === LLMNextStep.HandleDedupCurate);
1061
1060
 
1062
- const isPersonMigrationRequest =
1063
- request.next_step === LLMNextStep.HandlePersonIdentifierMigration ||
1064
- (request.next_step === LLMNextStep.HandleToolContinuation &&
1065
- request.data.originalNextStep === LLMNextStep.HandlePersonIdentifierMigration);
1066
-
1067
1061
  let tools: ToolDefinition[] = [];
1068
- if (isDedupRequest || isPersonMigrationRequest) {
1062
+ if (isDedupRequest) {
1069
1063
  const readMemory = this.stateManager.tools_getByName("read_memory");
1070
1064
  if (readMemory?.enabled) {
1071
1065
  tools = [readMemory];
@@ -1201,14 +1195,14 @@ const toolNextSteps = new Set([
1201
1195
 
1202
1196
  if (timeSinceHeartbeat >= heartbeatDelay) {
1203
1197
  const history = this.stateManager.messages_get(persona.id);
1204
- const contextWindowHours =
1205
- persona.context_window_hours
1206
- ?? this.stateManager.getHuman().settings?.default_context_window_hours
1207
- ?? 8;
1198
+ const contextWindowMs =
1199
+ persona.context_window_ms
1200
+ ?? this.stateManager.getHuman().settings?.default_context_window_ms
1201
+ ?? 28800000;
1208
1202
  const contextHistory = filterMessagesForContext(
1209
1203
  history,
1210
1204
  persona.context_boundary,
1211
- contextWindowHours
1205
+ contextWindowMs
1212
1206
  );
1213
1207
  const trailing = countTrailingPersonaMessages(contextHistory);
1214
1208
  if (trailing < 3) {
@@ -1621,13 +1615,6 @@ const toolNextSteps = new Set([
1621
1615
  }
1622
1616
  }
1623
1617
 
1624
- if (response.request.next_step === LLMNextStep.HandlePersonaDescriptions) {
1625
- const personaId = response.request.data.personaId as string;
1626
- if (personaId) {
1627
- this.interface.onPersonaUpdated?.(personaId);
1628
- }
1629
- }
1630
-
1631
1618
  if (
1632
1619
  response.request.next_step === LLMNextStep.HandlePersonaTraitExtraction ||
1633
1620
  response.request.next_step === LLMNextStep.HandlePersonaTopicRating