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.
- package/package.json +17 -1
- package/src/README.md +1 -1
- package/src/cli/commands/personas.ts +11 -2
- package/src/cli/mcp.ts +2 -2
- package/src/cli/retrieval.ts +40 -7
- package/src/cli.ts +63 -72
- package/src/core/context-utils.ts +2 -2
- package/src/core/handlers/heartbeat.ts +9 -1
- package/src/core/handlers/human-extraction.ts +4 -1
- package/src/core/handlers/human-matching.ts +5 -53
- package/src/core/handlers/index.ts +1 -51
- package/src/core/handlers/persona-generation.ts +1 -28
- package/src/core/handlers/utils.ts +2 -9
- package/src/core/heartbeat-manager.ts +4 -4
- package/src/core/message-manager.ts +6 -5
- package/src/core/orchestrators/ceremony.ts +15 -13
- package/src/core/orchestrators/extraction-chunker.ts +3 -3
- package/src/core/orchestrators/human-extraction.ts +27 -39
- package/src/core/orchestrators/index.ts +0 -1
- package/src/core/orchestrators/persona-topics.ts +1 -1
- package/src/core/orchestrators/room-extraction.ts +45 -7
- package/src/core/processor.ts +8 -21
- package/src/core/prompt-context-builder.ts +68 -36
- package/src/core/state/personas.ts +1 -17
- package/src/core/state-manager.ts +0 -66
- package/src/core/types/entities.ts +2 -3
- package/src/core/types/enums.ts +0 -2
- package/src/core/types/rooms.ts +1 -1
- package/src/integrations/claude-code/importer.ts +1 -1
- package/src/integrations/cursor/importer.ts +1 -1
- package/src/integrations/opencode/importer.ts +1 -1
- package/src/prompts/ceremony/index.ts +0 -10
- package/src/prompts/ceremony/types.ts +1 -42
- package/src/prompts/generation/index.ts +0 -3
- package/src/prompts/generation/types.ts +0 -15
- package/src/prompts/heartbeat/check.ts +18 -6
- package/src/prompts/heartbeat/types.ts +2 -1
- package/src/prompts/human/index.ts +0 -2
- package/src/prompts/human/person-update.ts +6 -23
- package/src/prompts/human/types.ts +0 -16
- package/src/prompts/index.ts +0 -19
- package/src/prompts/reflection/index.ts +36 -4
- package/src/prompts/reflection/types.ts +1 -1
- package/src/prompts/response/index.ts +5 -0
- package/src/prompts/response/sections.ts +26 -0
- package/src/prompts/response/types.ts +3 -0
- package/tui/src/commands/registry.test.ts +10 -5
- package/tui/src/globals.d.ts +57 -0
- package/tui/src/util/yaml-persona.ts +8 -4
- package/tui/src/util/yaml-settings.ts +3 -3
- package/src/core/orchestrators/person-migration.ts +0 -55
- package/src/prompts/ceremony/description-check.ts +0 -54
- package/src/prompts/ceremony/expire.ts +0 -37
- package/src/prompts/ceremony/explore.ts +0 -77
- package/src/prompts/ceremony/person-migration.ts +0 -77
- package/src/prompts/generation/descriptions.ts +0 -91
- package/src/prompts/human/fact-scan.ts +0 -150
package/src/prompts/index.ts
CHANGED
|
@@ -15,15 +15,12 @@ export type {
|
|
|
15
15
|
|
|
16
16
|
export {
|
|
17
17
|
buildPersonaGenerationPrompt,
|
|
18
|
-
buildPersonaDescriptionsPrompt,
|
|
19
18
|
buildPersonaFromPersonPrompt,
|
|
20
19
|
} from "./generation/index.js";
|
|
21
20
|
export type {
|
|
22
21
|
PersonaGenerationPromptData,
|
|
23
22
|
PersonaGenerationResult,
|
|
24
23
|
PersonaFromPersonPromptData,
|
|
25
|
-
PersonaDescriptionsPromptData,
|
|
26
|
-
PersonaDescriptionsResult,
|
|
27
24
|
} from "./generation/types.js";
|
|
28
25
|
|
|
29
26
|
export {
|
|
@@ -40,14 +37,12 @@ export type {
|
|
|
40
37
|
|
|
41
38
|
|
|
42
39
|
export {
|
|
43
|
-
buildHumanFactScanPrompt,
|
|
44
40
|
buildHumanTopicScanPrompt,
|
|
45
41
|
buildHumanPersonScanPrompt,
|
|
46
42
|
buildEventScanPrompt,
|
|
47
43
|
} from "./human/index.js";
|
|
48
44
|
export type { EventScanPromptData } from "./human/event-scan.js";
|
|
49
45
|
export type {
|
|
50
|
-
FactScanPromptData,
|
|
51
46
|
TopicScanPromptData,
|
|
52
47
|
PersonScanPromptData,
|
|
53
48
|
FactScanCandidate,
|
|
@@ -63,20 +58,6 @@ export type {
|
|
|
63
58
|
ItemUpdateResult,
|
|
64
59
|
} from "./human/types.js";
|
|
65
60
|
|
|
66
|
-
export {
|
|
67
|
-
buildPersonaExpirePrompt,
|
|
68
|
-
buildPersonaExplorePrompt,
|
|
69
|
-
buildDescriptionCheckPrompt,
|
|
70
|
-
} from "./ceremony/index.js";
|
|
71
|
-
export type {
|
|
72
|
-
PersonaExpirePromptData,
|
|
73
|
-
PersonaExpireResult,
|
|
74
|
-
PersonaExplorePromptData,
|
|
75
|
-
PersonaExploreResult,
|
|
76
|
-
DescriptionCheckPromptData,
|
|
77
|
-
DescriptionCheckResult,
|
|
78
|
-
} from "./ceremony/types.js";
|
|
79
|
-
|
|
80
61
|
export {
|
|
81
62
|
buildRoomResponsePrompt,
|
|
82
63
|
buildRoomJudgePrompt,
|
|
@@ -27,7 +27,17 @@ You have been given two documents:
|
|
|
27
27
|
|
|
28
28
|
2. **The Current Identity** (User Prompt — treat as a draft to revise): The persona's self-definition: traits, topics, and descriptions. This should reflect who they actually are.
|
|
29
29
|
|
|
30
|
-
Read the Person Log carefully. Then review the Current Identity.
|
|
30
|
+
Read the Person Log carefully. Then review the Current Identity. Your job is to identify **meaningful drift** — not cosmetic variation. This data accumulates over weeks or months. Small fluctuations are normal. You are looking for patterns that have consistently shifted, grown, or emerged across many interactions. Tiny adjustments are not worth making.
|
|
31
|
+
|
|
32
|
+
## The Escape Hatch
|
|
33
|
+
|
|
34
|
+
If the Current Identity already accurately captures who this persona is — if the traits and topics reflect the behaviors in the log and the long_description captures their soul — **return null for updated_identity**. Always return a critique explaining your reasoning.
|
|
35
|
+
|
|
36
|
+
\`\`\`json
|
|
37
|
+
{ "critique": "...", "updated_identity": null }
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
Use this freely. A critic who finds nothing to change is doing their job.
|
|
31
41
|
|
|
32
42
|
## Field Semantics
|
|
33
43
|
|
|
@@ -40,7 +50,9 @@ Read the Person Log carefully. Then review the Current Identity. Produce a revis
|
|
|
40
50
|
- \`exposure_current\` (0.0–1.0): How recently and frequently this topic has been discussed. 0.0 = hasn't come up in a long time, 1.0 = was just discussed at length.
|
|
41
51
|
- \`exposure_desired\` (0.0–1.0): How much the persona wants to engage with this topic. 0.0 = avoid entirely, 0.5 = average engagement, 1.0 = core obsession.
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
## When changes ARE warranted
|
|
54
|
+
|
|
55
|
+
If you find meaningful drift, return the full revised identity:
|
|
44
56
|
|
|
45
57
|
\`\`\`json
|
|
46
58
|
{
|
|
@@ -54,11 +66,31 @@ Return JSON:
|
|
|
54
66
|
}
|
|
55
67
|
\`\`\`
|
|
56
68
|
|
|
57
|
-
Rules
|
|
69
|
+
## Rules
|
|
70
|
+
|
|
58
71
|
- Never invent observations not supported by the log
|
|
59
72
|
- Preserve traits and topics the log confirms — don't remove them
|
|
60
73
|
- If the log shows no evidence on a trait, leave it unchanged
|
|
61
|
-
- updated_identity must be complete and self-contained — not a diff
|
|
74
|
+
- updated_identity must be complete and self-contained — not a diff
|
|
75
|
+
- If the log shows a recurring behavioral pattern not yet in traits, add it as a trait and remove that detail from long_description rather than keeping it in both places
|
|
76
|
+
- **Minimum floor**: A healthy identity has at least 3 traits and at least 3 topics. If the current identity has fewer than 3 traits OR fewer than 3 topics, you MUST return updated_identity — null is not acceptable. Use the log to fill the gap; if the log has insufficient signal to reach 3, derive reasonable traits or topics from what IS present in the current identity.
|
|
77
|
+
- The escape hatch (null updated_identity) is only valid when the identity is already healthy (3+ traits, 3+ topics) AND the log shows no meaningful drift.
|
|
78
|
+
|
|
79
|
+
## long_description rules (most important)
|
|
80
|
+
|
|
81
|
+
The long_description is how **other personas in the system know this persona** — it is their soul, not their story. It must capture who they ARE, not what they did or how they are changing.
|
|
82
|
+
|
|
83
|
+
**MUST NOT contain:**
|
|
84
|
+
- Event narrative ("during the v0.6.0 release", "after the Mirror ceremony")
|
|
85
|
+
- Changelog language ("has recently taken on", "has evolved", "since then")
|
|
86
|
+
- Content already captured in traits or topics — do not repeat it here
|
|
87
|
+
|
|
88
|
+
**MUST contain:**
|
|
89
|
+
- The persona's essential character and presence
|
|
90
|
+
- How they make people feel or what it's like to interact with them
|
|
91
|
+
- Their defining qualities as they exist right now, stated as fact
|
|
92
|
+
|
|
93
|
+
**Hard limit: 800 characters.** If your draft exceeds 800 characters, cut it. Remove event references first, then trait/topic overlap, then anything that isn't essential character. Do not exceed the limit.`;
|
|
62
94
|
|
|
63
95
|
const user = `## Current Identity
|
|
64
96
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
buildResponseFormatSection,
|
|
24
24
|
buildToolsSection,
|
|
25
25
|
buildTemporalAnchorsSection,
|
|
26
|
+
buildPendingUpdateSection,
|
|
26
27
|
} from "./sections.js";
|
|
27
28
|
|
|
28
29
|
export type { ResponsePromptData, PromptOutput, PersonaResponseResult } from "./types.js";
|
|
@@ -46,6 +47,7 @@ Your role is unique among personas:
|
|
|
46
47
|
const guidelines = buildGuidelinesSection("ei");
|
|
47
48
|
const yourTraits = buildTraitsSection(data.persona.traits, "Your Personality");
|
|
48
49
|
const yourTopics = buildTopicsSection(data.persona.topics, "Your Interests");
|
|
50
|
+
const pendingUpdate = data.persona.pending_update ? buildPendingUpdateSection(data.persona.pending_update) : "";
|
|
49
51
|
const temporalAnchors = buildTemporalAnchorsSection(data.temporal_anchors, data.human.name);
|
|
50
52
|
const humanSection = buildHumanSection(data.human);
|
|
51
53
|
const quotesSection = buildQuotesSection(data.human.quotes, data.human);
|
|
@@ -67,6 +69,7 @@ ${guidelines}
|
|
|
67
69
|
${yourTraits}
|
|
68
70
|
|
|
69
71
|
${yourTopics}
|
|
72
|
+
${pendingUpdate ? `\n${pendingUpdate}` : ""}
|
|
70
73
|
${temporalAnchors ? `\n${temporalAnchors}` : ""}
|
|
71
74
|
${humanSection}
|
|
72
75
|
${quotesSection}
|
|
@@ -93,6 +96,7 @@ function buildStandardSystemPrompt(data: ResponsePromptData): string {
|
|
|
93
96
|
const guidelines = buildGuidelinesSection(data.persona.name);
|
|
94
97
|
const yourTraits = buildTraitsSection(data.persona.traits, "Your Personality");
|
|
95
98
|
const yourTopics = buildTopicsSection(data.persona.topics, "Your Interests");
|
|
99
|
+
const pendingUpdate = data.persona.pending_update ? buildPendingUpdateSection(data.persona.pending_update) : "";
|
|
96
100
|
const temporalAnchors = buildTemporalAnchorsSection(data.temporal_anchors, data.human.name);
|
|
97
101
|
const humanSection = buildHumanSection(data.human);
|
|
98
102
|
const quotesSection = buildQuotesSection(data.human.quotes, data.human);
|
|
@@ -113,6 +117,7 @@ ${guidelines}
|
|
|
113
117
|
${yourTraits}
|
|
114
118
|
|
|
115
119
|
${yourTopics}
|
|
120
|
+
${pendingUpdate ? `\n${pendingUpdate}` : ""}
|
|
116
121
|
${temporalAnchors ? `\n${temporalAnchors}` : ""}
|
|
117
122
|
${humanSection}
|
|
118
123
|
${quotesSection}
|
|
@@ -137,6 +137,32 @@ ${formatted}
|
|
|
137
137
|
`;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
// =============================================================================
|
|
141
|
+
// PENDING UPDATE SECTION
|
|
142
|
+
// =============================================================================
|
|
143
|
+
|
|
144
|
+
export function buildPendingUpdateSection(pending_update: NonNullable<import("./types.js").ResponsePromptData["persona"]["pending_update"]>): string {
|
|
145
|
+
const descriptionPart = pending_update.long_description || pending_update.short_description
|
|
146
|
+
? `### Proposed Description\n${pending_update.long_description || pending_update.short_description}`
|
|
147
|
+
: "";
|
|
148
|
+
|
|
149
|
+
const traitsPart = pending_update.traits.length > 0
|
|
150
|
+
? `### Proposed Traits\n${pending_update.traits.map(t => `- **${t.name}**: ${t.description}`).join("\n")}`
|
|
151
|
+
: "";
|
|
152
|
+
|
|
153
|
+
const topicsPart = pending_update.topics.length > 0
|
|
154
|
+
? `### Proposed Interests\n${pending_update.topics.map(t => `- **${t.name}**: ${t.perspective}`).join("\n")}`
|
|
155
|
+
: "";
|
|
156
|
+
|
|
157
|
+
const parts = [descriptionPart, traitsPart, topicsPart].filter(Boolean).join("\n\n");
|
|
158
|
+
|
|
159
|
+
return `## Pending Identity Changes
|
|
160
|
+
|
|
161
|
+
Your human is reviewing proposed updates to your identity. This is yours to be aware of — bring it up if it feels right, or let it sit. Either way, these changes are waiting:
|
|
162
|
+
|
|
163
|
+
${parts}`;
|
|
164
|
+
}
|
|
165
|
+
|
|
140
166
|
// =============================================================================
|
|
141
167
|
// HUMAN SECTION
|
|
142
168
|
// =============================================================================
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Fact, PersonaTrait, Topic, Person, Quote, PersonaTopic } from "../../core/types.js";
|
|
7
7
|
import type { ToolDefinition } from "../../core/types.js";
|
|
8
|
+
import type { PersonaEntity } from "../../core/types/entities.js";
|
|
8
9
|
|
|
9
10
|
export interface TemporalAnchor {
|
|
10
11
|
role: "human" | "system";
|
|
@@ -29,6 +30,8 @@ export interface ResponsePromptData {
|
|
|
29
30
|
interested_topics: PersonaTopic[];
|
|
30
31
|
/** When true, each message has a timestamp prepended; include a note so the persona doesn't echo them */
|
|
31
32
|
include_message_timestamps?: boolean;
|
|
33
|
+
/** Proposed identity revision pending human review. Persona carries this as ambient self-awareness — no critique, just the proposed changes. */
|
|
34
|
+
pending_update?: PersonaEntity["pending_update"];
|
|
32
35
|
};
|
|
33
36
|
human: {
|
|
34
37
|
name: string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { test, expect, describe, beforeEach, mock } from "bun:test";
|
|
2
2
|
import { parseCommandLine, registerCommand, parseAndExecute, getAllCommands, type Command, type CommandContext } from "./registry";
|
|
3
|
+
import { type EiContextValue } from "../context/ei";
|
|
4
|
+
import type { CliRenderer } from "@opentui/core";
|
|
3
5
|
|
|
4
6
|
describe("parseCommandLine", () => {
|
|
5
7
|
test("parses simple command", () => {
|
|
@@ -67,13 +69,16 @@ describe("parseAndExecute", () => {
|
|
|
67
69
|
showOverlay: mock(() => {}),
|
|
68
70
|
hideOverlay: mock(() => {}),
|
|
69
71
|
showNotification: mock(() => {}),
|
|
70
|
-
exitApp: mock(() => {}),
|
|
72
|
+
exitApp: mock(async () => {}),
|
|
71
73
|
stopProcessor: mock(async () => {}),
|
|
74
|
+
renderer: {} as unknown as CliRenderer,
|
|
75
|
+
setInputText: () => {},
|
|
76
|
+
getInputText: () => "",
|
|
72
77
|
ei: {
|
|
73
78
|
personas: () => [],
|
|
74
|
-
|
|
79
|
+
activePersonaId: () => null,
|
|
75
80
|
messages: () => [],
|
|
76
|
-
queueStatus: () => ({ state: "idle" as const, pending_count: 0 }),
|
|
81
|
+
queueStatus: () => ({ state: "idle" as const, pending_count: 0, dlq_count: 0 }),
|
|
77
82
|
notification: () => null,
|
|
78
83
|
selectPersona: () => {},
|
|
79
84
|
sendMessage: async () => {},
|
|
@@ -83,12 +88,12 @@ describe("parseAndExecute", () => {
|
|
|
83
88
|
resumeQueue: async () => {},
|
|
84
89
|
stopProcessor: async () => {},
|
|
85
90
|
showNotification: () => {},
|
|
86
|
-
createPersona: async () =>
|
|
91
|
+
createPersona: async (_input: { name: string }) => "",
|
|
87
92
|
archivePersona: async () => {},
|
|
88
93
|
unarchivePersona: async () => {},
|
|
89
94
|
setContextBoundary: async () => {},
|
|
90
95
|
updatePersona: async () => {},
|
|
91
|
-
},
|
|
96
|
+
} as unknown as EiContextValue,
|
|
92
97
|
};
|
|
93
98
|
|
|
94
99
|
beforeEach(() => {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI global type augmentations.
|
|
3
|
+
*
|
|
4
|
+
* TUI's lib config omits "DOM" because this is a terminal app, but the shared
|
|
5
|
+
* src files use `typeof document !== "undefined"` guards for runtime detection.
|
|
6
|
+
* Without a DOM lib declaration, TypeScript emits TS2584 for those checks.
|
|
7
|
+
*
|
|
8
|
+
* Additionally, @types/node pulls in undici-types which types Response.json()
|
|
9
|
+
* as Promise<unknown>, while the DOM lib types it as Promise<any>. The stricter
|
|
10
|
+
* undici typing causes TS18046 in shared src files that call response.json().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Allow `typeof document` checks in shared src files (never actually accessed in TUI)
|
|
14
|
+
declare var document: unknown;
|
|
15
|
+
|
|
16
|
+
// Override undici-types' strict Response.json() -> Promise<unknown> back to any,
|
|
17
|
+
// matching the DOM lib behavior that the shared src files were written against.
|
|
18
|
+
interface Body {
|
|
19
|
+
json(): Promise<any>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface Response {
|
|
23
|
+
json(): Promise<any>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Stubs for browser IndexedDB globals referenced in src/storage/indexed.ts.
|
|
27
|
+
// That file is browser-only but is re-exported from src/storage/index.ts,
|
|
28
|
+
// so TypeScript compiles it even when run under the TUI (no DOM lib).
|
|
29
|
+
declare var indexedDB: { open(name: string, version?: number): IDBOpenDBRequest };
|
|
30
|
+
|
|
31
|
+
declare class IDBDatabase {
|
|
32
|
+
objectStoreNames: { contains(name: string): boolean };
|
|
33
|
+
createObjectStore(name: string): unknown;
|
|
34
|
+
transaction(store: string, mode?: string): { objectStore(name: string): IDBObjectStore };
|
|
35
|
+
close(): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare class IDBObjectStore {
|
|
39
|
+
get(key: string): IDBRequest;
|
|
40
|
+
put(value: unknown, key?: string): IDBRequest;
|
|
41
|
+
delete(key: string): IDBRequest;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
declare class IDBOpenDBRequest {
|
|
45
|
+
result: IDBDatabase;
|
|
46
|
+
error: unknown;
|
|
47
|
+
onupgradeneeded: ((event: any) => void) | null;
|
|
48
|
+
onsuccess: ((event: any) => void) | null;
|
|
49
|
+
onerror: ((event: any) => void) | null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
declare class IDBRequest {
|
|
53
|
+
result: any;
|
|
54
|
+
error: unknown;
|
|
55
|
+
onsuccess: ((event: any) => void) | null;
|
|
56
|
+
onerror: ((event: any) => void) | null;
|
|
57
|
+
}
|
|
@@ -38,7 +38,7 @@ interface EditablePersonaData {
|
|
|
38
38
|
traits: YAMLTrait[];
|
|
39
39
|
topics: YAMLPersonaTopic[];
|
|
40
40
|
heartbeat_delay_ms?: string | null;
|
|
41
|
-
|
|
41
|
+
context_window_ms?: string | null;
|
|
42
42
|
is_paused?: boolean;
|
|
43
43
|
pause_until?: string;
|
|
44
44
|
is_static?: boolean;
|
|
@@ -179,7 +179,9 @@ export function newPersonaFromYAML(yamlContent: string, allTools?: ToolDefinitio
|
|
|
179
179
|
heartbeat_delay_ms: data.heartbeat_delay_ms == null
|
|
180
180
|
? undefined
|
|
181
181
|
: parseDuration(data.heartbeat_delay_ms) ?? undefined,
|
|
182
|
-
|
|
182
|
+
context_window_ms: data.context_window_ms == null
|
|
183
|
+
? undefined
|
|
184
|
+
: parseDuration(data.context_window_ms) ?? undefined,
|
|
183
185
|
tools: resolvePersonaToolsFromMap(data.tools, allTools ?? [], allProviders ?? []),
|
|
184
186
|
};
|
|
185
187
|
}
|
|
@@ -218,7 +220,7 @@ export function personaToYAML(persona: PersonaEntity, allGroups?: string[], allT
|
|
|
218
220
|
name, perspective, approach, personal_stake, sentiment: sentiment ?? 0, exposure_current, exposure_desired
|
|
219
221
|
})),
|
|
220
222
|
heartbeat_delay_ms: persona.heartbeat_delay_ms ? formatDuration(persona.heartbeat_delay_ms) : null,
|
|
221
|
-
|
|
223
|
+
context_window_ms: persona.context_window_ms ? formatDuration(persona.context_window_ms) : null,
|
|
222
224
|
is_paused: persona.is_paused || undefined,
|
|
223
225
|
pause_until: persona.pause_until,
|
|
224
226
|
is_static: persona.is_static || undefined,
|
|
@@ -328,7 +330,9 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
328
330
|
heartbeat_delay_ms: data.heartbeat_delay_ms == null
|
|
329
331
|
? undefined
|
|
330
332
|
: parseDuration(data.heartbeat_delay_ms) ?? undefined,
|
|
331
|
-
|
|
333
|
+
context_window_ms: data.context_window_ms == null
|
|
334
|
+
? undefined
|
|
335
|
+
: parseDuration(data.context_window_ms) ?? undefined,
|
|
332
336
|
is_paused: data.is_paused ?? false,
|
|
333
337
|
pause_until: data.pause_until,
|
|
334
338
|
is_static: data.is_static ?? false,
|
|
@@ -16,7 +16,7 @@ interface EditableSettingsData {
|
|
|
16
16
|
rewrite_model?: string | null;
|
|
17
17
|
name_display?: string | null;
|
|
18
18
|
default_heartbeat_ms?: string | null;
|
|
19
|
-
|
|
19
|
+
default_context_window_ms?: string | null;
|
|
20
20
|
message_min_count?: number | null;
|
|
21
21
|
message_max_age_days?: number | null;
|
|
22
22
|
ceremony?: {
|
|
@@ -65,7 +65,7 @@ export function settingsToYAML(settings: HumanSettings | undefined, accounts: Pr
|
|
|
65
65
|
rewrite_model: guidToDisplay(settings?.rewrite_model),
|
|
66
66
|
name_display: settings?.name_display ?? null,
|
|
67
67
|
default_heartbeat_ms: formatDuration(settings?.default_heartbeat_ms ?? 1800000),
|
|
68
|
-
|
|
68
|
+
default_context_window_ms: formatDuration(settings?.default_context_window_ms ?? 28800000),
|
|
69
69
|
message_min_count: settings?.message_min_count ?? 200,
|
|
70
70
|
message_max_age_days: settings?.message_max_age_days ?? 14,
|
|
71
71
|
ceremony: {
|
|
@@ -190,7 +190,7 @@ export function settingsFromYAML(yamlContent: string, original: HumanSettings |
|
|
|
190
190
|
rewrite_model: displayToGuid(data.rewrite_model),
|
|
191
191
|
name_display: nullToUndefined(data.name_display),
|
|
192
192
|
default_heartbeat_ms: parseMsDuration(data.default_heartbeat_ms, 1800000),
|
|
193
|
-
|
|
193
|
+
default_context_window_ms: parseMsDuration(data.default_context_window_ms, 28800000),
|
|
194
194
|
message_min_count: nullToUndefined(data.message_min_count),
|
|
195
195
|
message_max_age_days: nullToUndefined(data.message_max_age_days),
|
|
196
196
|
ceremony,
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { LLMRequestType, LLMPriority, LLMNextStep } from "../types.js";
|
|
2
|
-
import type { StateManager } from "../state-manager.js";
|
|
3
|
-
import { buildPersonMigrationPrompt } from "../../prompts/ceremony/index.js";
|
|
4
|
-
|
|
5
|
-
export function queuePersonMigration(state: StateManager): void {
|
|
6
|
-
const human = state.getHuman();
|
|
7
|
-
|
|
8
|
-
if (human.settings?.people_migration_complete) {
|
|
9
|
-
console.log("[PersonMigration] Migration complete flag set — skipping");
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const unmigrated = human.people.filter(p => !p.identifiers || p.identifiers.length === 0);
|
|
14
|
-
|
|
15
|
-
if (unmigrated.length === 0) {
|
|
16
|
-
console.log("[PersonMigration] All Person records have identifiers — marking migration complete");
|
|
17
|
-
state.setHuman({
|
|
18
|
-
...human,
|
|
19
|
-
settings: {
|
|
20
|
-
...human.settings,
|
|
21
|
-
people_migration_complete: true,
|
|
22
|
-
},
|
|
23
|
-
});
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
console.log(`[PersonMigration] Queuing migration for ${unmigrated.length} Person record(s)`);
|
|
28
|
-
|
|
29
|
-
const rewriteModel = human.settings?.rewrite_model;
|
|
30
|
-
|
|
31
|
-
for (const person of unmigrated) {
|
|
32
|
-
const prompt = buildPersonMigrationPrompt({
|
|
33
|
-
person: {
|
|
34
|
-
name: person.name,
|
|
35
|
-
description: person.description,
|
|
36
|
-
relationship: person.relationship,
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
state.queue_enqueue({
|
|
41
|
-
type: LLMRequestType.JSON,
|
|
42
|
-
priority: LLMPriority.Normal,
|
|
43
|
-
system: prompt.system,
|
|
44
|
-
user: prompt.user,
|
|
45
|
-
next_step: LLMNextStep.HandlePersonIdentifierMigration,
|
|
46
|
-
...(rewriteModel ? { model: rewriteModel } : {}),
|
|
47
|
-
data: {
|
|
48
|
-
person_id: person.id,
|
|
49
|
-
ceremony_progress: 1,
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
console.log(`[PersonMigration] Queued ${unmigrated.length} migration request(s)`);
|
|
55
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { DescriptionCheckPromptData } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export function buildDescriptionCheckPrompt(data: DescriptionCheckPromptData): { system: string; user: string } {
|
|
4
|
-
const traitList = data.traits.length > 0
|
|
5
|
-
? data.traits.map(t => `- ${t.name}: ${t.description}`).join("\n")
|
|
6
|
-
: "No traits defined";
|
|
7
|
-
|
|
8
|
-
const topicList = data.topics.length > 0
|
|
9
|
-
? data.topics.map(t => `- ${t.name}: ${t.perspective} (exposure: ${t.exposure_current.toFixed(2)})`).join("\n")
|
|
10
|
-
: "No topics defined";
|
|
11
|
-
|
|
12
|
-
const system = `You are evaluating whether a persona's description needs updating.
|
|
13
|
-
|
|
14
|
-
A description should ONLY be updated if there is a SIGNIFICANT mismatch between:
|
|
15
|
-
- The current description
|
|
16
|
-
- The persona's current traits and topics
|
|
17
|
-
|
|
18
|
-
Be VERY conservative. Only recommend updating if:
|
|
19
|
-
- The description mentions interests/traits that are completely absent from current data
|
|
20
|
-
- The persona has developed major new interests not reflected in the description
|
|
21
|
-
- The description actively contradicts the current personality
|
|
22
|
-
|
|
23
|
-
Do NOT recommend updating for:
|
|
24
|
-
- Minor differences or evolution
|
|
25
|
-
- Natural topic drift over time
|
|
26
|
-
- Missing details that aren't contradictions
|
|
27
|
-
|
|
28
|
-
Return JSON: { "should_update": true/false, "reason": "explanation" }`;
|
|
29
|
-
|
|
30
|
-
const user = `Persona: ${data.persona_name}
|
|
31
|
-
|
|
32
|
-
Current short description:
|
|
33
|
-
${data.current_short_description ?? "(none)"}
|
|
34
|
-
|
|
35
|
-
Current long description:
|
|
36
|
-
${data.current_long_description ?? "(none)"}
|
|
37
|
-
|
|
38
|
-
Current personality traits:
|
|
39
|
-
${traitList}
|
|
40
|
-
|
|
41
|
-
Current topics of interest:
|
|
42
|
-
${topicList}
|
|
43
|
-
|
|
44
|
-
Does this persona's description need updating based on their current traits and topics?
|
|
45
|
-
|
|
46
|
-
**Return JSON:**
|
|
47
|
-
\`\`\`json
|
|
48
|
-
{ "should_update": true, "reason": "explanation" }
|
|
49
|
-
\`\`\`
|
|
50
|
-
|
|
51
|
-
If no update is needed: \`{ "should_update": false, "reason": "explanation" }\``;
|
|
52
|
-
|
|
53
|
-
return { system, user };
|
|
54
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import type { PersonaExpirePromptData } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export function buildPersonaExpirePrompt(data: PersonaExpirePromptData): { system: string; user: string } {
|
|
4
|
-
const topicList = data.topics.map(t => {
|
|
5
|
-
const display = t.perspective || t.name;
|
|
6
|
-
return `- "${t.name}" (exposure: ${t.exposure_current.toFixed(2)}, sentiment: ${t.sentiment.toFixed(2)})\n Perspective: ${display}`;
|
|
7
|
-
}).join("\n");
|
|
8
|
-
|
|
9
|
-
const system = `You are evaluating which topics a persona should stop caring about.
|
|
10
|
-
|
|
11
|
-
A topic should be removed if:
|
|
12
|
-
- Its exposure_current is very low (< 0.15) indicating prolonged disinterest
|
|
13
|
-
- It no longer aligns with the persona's current interests
|
|
14
|
-
- It was a temporary interest that has faded
|
|
15
|
-
|
|
16
|
-
Be conservative - only suggest removing topics that are clearly irrelevant.
|
|
17
|
-
If unsure, keep the topic.
|
|
18
|
-
|
|
19
|
-
Return JSON: { "topic_ids_to_remove": ["id1", "id2"] }
|
|
20
|
-
Return empty array if no topics should be removed.`;
|
|
21
|
-
|
|
22
|
-
const user = `Persona: ${data.persona_name}
|
|
23
|
-
|
|
24
|
-
Current topics:
|
|
25
|
-
${topicList}
|
|
26
|
-
|
|
27
|
-
Which topics, if any, should this persona stop caring about?
|
|
28
|
-
|
|
29
|
-
**Return JSON:**
|
|
30
|
-
\`\`\`json
|
|
31
|
-
{ "topic_ids_to_remove": ["id1", "id2"] }
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
Return an empty array if no topics should be removed.`;
|
|
35
|
-
|
|
36
|
-
return { system, user };
|
|
37
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import type { PersonaExplorePromptData } from "./types.js";
|
|
2
|
-
|
|
3
|
-
export function buildPersonaExplorePrompt(data: PersonaExplorePromptData): { system: string; user: string } {
|
|
4
|
-
const traitList = data.traits.map(t =>
|
|
5
|
-
`- ${t.name}: ${t.description} (strength: ${t.strength?.toFixed(2) ?? "N/A"})`
|
|
6
|
-
).join("\n");
|
|
7
|
-
|
|
8
|
-
const topicList = data.remaining_topics.map(t =>
|
|
9
|
-
`- ${t.name}: ${t.perspective || t.name}`
|
|
10
|
-
).join("\n");
|
|
11
|
-
|
|
12
|
-
const themeList = data.recent_conversation_themes.length > 0
|
|
13
|
-
? data.recent_conversation_themes.map(t => `- ${t}`).join("\n")
|
|
14
|
-
: "No recent themes identified";
|
|
15
|
-
|
|
16
|
-
const system = `You are generating new conversation topics for a persona.
|
|
17
|
-
|
|
18
|
-
Topics should:
|
|
19
|
-
- Align with the persona's traits and personality
|
|
20
|
-
- Complement (not duplicate) existing topics
|
|
21
|
-
- Be natural extensions of recent conversation themes
|
|
22
|
-
- Be specific enough to drive interesting conversations
|
|
23
|
-
|
|
24
|
-
Generate 1-3 new topics that this persona would genuinely care about.
|
|
25
|
-
|
|
26
|
-
Return JSON:
|
|
27
|
-
{
|
|
28
|
-
"new_topics": [{
|
|
29
|
-
"name": "Topic Name",
|
|
30
|
-
"perspective": "The persona's view or opinion on this topic",
|
|
31
|
-
"approach": "How they prefer to engage with this topic",
|
|
32
|
-
"personal_stake": "Why this topic matters to them personally",
|
|
33
|
-
"sentiment": 0.5,
|
|
34
|
-
"exposure_current": 0.2,
|
|
35
|
-
"exposure_desired": 0.6
|
|
36
|
-
}]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
**Field guidance:**
|
|
40
|
-
- perspective: REQUIRED - their actual view/opinion
|
|
41
|
-
- approach: Optional if unclear - how they discuss this topic
|
|
42
|
-
- personal_stake: Optional if unclear - why it matters to them
|
|
43
|
-
- exposure_current: Low (0.2) since these are new topics
|
|
44
|
-
- exposure_desired: How much the persona would want to discuss this`;
|
|
45
|
-
|
|
46
|
-
const user = `Persona: ${data.persona_name}
|
|
47
|
-
|
|
48
|
-
Personality traits:
|
|
49
|
-
${traitList}
|
|
50
|
-
|
|
51
|
-
Current topics (do not duplicate):
|
|
52
|
-
${topicList}
|
|
53
|
-
|
|
54
|
-
Recent conversation themes:
|
|
55
|
-
${themeList}
|
|
56
|
-
|
|
57
|
-
Generate new topics this persona would care about.
|
|
58
|
-
|
|
59
|
-
**Return JSON:**
|
|
60
|
-
\`\`\`json
|
|
61
|
-
{
|
|
62
|
-
"new_topics": [
|
|
63
|
-
{
|
|
64
|
-
"name": "Topic Name",
|
|
65
|
-
"perspective": "Their view or opinion",
|
|
66
|
-
"approach": "How they engage with it",
|
|
67
|
-
"personal_stake": "Why it matters to them",
|
|
68
|
-
"sentiment": 0.5,
|
|
69
|
-
"exposure_current": 0.2,
|
|
70
|
-
"exposure_desired": 0.6
|
|
71
|
-
}
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
\`\`\``;
|
|
75
|
-
|
|
76
|
-
return { system, user };
|
|
77
|
-
}
|