ei-tui 0.1.24 → 0.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/README.md +42 -0
- package/package.json +1 -1
- package/src/README.md +4 -11
- package/src/cli/README.md +4 -5
- package/src/cli/retrieval.ts +3 -25
- package/src/cli.ts +3 -7
- package/src/core/AGENTS.md +1 -1
- package/src/core/constants/built-in-facts.ts +49 -0
- package/src/core/constants/index.ts +1 -0
- package/src/core/context-utils.ts +0 -1
- package/src/core/embedding-service.ts +8 -0
- package/src/core/handlers/dedup.ts +34 -14
- package/src/core/handlers/heartbeat.ts +2 -3
- package/src/core/handlers/human-extraction.ts +95 -30
- package/src/core/handlers/human-matching.ts +326 -248
- package/src/core/handlers/index.ts +8 -6
- package/src/core/handlers/persona-generation.ts +8 -8
- package/src/core/handlers/rewrite.ts +4 -29
- package/src/core/handlers/utils.ts +23 -1
- package/src/core/heartbeat-manager.ts +2 -4
- package/src/core/human-data-manager.ts +5 -27
- package/src/core/message-manager.ts +10 -10
- package/src/core/orchestrators/ceremony.ts +60 -46
- package/src/core/orchestrators/dedup-phase.ts +11 -5
- package/src/core/orchestrators/human-extraction.ts +351 -207
- package/src/core/orchestrators/index.ts +6 -4
- package/src/core/orchestrators/persona-generation.ts +3 -3
- package/src/core/processor.ts +113 -22
- package/src/core/prompt-context-builder.ts +4 -6
- package/src/core/state/human.ts +1 -26
- package/src/core/state/personas.ts +2 -2
- package/src/core/state-manager.ts +107 -14
- package/src/core/tools/builtin/read-memory.ts +7 -8
- package/src/core/types/data-items.ts +2 -4
- package/src/core/types/entities.ts +6 -4
- package/src/core/types/enums.ts +6 -9
- package/src/core/types/llm.ts +2 -2
- package/src/core/utils/crossFind.ts +2 -5
- package/src/core/utils/event-windows.ts +31 -0
- package/src/integrations/claude-code/importer.ts +8 -4
- package/src/integrations/claude-code/types.ts +2 -0
- package/src/integrations/opencode/importer.ts +7 -3
- package/src/prompts/AGENTS.md +73 -1
- package/src/prompts/ceremony/dedup.ts +41 -7
- package/src/prompts/ceremony/rewrite.ts +3 -22
- package/src/prompts/ceremony/types.ts +3 -3
- package/src/prompts/generation/descriptions.ts +2 -2
- package/src/prompts/generation/types.ts +2 -2
- package/src/prompts/heartbeat/types.ts +2 -2
- package/src/prompts/human/event-scan.ts +122 -0
- package/src/prompts/human/fact-find.ts +106 -0
- package/src/prompts/human/fact-scan.ts +0 -2
- package/src/prompts/human/index.ts +17 -10
- package/src/prompts/human/person-match.ts +65 -0
- package/src/prompts/human/person-scan.ts +52 -59
- package/src/prompts/human/person-update.ts +241 -0
- package/src/prompts/human/topic-match.ts +65 -0
- package/src/prompts/human/topic-scan.ts +51 -71
- package/src/prompts/human/topic-update.ts +295 -0
- package/src/prompts/human/types.ts +63 -40
- package/src/prompts/index.ts +4 -8
- package/src/prompts/persona/topics-update.ts +2 -2
- package/src/prompts/persona/traits.ts +2 -2
- package/src/prompts/persona/types.ts +3 -3
- package/src/prompts/response/index.ts +1 -1
- package/src/prompts/response/sections.ts +9 -12
- package/src/prompts/response/types.ts +2 -3
- package/src/storage/embeddings.ts +1 -1
- package/src/storage/index.ts +1 -0
- package/src/storage/indexed.ts +174 -0
- package/src/storage/merge.ts +67 -2
- package/tui/src/app.tsx +7 -5
- package/tui/src/commands/archive.tsx +2 -2
- package/tui/src/commands/context.tsx +3 -4
- package/tui/src/commands/delete.tsx +4 -4
- package/tui/src/commands/dlq.ts +3 -4
- package/tui/src/commands/help.tsx +1 -1
- package/tui/src/commands/me.tsx +8 -18
- package/tui/src/commands/persona.tsx +2 -2
- package/tui/src/commands/provider.tsx +3 -5
- package/tui/src/commands/queue.ts +3 -4
- package/tui/src/commands/quotes.tsx +6 -8
- package/tui/src/commands/registry.ts +1 -1
- package/tui/src/commands/setsync.tsx +2 -2
- package/tui/src/commands/settings.tsx +18 -4
- package/tui/src/commands/spotify-auth.ts +0 -1
- package/tui/src/commands/tools.tsx +4 -5
- package/tui/src/context/ei.tsx +5 -14
- package/tui/src/context/overlay.tsx +17 -6
- package/tui/src/util/editor.ts +22 -11
- package/tui/src/util/persona-editor.tsx +6 -8
- package/tui/src/util/provider-editor.tsx +6 -8
- package/tui/src/util/toolkit-editor.tsx +3 -4
- package/tui/src/util/yaml-serializers.ts +48 -33
- package/src/cli/commands/traits.ts +0 -25
- package/src/prompts/human/item-match.ts +0 -74
- package/src/prompts/human/item-update.ts +0 -364
- package/src/prompts/human/trait-scan.ts +0 -115
|
@@ -77,7 +77,7 @@ export const providerCommand: Command = {
|
|
|
77
77
|
ctx.showNotification("No providers configured. Use /provider new to create one.", "info");
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
|
-
ctx.showOverlay((hideOverlay) => (
|
|
80
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
81
81
|
<ProviderListOverlay
|
|
82
82
|
providers={providers}
|
|
83
83
|
activeProviderKey={activeKey}
|
|
@@ -86,8 +86,7 @@ export const providerCommand: Command = {
|
|
|
86
86
|
await setProviderOnPersona(provider.key, provider.defaultModel, ctx);
|
|
87
87
|
}}
|
|
88
88
|
onEdit={async (provider) => {
|
|
89
|
-
|
|
90
|
-
await new Promise(r => setTimeout(r, 50));
|
|
89
|
+
hideForEditor();
|
|
91
90
|
const human = await ctx.ei.getHuman();
|
|
92
91
|
const account = human.settings?.accounts?.find(a => a.id === provider.id);
|
|
93
92
|
if (account) {
|
|
@@ -96,12 +95,11 @@ export const providerCommand: Command = {
|
|
|
96
95
|
}}
|
|
97
96
|
onNew={async () => {
|
|
98
97
|
hideOverlay();
|
|
99
|
-
await new Promise(r => setTimeout(r, 50));
|
|
100
98
|
await createProviderViaEditor(ctx);
|
|
101
99
|
}}
|
|
102
100
|
onDismiss={hideOverlay}
|
|
103
101
|
/>
|
|
104
|
-
));
|
|
102
|
+
), ctx.renderer);
|
|
105
103
|
return;
|
|
106
104
|
}
|
|
107
105
|
|
|
@@ -53,18 +53,17 @@ export const queueCommand: Command = {
|
|
|
53
53
|
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
54
54
|
|
|
55
55
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
56
|
-
ctx.showOverlay((hideOverlay) =>
|
|
56
|
+
ctx.showOverlay((hideOverlay, hideForEditor) =>
|
|
57
57
|
ConfirmOverlay({
|
|
58
58
|
message: `YAML error:\n${errorMsg}\n\nRe-edit?`,
|
|
59
|
-
onConfirm: () => {
|
|
59
|
+
onConfirm: () => { hideForEditor(); resolve(true); },
|
|
60
60
|
onCancel: () => { hideOverlay(); resolve(false); },
|
|
61
61
|
})
|
|
62
|
-
);
|
|
62
|
+
, ctx.renderer);
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
if (shouldReEdit) {
|
|
66
66
|
yamlContent = result.content;
|
|
67
|
-
await new Promise(r => setTimeout(r, 50));
|
|
68
67
|
continue;
|
|
69
68
|
}
|
|
70
69
|
|
|
@@ -75,12 +75,12 @@ async function openQuotesInEditor(
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
78
|
-
ctx.showOverlay((hideOverlay) => (
|
|
78
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
79
79
|
<ConfirmOverlay
|
|
80
80
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
81
81
|
onConfirm={() => {
|
|
82
82
|
logger.debug("[quotes] user confirmed re-edit");
|
|
83
|
-
|
|
83
|
+
hideForEditor();
|
|
84
84
|
resolve(true);
|
|
85
85
|
}}
|
|
86
86
|
onCancel={() => {
|
|
@@ -89,7 +89,7 @@ async function openQuotesInEditor(
|
|
|
89
89
|
resolve(false);
|
|
90
90
|
}}
|
|
91
91
|
/>
|
|
92
|
-
));
|
|
92
|
+
), ctx.renderer);
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
logger.debug("[quotes] shouldReEdit", { shouldReEdit, iteration: editorIteration });
|
|
@@ -97,7 +97,6 @@ async function openQuotesInEditor(
|
|
|
97
97
|
if (shouldReEdit) {
|
|
98
98
|
yamlContent = result.content;
|
|
99
99
|
logger.debug("[quotes] continuing to next iteration");
|
|
100
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
101
100
|
continue;
|
|
102
101
|
} else {
|
|
103
102
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -146,14 +145,13 @@ export const quotesCommand: Command = {
|
|
|
146
145
|
const allQuotes = await ctx.ei.getQuotes();
|
|
147
146
|
const messageQuotes = allQuotes.filter(q => q.message_id === targetMessage.id);
|
|
148
147
|
|
|
149
|
-
ctx.showOverlay((hide) => (
|
|
148
|
+
ctx.showOverlay((hide, hideForEditor) => (
|
|
150
149
|
<QuotesOverlay
|
|
151
150
|
quotes={messageQuotes}
|
|
152
151
|
messageIndex={index}
|
|
153
152
|
onClose={hide}
|
|
154
153
|
onEdit={async () => {
|
|
155
|
-
|
|
156
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
154
|
+
hideForEditor();
|
|
157
155
|
await openQuotesInEditor(ctx, messageQuotes, `quotes from message [${index}]`);
|
|
158
156
|
}}
|
|
159
157
|
onDelete={async (quoteId) => {
|
|
@@ -161,7 +159,7 @@ export const quotesCommand: Command = {
|
|
|
161
159
|
ctx.showNotification("Quote deleted", "info");
|
|
162
160
|
}}
|
|
163
161
|
/>
|
|
164
|
-
));
|
|
162
|
+
), ctx.renderer);
|
|
165
163
|
return;
|
|
166
164
|
}
|
|
167
165
|
|
|
@@ -11,7 +11,7 @@ export interface Command {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export interface CommandContext {
|
|
14
|
-
showOverlay: (renderer: OverlayRenderer) => void;
|
|
14
|
+
showOverlay: (renderer: OverlayRenderer, cliRenderer?: CliRenderer) => void;
|
|
15
15
|
hideOverlay: () => void;
|
|
16
16
|
showNotification: (msg: string, level: "error" | "warn" | "info") => void;
|
|
17
17
|
exitApp: () => Promise<void>;
|
|
@@ -16,7 +16,7 @@ export const setSyncCommand: Command = {
|
|
|
16
16
|
const [username, passphrase] = args;
|
|
17
17
|
|
|
18
18
|
const confirmed = await new Promise<boolean>((resolve) => {
|
|
19
|
-
ctx.showOverlay((hideOverlay) => (
|
|
19
|
+
ctx.showOverlay((hideOverlay, _hideForEditor) => (
|
|
20
20
|
<ConfirmOverlay
|
|
21
21
|
message={`Set sync credentials for "${username}"?\n\nThis requires a restart. Just re-run ei once it closes!`}
|
|
22
22
|
onConfirm={() => {
|
|
@@ -28,7 +28,7 @@ export const setSyncCommand: Command = {
|
|
|
28
28
|
resolve(false);
|
|
29
29
|
}}
|
|
30
30
|
/>
|
|
31
|
-
));
|
|
31
|
+
), ctx.renderer);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
if (!confirmed) {
|
|
@@ -45,6 +45,21 @@ export const settingsCommand: Command = {
|
|
|
45
45
|
// Validate provider name in default_model (case-insensitive match + auto-correct)
|
|
46
46
|
const llmAccounts = human.settings?.accounts?.filter(a => a.type === "llm") ?? [];
|
|
47
47
|
newSettings.default_model = validateModelProvider(newSettings.default_model, llmAccounts);
|
|
48
|
+
|
|
49
|
+
if (newSettings.opencode?.extraction_model) {
|
|
50
|
+
newSettings.opencode.extraction_model = validateModelProvider(
|
|
51
|
+
newSettings.opencode.extraction_model,
|
|
52
|
+
llmAccounts
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (newSettings.claudeCode?.extraction_model) {
|
|
57
|
+
newSettings.claudeCode.extraction_model = validateModelProvider(
|
|
58
|
+
newSettings.claudeCode.extraction_model,
|
|
59
|
+
llmAccounts
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
await ctx.ei.updateSettings(newSettings);
|
|
49
64
|
ctx.showNotification("Settings updated", "info");
|
|
50
65
|
return;
|
|
@@ -54,11 +69,11 @@ export const settingsCommand: Command = {
|
|
|
54
69
|
logger.debug("[settings] YAML parse error, prompting for re-edit", { iteration: editorIteration, error: errorMsg });
|
|
55
70
|
|
|
56
71
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
57
|
-
ctx.showOverlay((hideOverlay) => (
|
|
72
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
58
73
|
<ConfirmOverlay
|
|
59
74
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
60
75
|
onConfirm={() => {
|
|
61
|
-
|
|
76
|
+
hideForEditor();
|
|
62
77
|
resolve(true);
|
|
63
78
|
}}
|
|
64
79
|
onCancel={() => {
|
|
@@ -66,12 +81,11 @@ export const settingsCommand: Command = {
|
|
|
66
81
|
resolve(false);
|
|
67
82
|
}}
|
|
68
83
|
/>
|
|
69
|
-
));
|
|
84
|
+
), ctx.renderer);
|
|
70
85
|
});
|
|
71
86
|
|
|
72
87
|
if (shouldReEdit) {
|
|
73
88
|
yamlContent = result.content;
|
|
74
|
-
await new Promise(r => setTimeout(r, 50));
|
|
75
89
|
continue;
|
|
76
90
|
} else {
|
|
77
91
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -45,7 +45,6 @@ export async function runSpotifyAuth(ctx: CommandContext): Promise<void> {
|
|
|
45
45
|
const codePromise = waitForAuthCode(ctx);
|
|
46
46
|
|
|
47
47
|
// Give the server a tick to bind its port before opening the browser
|
|
48
|
-
await new Promise<void>((r) => setTimeout(r, 50));
|
|
49
48
|
logger.info("[spotify-auth] Server should be up — opening browser now");
|
|
50
49
|
|
|
51
50
|
// Open the authorization URL in the user's default browser
|
|
@@ -25,20 +25,19 @@ export const toolsCommand: Command = {
|
|
|
25
25
|
toolCount: allTools.filter(t => t.provider_id === p.id).length,
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
|
-
ctx.showOverlay((hideOverlay) => (
|
|
28
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
29
29
|
<ToolkitListOverlay
|
|
30
30
|
toolkits={toolkits}
|
|
31
31
|
onEdit={async (toolkit) => {
|
|
32
|
-
|
|
33
|
-
await new Promise(r => setTimeout(r, 50));
|
|
32
|
+
hideForEditor();
|
|
34
33
|
const provider = providers.find(p => p.id === toolkit.id);
|
|
35
34
|
if (provider) {
|
|
36
|
-
const providerTools = allTools.filter(t => t.provider_id ===
|
|
35
|
+
const providerTools = allTools.filter(t => t.provider_id === toolkit.id);
|
|
37
36
|
await openToolkitEditor(provider, providerTools, ctx);
|
|
38
37
|
}
|
|
39
38
|
}}
|
|
40
39
|
onDismiss={hideOverlay}
|
|
41
40
|
/>
|
|
42
|
-
));
|
|
41
|
+
), ctx.renderer);
|
|
43
42
|
},
|
|
44
43
|
};
|
package/tui/src/context/ei.tsx
CHANGED
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
HumanEntity,
|
|
24
24
|
HumanSettings,
|
|
25
25
|
Fact,
|
|
26
|
-
Trait,
|
|
27
26
|
Topic,
|
|
28
27
|
Person,
|
|
29
28
|
Quote,
|
|
@@ -78,10 +77,9 @@ export interface EiContextValue {
|
|
|
78
77
|
updateHuman: (updates: Partial<HumanEntity>) => Promise<void>;
|
|
79
78
|
updateSettings: (updates: Partial<HumanSettings>) => Promise<void>;
|
|
80
79
|
upsertFact: (fact: Fact) => Promise<void>;
|
|
81
|
-
upsertTrait: (trait: Trait) => Promise<void>;
|
|
82
80
|
upsertTopic: (topic: Topic) => Promise<void>;
|
|
83
81
|
upsertPerson: (person: Person) => Promise<void>;
|
|
84
|
-
removeDataItem: (type: "fact" | "
|
|
82
|
+
removeDataItem: (type: "fact" | "topic" | "person", id: string) => Promise<void>;
|
|
85
83
|
syncStatus: () => { configured: boolean; envBased: boolean };
|
|
86
84
|
triggerSync: () => Promise<{ success: boolean; error?: string }>;
|
|
87
85
|
getGroupList: () => Promise<string[]>;
|
|
@@ -92,10 +90,9 @@ export interface EiContextValue {
|
|
|
92
90
|
quotesVersion: () => number;
|
|
93
91
|
searchHumanData: (
|
|
94
92
|
query: string,
|
|
95
|
-
options?: { types?: Array<"fact" | "
|
|
93
|
+
options?: { types?: Array<"fact" | "topic" | "person" | "quote">; limit?: number }
|
|
96
94
|
) => Promise<{
|
|
97
95
|
facts: Fact[];
|
|
98
|
-
traits: Trait[];
|
|
99
96
|
topics: Topic[];
|
|
100
97
|
people: Person[];
|
|
101
98
|
quotes: Quote[];
|
|
@@ -329,11 +326,6 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
329
326
|
await processor.upsertFact(fact);
|
|
330
327
|
};
|
|
331
328
|
|
|
332
|
-
const upsertTrait = async (trait: Trait) => {
|
|
333
|
-
if (!processor) return;
|
|
334
|
-
await processor.upsertTrait(trait);
|
|
335
|
-
};
|
|
336
|
-
|
|
337
329
|
const upsertTopic = async (topic: Topic) => {
|
|
338
330
|
if (!processor) return;
|
|
339
331
|
await processor.upsertTopic(topic);
|
|
@@ -344,7 +336,7 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
344
336
|
await processor.upsertPerson(person);
|
|
345
337
|
};
|
|
346
338
|
|
|
347
|
-
const removeDataItem = async (type: "fact" | "
|
|
339
|
+
const removeDataItem = async (type: "fact" | "topic" | "person", id: string) => {
|
|
348
340
|
if (!processor) return;
|
|
349
341
|
await processor.removeDataItem(type, id);
|
|
350
342
|
};
|
|
@@ -450,9 +442,9 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
450
442
|
|
|
451
443
|
const searchHumanData = async (
|
|
452
444
|
query: string,
|
|
453
|
-
options?: { types?: Array<"fact" | "
|
|
445
|
+
options?: { types?: Array<"fact" | "topic" | "person" | "quote">; limit?: number }
|
|
454
446
|
) => {
|
|
455
|
-
if (!processor) return { facts: [],
|
|
447
|
+
if (!processor) return { facts: [], topics: [], people: [], quotes: [] };
|
|
456
448
|
return processor.searchHumanData(query, options);
|
|
457
449
|
};
|
|
458
450
|
|
|
@@ -648,7 +640,6 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
648
640
|
updateHuman,
|
|
649
641
|
updateSettings,
|
|
650
642
|
upsertFact,
|
|
651
|
-
upsertTrait,
|
|
652
643
|
upsertTopic,
|
|
653
644
|
upsertPerson,
|
|
654
645
|
removeDataItem,
|
|
@@ -6,24 +6,35 @@ import {
|
|
|
6
6
|
type JSX,
|
|
7
7
|
type Accessor,
|
|
8
8
|
} from "solid-js";
|
|
9
|
+
import { type CliRenderer } from "@opentui/core";
|
|
9
10
|
import { logger } from "../util/logger";
|
|
10
11
|
|
|
11
|
-
export type OverlayRenderer = (hideOverlay: () => void) => JSX.Element;
|
|
12
|
+
export type OverlayRenderer = (hideOverlay: () => void, hideForEditor: () => void) => JSX.Element;
|
|
12
13
|
|
|
13
14
|
interface OverlayContextValue {
|
|
14
|
-
overlayRenderer: Accessor<
|
|
15
|
-
showOverlay: (renderer: OverlayRenderer) => void;
|
|
15
|
+
overlayRenderer: Accessor<(() => JSX.Element) | null>;
|
|
16
|
+
showOverlay: (renderer: OverlayRenderer, cliRenderer?: CliRenderer) => void;
|
|
16
17
|
hideOverlay: () => void;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
const OverlayContext = createContext<OverlayContextValue>();
|
|
20
21
|
|
|
21
22
|
export const OverlayProvider: ParentComponent = (props) => {
|
|
22
|
-
const [overlayRenderer, setOverlayRenderer] = createSignal<
|
|
23
|
+
const [overlayRenderer, setOverlayRenderer] = createSignal<(() => JSX.Element) | null>(null);
|
|
23
24
|
|
|
24
|
-
const showOverlay = (renderer: OverlayRenderer) => {
|
|
25
|
+
const showOverlay = (renderer: OverlayRenderer, cliRenderer?: CliRenderer) => {
|
|
25
26
|
logger.debug("[overlay] showOverlay called");
|
|
26
|
-
|
|
27
|
+
const hideForEditor = () => {
|
|
28
|
+
if (cliRenderer) {
|
|
29
|
+
cliRenderer.currentRenderBuffer.clear();
|
|
30
|
+
}
|
|
31
|
+
setOverlayRenderer(null);
|
|
32
|
+
};
|
|
33
|
+
const hideOverlay = () => {
|
|
34
|
+
logger.debug("[overlay] hideOverlay called");
|
|
35
|
+
setOverlayRenderer(null);
|
|
36
|
+
};
|
|
37
|
+
setOverlayRenderer(() => () => renderer(hideOverlay, hideForEditor));
|
|
27
38
|
};
|
|
28
39
|
|
|
29
40
|
const hideOverlay = () => {
|
package/tui/src/util/editor.ts
CHANGED
|
@@ -3,6 +3,7 @@ import * as fs from "fs";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import type { CliRenderer } from "@opentui/core";
|
|
6
|
+
import { RendererControlState } from "@opentui/core";
|
|
6
7
|
import { logger } from "./logger";
|
|
7
8
|
|
|
8
9
|
export interface EditorOptions {
|
|
@@ -103,23 +104,30 @@ export async function spawnEditor(options: EditorOptions): Promise<EditorResult>
|
|
|
103
104
|
const safeName = filename.replace(/\s+/g, "-");
|
|
104
105
|
const tmpFile = path.join(tmpDir, `ei-${Date.now()}-${safeName}`);
|
|
105
106
|
|
|
106
|
-
logger.debug("[editor] spawnEditor called", { filename, editor });
|
|
107
|
+
logger.debug("[editor] spawnEditor called - START", { filename, editor });
|
|
108
|
+
|
|
109
|
+
// CRITICAL: 50ms delay to let SolidJS reactive updates settle before suspending
|
|
110
|
+
// This prevents ghost frames when transitioning from overlays to editor
|
|
111
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
107
112
|
|
|
108
113
|
fs.writeFileSync(tmpFile, initialContent, "utf-8");
|
|
109
114
|
const originalContent = initialContent;
|
|
110
115
|
|
|
111
116
|
return new Promise((resolve) => {
|
|
112
|
-
|
|
113
|
-
renderer.
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const wasAlreadySuspended = renderer.controlState === RendererControlState.EXPLICIT_SUSPENDED;
|
|
118
|
+
logger.debug("[editor] renderer state", { controlState: renderer.controlState, wasAlreadySuspended });
|
|
119
|
+
|
|
120
|
+
if (!wasAlreadySuspended) {
|
|
121
|
+
renderer.suspend();
|
|
122
|
+
}
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
renderer.currentRenderBuffer.clear();
|
|
118
125
|
const child = spawn(editor, [tmpFile], {
|
|
119
126
|
stdio: "inherit",
|
|
120
127
|
shell: true,
|
|
121
128
|
});
|
|
122
129
|
|
|
130
|
+
|
|
123
131
|
child.on("error", () => {
|
|
124
132
|
logger.error("[editor] editor process error");
|
|
125
133
|
renderer.currentRenderBuffer.clear();
|
|
@@ -135,13 +143,16 @@ export async function spawnEditor(options: EditorOptions): Promise<EditorResult>
|
|
|
135
143
|
|
|
136
144
|
child.on("exit", (code) => {
|
|
137
145
|
logger.debug("[editor] editor process exited", { code });
|
|
138
|
-
logger.debug("[editor] calling renderer.currentRenderBuffer.clear()");
|
|
139
146
|
renderer.currentRenderBuffer.clear();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
|
|
148
|
+
if (!wasAlreadySuspended) {
|
|
149
|
+
logger.debug("[editor] calling renderer.resume()");
|
|
150
|
+
renderer.resume();
|
|
151
|
+
} else {
|
|
152
|
+
logger.debug("[editor] already suspended before spawn, skipping resume");
|
|
153
|
+
}
|
|
154
|
+
|
|
143
155
|
queueMicrotask(() => {
|
|
144
|
-
logger.debug("[editor] calling renderer.requestRender()");
|
|
145
156
|
renderer.requestRender();
|
|
146
157
|
});
|
|
147
158
|
|
|
@@ -80,12 +80,12 @@ export async function createPersonaViaEditor(options: NewPersonaEditorOptions):
|
|
|
80
80
|
logger.debug("[persona-editor] YAML parse error in new persona", { error: errorMsg });
|
|
81
81
|
|
|
82
82
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
83
|
-
ctx.showOverlay((hideOverlay) => (
|
|
83
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
84
84
|
<ConfirmOverlay
|
|
85
85
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
86
86
|
onConfirm={() => {
|
|
87
87
|
logger.debug("[persona-editor] user confirmed re-edit (new)");
|
|
88
|
-
|
|
88
|
+
hideForEditor();
|
|
89
89
|
resolve(true);
|
|
90
90
|
}}
|
|
91
91
|
onCancel={() => {
|
|
@@ -94,12 +94,11 @@ export async function createPersonaViaEditor(options: NewPersonaEditorOptions):
|
|
|
94
94
|
resolve(false);
|
|
95
95
|
}}
|
|
96
96
|
/>
|
|
97
|
-
));
|
|
97
|
+
), ctx.renderer);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
if (shouldReEdit) {
|
|
101
101
|
yamlContent = result.content;
|
|
102
|
-
await new Promise(r => setTimeout(r, 50));
|
|
103
102
|
continue;
|
|
104
103
|
} else {
|
|
105
104
|
ctx.showNotification("Creation cancelled", "info");
|
|
@@ -155,12 +154,12 @@ export async function openPersonaEditor(options: PersonaEditorOptions): Promise<
|
|
|
155
154
|
logger.debug("[persona-editor] YAML parse error", { error: errorMsg });
|
|
156
155
|
|
|
157
156
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
158
|
-
ctx.showOverlay((hideOverlay) => (
|
|
157
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
159
158
|
<ConfirmOverlay
|
|
160
159
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
161
160
|
onConfirm={() => {
|
|
162
161
|
logger.debug("[persona-editor] user confirmed re-edit");
|
|
163
|
-
|
|
162
|
+
hideForEditor();
|
|
164
163
|
resolve(true);
|
|
165
164
|
}}
|
|
166
165
|
onCancel={() => {
|
|
@@ -169,12 +168,11 @@ export async function openPersonaEditor(options: PersonaEditorOptions): Promise<
|
|
|
169
168
|
resolve(false);
|
|
170
169
|
}}
|
|
171
170
|
/>
|
|
172
|
-
));
|
|
171
|
+
), ctx.renderer);
|
|
173
172
|
});
|
|
174
173
|
|
|
175
174
|
if (shouldReEdit) {
|
|
176
175
|
yamlContent = result.content;
|
|
177
|
-
await new Promise(r => setTimeout(r, 50));
|
|
178
176
|
continue;
|
|
179
177
|
} else {
|
|
180
178
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -66,11 +66,11 @@ export async function createProviderViaEditor(ctx: CommandContext): Promise<NewP
|
|
|
66
66
|
logger.debug("[provider-editor] YAML parse error in new provider", { error: errorMsg });
|
|
67
67
|
|
|
68
68
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
69
|
-
ctx.showOverlay((hideOverlay) => (
|
|
69
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
70
70
|
<ConfirmOverlay
|
|
71
71
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
72
72
|
onConfirm={() => {
|
|
73
|
-
|
|
73
|
+
hideForEditor();
|
|
74
74
|
resolve(true);
|
|
75
75
|
}}
|
|
76
76
|
onCancel={() => {
|
|
@@ -78,12 +78,11 @@ export async function createProviderViaEditor(ctx: CommandContext): Promise<NewP
|
|
|
78
78
|
resolve(false);
|
|
79
79
|
}}
|
|
80
80
|
/>
|
|
81
|
-
));
|
|
81
|
+
), ctx.renderer);
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
if (shouldReEdit) {
|
|
85
85
|
yamlContent = result.content;
|
|
86
|
-
await new Promise(r => setTimeout(r, 50));
|
|
87
86
|
continue;
|
|
88
87
|
} else {
|
|
89
88
|
ctx.showNotification("Creation cancelled", "info");
|
|
@@ -140,11 +139,11 @@ export async function openProviderEditor(account: ProviderAccount, ctx: CommandC
|
|
|
140
139
|
logger.debug("[provider-editor] YAML parse error", { error: errorMsg });
|
|
141
140
|
|
|
142
141
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
143
|
-
ctx.showOverlay((hideOverlay) => (
|
|
142
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
144
143
|
<ConfirmOverlay
|
|
145
144
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
146
145
|
onConfirm={() => {
|
|
147
|
-
|
|
146
|
+
hideForEditor();
|
|
148
147
|
resolve(true);
|
|
149
148
|
}}
|
|
150
149
|
onCancel={() => {
|
|
@@ -152,12 +151,11 @@ export async function openProviderEditor(account: ProviderAccount, ctx: CommandC
|
|
|
152
151
|
resolve(false);
|
|
153
152
|
}}
|
|
154
153
|
/>
|
|
155
|
-
));
|
|
154
|
+
), ctx.renderer);
|
|
156
155
|
});
|
|
157
156
|
|
|
158
157
|
if (shouldReEdit) {
|
|
159
158
|
yamlContent = result.content;
|
|
160
|
-
await new Promise(r => setTimeout(r, 50));
|
|
161
159
|
continue;
|
|
162
160
|
} else {
|
|
163
161
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -55,11 +55,11 @@ export async function openToolkitEditor(
|
|
|
55
55
|
logger.debug("[toolkit-editor] YAML parse error", { error: errorMsg });
|
|
56
56
|
|
|
57
57
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
58
|
-
ctx.showOverlay((hideOverlay) => (
|
|
58
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
59
59
|
<ConfirmOverlay
|
|
60
60
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
61
61
|
onConfirm={() => {
|
|
62
|
-
|
|
62
|
+
hideForEditor();
|
|
63
63
|
resolve(true);
|
|
64
64
|
}}
|
|
65
65
|
onCancel={() => {
|
|
@@ -67,12 +67,11 @@ export async function openToolkitEditor(
|
|
|
67
67
|
resolve(false);
|
|
68
68
|
}}
|
|
69
69
|
/>
|
|
70
|
-
));
|
|
70
|
+
), ctx.renderer);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
if (shouldReEdit) {
|
|
74
74
|
yamlContent = result.content;
|
|
75
|
-
await new Promise(r => setTimeout(r, 50));
|
|
76
75
|
continue;
|
|
77
76
|
} else {
|
|
78
77
|
ctx.showNotification("Changes discarded", "info");
|