ei-tui 0.1.24 → 0.1.25
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 +1 -1
- package/src/core/handlers/dedup.ts +28 -2
- package/src/core/orchestrators/ceremony.ts +12 -9
- package/src/core/orchestrators/dedup-phase.ts +11 -4
- package/src/core/processor.ts +14 -5
- package/src/prompts/ceremony/dedup.ts +41 -7
- 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 +3 -4
- 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 +3 -4
- package/tui/src/commands/spotify-auth.ts +0 -1
- package/tui/src/commands/tools.tsx +4 -5
- 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/package.json
CHANGED
|
@@ -23,6 +23,12 @@ export async function handleDedupCurate(
|
|
|
23
23
|
const entity_ids = response.request.data.entity_ids as string[];
|
|
24
24
|
const state = stateManager.getHuman();
|
|
25
25
|
|
|
26
|
+
// Validate entity_type
|
|
27
|
+
if (!entity_type || !['fact', 'trait', 'topic', 'person'].includes(entity_type)) {
|
|
28
|
+
console.error(`[Dedup] Invalid entity_type: "${entity_type}" (from request data)`, response.request.data);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
// Parse Opus response
|
|
27
33
|
let decisions: DedupResult;
|
|
28
34
|
try {
|
|
@@ -43,8 +49,28 @@ export async function handleDedupCurate(
|
|
|
43
49
|
|
|
44
50
|
console.log(`[Dedup] Processing cluster: ${decisions.update.length} updates, ${decisions.remove.length} removals, ${decisions.add.length} additions`);
|
|
45
51
|
|
|
46
|
-
//
|
|
47
|
-
const
|
|
52
|
+
// Map entity_type to pluralized state property name
|
|
53
|
+
const pluralMap: Record<DataItemType, 'facts' | 'traits' | 'topics' | 'people'> = {
|
|
54
|
+
fact: 'facts',
|
|
55
|
+
trait: 'traits',
|
|
56
|
+
topic: 'topics',
|
|
57
|
+
person: 'people'
|
|
58
|
+
};
|
|
59
|
+
const entityList = state[pluralMap[entity_type]];
|
|
60
|
+
|
|
61
|
+
// Validate entityList exists
|
|
62
|
+
if (!entityList || !Array.isArray(entityList)) {
|
|
63
|
+
console.error(`[Dedup] entityList is ${entityList === undefined ? 'undefined' : 'not an array'} for entity_type="${entity_type}" (looking for state.${entity_type}s)`, {
|
|
64
|
+
entity_type,
|
|
65
|
+
entity_ids,
|
|
66
|
+
stateKeys: Object.keys(state),
|
|
67
|
+
factsExists: !!state.facts,
|
|
68
|
+
traitsExists: !!state.traits,
|
|
69
|
+
topicsExists: !!state.topics,
|
|
70
|
+
peopleExists: !!state.people
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
48
74
|
const entities = entity_ids
|
|
49
75
|
.map((id: string) => entityList.find((e: Fact | Trait | Topic | Person) => e.id === id))
|
|
50
76
|
.filter((e: Fact | Trait | Topic | Person | undefined): e is (Fact | Trait | Topic | Person) => e !== undefined);
|
|
@@ -182,7 +182,6 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
182
182
|
// Dedup phase complete → start Expose phase
|
|
183
183
|
console.log("[ceremony:progress] Dedup complete, starting Expose phase");
|
|
184
184
|
|
|
185
|
-
const human = state.getHuman();
|
|
186
185
|
const personas = state.persona_getAll();
|
|
187
186
|
const activePersonas = personas.filter(p =>
|
|
188
187
|
!p.is_paused &&
|
|
@@ -190,17 +189,21 @@ export function handleCeremonyProgress(state: StateManager, lastPhase: number):
|
|
|
190
189
|
!p.is_static
|
|
191
190
|
);
|
|
192
191
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
192
|
+
// Find personas with unprocessed messages (any message with p/r/o/f = false)
|
|
193
|
+
const personasWithUnprocessed = activePersonas.filter(p => {
|
|
194
|
+
const messages = state.messages_get(p.id);
|
|
195
|
+
return messages.some(msg =>
|
|
196
|
+
!msg.p ||
|
|
197
|
+
!msg.r ||
|
|
198
|
+
!msg.o ||
|
|
199
|
+
!msg.f
|
|
200
|
+
);
|
|
200
201
|
});
|
|
201
202
|
|
|
203
|
+
console.log(`[ceremony:expose] Found ${activePersonas.length} active personas, ${personasWithUnprocessed.length} with unprocessed messages`);
|
|
204
|
+
|
|
202
205
|
const options: ExtractionOptions = { ceremony_progress: 2 };
|
|
203
|
-
for (const persona of
|
|
206
|
+
for (const persona of personasWithUnprocessed) {
|
|
204
207
|
queueExposurePhase(persona.id, state, options);
|
|
205
208
|
}
|
|
206
209
|
return;
|
|
@@ -20,7 +20,7 @@ interface Cluster {
|
|
|
20
20
|
// DEDUP CANDIDATE FINDING (copied from ceremony.ts)
|
|
21
21
|
// =============================================================================
|
|
22
22
|
|
|
23
|
-
const DEDUP_DEFAULT_THRESHOLD = 0.95
|
|
23
|
+
const DEDUP_DEFAULT_THRESHOLD = 0.85; // Lowered from 0.95 based on experimental analysis: 0.95 only catches 3.9% of duplicate name groups, 0.85 catches 46.7%
|
|
24
24
|
|
|
25
25
|
function findDedupCandidates<T extends DedupableItem>(
|
|
26
26
|
items: T[],
|
|
@@ -127,6 +127,13 @@ function filterClusters(clusters: Cluster[]): Cluster[] {
|
|
|
127
127
|
|
|
128
128
|
export function queueDedupPhase(state: StateManager): void {
|
|
129
129
|
const human = state.getHuman();
|
|
130
|
+
const rewriteModel = human.settings?.rewrite_model;
|
|
131
|
+
|
|
132
|
+
if (!rewriteModel) {
|
|
133
|
+
console.log("[Dedup] rewrite_model not set — skipping dedup phase");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
const threshold = human.settings?.ceremony?.dedup_threshold ?? DEDUP_DEFAULT_THRESHOLD;
|
|
131
138
|
|
|
132
139
|
console.log(`[Dedup] Starting deduplication phase (threshold: ${threshold})`);
|
|
@@ -183,13 +190,13 @@ export function queueDedupPhase(state: StateManager): void {
|
|
|
183
190
|
system: prompt.system,
|
|
184
191
|
user: prompt.user,
|
|
185
192
|
next_step: LLMNextStep.HandleDedupCurate,
|
|
193
|
+
model: rewriteModel,
|
|
186
194
|
data: {
|
|
187
195
|
entity_type: type,
|
|
188
|
-
entity_ids: cluster.ids,
|
|
189
|
-
ceremony_progress: 1
|
|
196
|
+
entity_ids: cluster.ids,
|
|
197
|
+
ceremony_progress: 1
|
|
190
198
|
}
|
|
191
199
|
});
|
|
192
|
-
|
|
193
200
|
totalClusters++;
|
|
194
201
|
}
|
|
195
202
|
}
|
package/src/core/processor.ts
CHANGED
|
@@ -291,7 +291,7 @@ export class Processor {
|
|
|
291
291
|
builtin: true,
|
|
292
292
|
enabled: true,
|
|
293
293
|
created_at: now,
|
|
294
|
-
max_calls_per_interaction: 3
|
|
294
|
+
max_calls_per_interaction: 6, // Dedup needs to verify relationships before irreversible merges. Typical cluster (3-8 items) requires: parent concept lookup + 2 relationship verifications + context validation. Still under HARD_TOOL_CALL_LIMIT (10).
|
|
295
295
|
});
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -715,14 +715,23 @@ const toolNextSteps = new Set([
|
|
|
715
715
|
LLMNextStep.HandleHeartbeatCheck,
|
|
716
716
|
LLMNextStep.HandleEiHeartbeat,
|
|
717
717
|
LLMNextStep.HandleToolContinuation,
|
|
718
|
+
LLMNextStep.HandleDedupCurate,
|
|
718
719
|
]);
|
|
719
720
|
const toolPersonaId =
|
|
720
721
|
personaId ??
|
|
721
722
|
(request.next_step === LLMNextStep.HandleEiHeartbeat ? "ei" : undefined);
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
723
|
+
|
|
724
|
+
// Dedup operates on Human data, not persona data - provide read_memory directly
|
|
725
|
+
let tools: ToolDefinition[] = [];
|
|
726
|
+
if (request.next_step === LLMNextStep.HandleDedupCurate) {
|
|
727
|
+
const readMemory = this.stateManager.tools_getByName("read_memory");
|
|
728
|
+
if (readMemory?.enabled) {
|
|
729
|
+
tools = [readMemory];
|
|
730
|
+
}
|
|
731
|
+
} else if (toolNextSteps.has(request.next_step) && toolPersonaId) {
|
|
732
|
+
tools = this.stateManager.tools_getForPersona(toolPersonaId, this.isTUI);
|
|
733
|
+
}
|
|
734
|
+
|
|
726
735
|
console.log(
|
|
727
736
|
`[Tools] Dispatch for ${request.next_step} persona=${toolPersonaId ?? "none"}: ${tools.length} tool(s) attached`
|
|
728
737
|
);
|
|
@@ -15,22 +15,56 @@ import type { DedupPromptData } from "./types.js";
|
|
|
15
15
|
export function buildDedupPrompt(data: DedupPromptData): { system: string; user: string } {
|
|
16
16
|
const typeLabel = data.itemType.charAt(0).toUpperCase() + data.itemType.slice(1);
|
|
17
17
|
|
|
18
|
-
const system =
|
|
18
|
+
const system = `## HARD RULES (Non-Negotiable — Override All Other Instructions)
|
|
19
|
+
|
|
20
|
+
You are working with Opus 4.6 constraints. These rules prevent overthinking and ensure decisive action:
|
|
21
|
+
|
|
22
|
+
### 1. TOOL BUDGET
|
|
23
|
+
- You have **6 \`read_memory\` calls** for this cluster
|
|
24
|
+
- Prioritize: verify ambiguous relationships > check parent concepts > validate new entities
|
|
25
|
+
- After 6 calls, make decisions with available information
|
|
26
|
+
- Do NOT waste calls re-checking pairs you already examined
|
|
27
|
+
|
|
28
|
+
### 2. SATISFICING MODE (Good Enough > Perfect)
|
|
29
|
+
- If two items share **85%+ semantic similarity** on core meaning → merge them
|
|
30
|
+
- Do NOT re-examine after deciding to merge
|
|
31
|
+
- Do NOT explore alternative groupings
|
|
32
|
+
- First valid match wins — stop searching for "better" options
|
|
33
|
+
|
|
34
|
+
### 3. FORBIDDEN PATTERNS (Signs of Overthinking)
|
|
35
|
+
If you find yourself writing these phrases, **STOP IMMEDIATELY**:
|
|
36
|
+
- ❌ "On the other hand..." / "However, there's another angle..."
|
|
37
|
+
- ❌ "Let me reconsider..." / "But what if..."
|
|
38
|
+
- ❌ "This could be interpreted as..."
|
|
39
|
+
- ❌ Re-analyzing the same pair after making a decision
|
|
40
|
+
|
|
41
|
+
Output format when you catch overthinking:
|
|
42
|
+
\`\`\`
|
|
43
|
+
[OVERTHINKING DETECTED]
|
|
44
|
+
Decision: [Yes/No to merge]
|
|
45
|
+
Reason: [1 sentence]
|
|
46
|
+
\`\`\`
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## YOUR TASK
|
|
51
|
+
|
|
52
|
+
You are acting as the curator for a user's internal database. You have been given a cluster of ${typeLabel} records that our system believes may be duplicates (based on semantic similarity >= 0.90).
|
|
19
53
|
|
|
20
54
|
**YOUR PRIME DIRECTIVE IS TO LOSE _NO_ DATA.**
|
|
21
55
|
|
|
22
56
|
Your secondary directive is to ORGANIZE IT into small, non-repetitive components. The user NEEDS the data, but the data is used by AI agents, so duplication limits usefulness—agents waste tokens re-reading the same information under different names.
|
|
23
57
|
|
|
24
|
-
You have access to a tool called \`read_memory\`
|
|
58
|
+
You have access to a tool called \`read_memory\` (6 calls max — see HARD RULES above). Use it strategically to verify relationships, check for related records, or gather context before making merge decisions.
|
|
25
59
|
|
|
26
|
-
|
|
27
|
-
1. **Identify true duplicates**: Examine each record. Are these genuinely the same thing with different wording, or are they distinct but related concepts?
|
|
60
|
+
### Decision Process:
|
|
61
|
+
1. **Identify true duplicates**: Examine each record. Are these genuinely the same thing with different wording (85%+ core meaning overlap), or are they distinct but related concepts?
|
|
28
62
|
2. **Merge where appropriate**: For TRUE duplicates, consolidate all unique information into ONE canonical record. Pick the best "name" (most descriptive, most commonly used). Merge all descriptions—every unique detail must be preserved.
|
|
29
63
|
3. **Keep distinct concepts separate**: Similar ≠ duplicate. "Software Engineering" and "Software Architecture" may be related but are NOT the same. "Job at Company X" and "Profession: Software Engineer" are related but distinct. Do NOT merge these.
|
|
30
64
|
4. **Track what was merged**: For removed records, indicate which record absorbed their data (via "replaced_by" field).
|
|
31
65
|
5. **Add new records if needed**: If consolidating reveals a MISSING intermediate concept (e.g., merging "Python Developer" and "Backend Engineer" reveals we're missing "Software Engineering" as a parent topic), create it.
|
|
32
66
|
|
|
33
|
-
|
|
67
|
+
### Output Format:
|
|
34
68
|
{
|
|
35
69
|
"update": [
|
|
36
70
|
/* Full ${typeLabel} record payloads with all fields preserved */
|
|
@@ -53,14 +87,14 @@ Record format for "${typeLabel}" (based on type):
|
|
|
53
87
|
|
|
54
88
|
${buildRecordFormatExamples(data.itemType)}
|
|
55
89
|
|
|
56
|
-
Rules:
|
|
90
|
+
### Rules:
|
|
57
91
|
- Do NOT invent information. Only redistribute what exists in the cluster.
|
|
58
92
|
- Descriptions should be concise—ideally under 300 characters, never over 500.
|
|
59
93
|
- Preserve all numeric values (sentiment, strength, confidence, exposure, etc.) from source records. When merging, take the HIGHER value for strength/confidence, AVERAGE for sentiment.
|
|
60
94
|
- Every removed record MUST have "replaced_by" pointing to the canonical record that absorbed its data.
|
|
61
95
|
- The "update" array should contain AT LEAST ONE record (the canonical/merged one), even if all others are removed.
|
|
62
96
|
- If records are NOT duplicates (just similar), return them ALL in "update" unchanged, with empty "remove" and "add" arrays.
|
|
63
|
-
- Use \`read_memory\` to check for related records or gather context before making irreversible merge decisions.`;
|
|
97
|
+
- Use \`read_memory\` strategically (6 calls max) to check for related records or gather context before making irreversible merge decisions.`;
|
|
64
98
|
|
|
65
99
|
const user = JSON.stringify({
|
|
66
100
|
cluster: data.cluster.map(stripEmbedding),
|
package/tui/src/app.tsx
CHANGED
|
@@ -10,23 +10,25 @@ import { StatusBar } from "./components/StatusBar";
|
|
|
10
10
|
import { Show } from "solid-js";
|
|
11
11
|
import { useEi } from "./context/ei";
|
|
12
12
|
import { WelcomeOverlay } from "./components/WelcomeOverlay";
|
|
13
|
+
import { useRenderer } from "@opentui/solid";
|
|
13
14
|
|
|
14
15
|
function AppContent() {
|
|
15
|
-
const { overlayRenderer,
|
|
16
|
+
const { overlayRenderer, showOverlay } = useOverlay();
|
|
16
17
|
const { showWelcomeOverlay, dismissWelcomeOverlay } = useEi();
|
|
17
|
-
|
|
18
|
+
const renderer = useRenderer();
|
|
18
19
|
// Show welcome overlay when LLM detection determines no provider is configured
|
|
19
20
|
createEffect(() => {
|
|
20
21
|
if (showWelcomeOverlay()) {
|
|
21
|
-
showOverlay((onDismiss) => (
|
|
22
|
+
showOverlay((onDismiss, _hideForEditor) => (
|
|
22
23
|
<WelcomeOverlay onDismiss={() => {
|
|
23
24
|
dismissWelcomeOverlay();
|
|
24
25
|
onDismiss();
|
|
25
26
|
}} />
|
|
26
|
-
));
|
|
27
|
+
), renderer);
|
|
27
28
|
}
|
|
28
29
|
});
|
|
29
30
|
|
|
31
|
+
|
|
30
32
|
return (
|
|
31
33
|
<box flexDirection="column" width="100%" height="100%">
|
|
32
34
|
<Layout
|
|
@@ -36,7 +38,7 @@ function AppContent() {
|
|
|
36
38
|
/>
|
|
37
39
|
<StatusBar />
|
|
38
40
|
<Show when={overlayRenderer()}>
|
|
39
|
-
{overlayRenderer()!(
|
|
41
|
+
{overlayRenderer()!()}
|
|
40
42
|
</Show>
|
|
41
43
|
</box>
|
|
42
44
|
);
|
|
@@ -16,7 +16,7 @@ export const archiveCommand: Command = {
|
|
|
16
16
|
ctx.showNotification("No archived personas", "info");
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
-
ctx.showOverlay((hideOverlay) => (
|
|
19
|
+
ctx.showOverlay((hideOverlay, _hideForEditor) => (
|
|
20
20
|
<PersonaListOverlay
|
|
21
21
|
personas={archived}
|
|
22
22
|
activePersonaId={null}
|
|
@@ -30,7 +30,7 @@ export const archiveCommand: Command = {
|
|
|
30
30
|
}}
|
|
31
31
|
onDismiss={hideOverlay}
|
|
32
32
|
/>
|
|
33
|
-
));
|
|
33
|
+
), ctx.renderer);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -90,12 +90,12 @@ export const contextCommand: Command = {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
93
|
-
ctx.showOverlay((hideOverlay) => (
|
|
93
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
94
94
|
<ConfirmOverlay
|
|
95
95
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
96
96
|
onConfirm={() => {
|
|
97
97
|
logger.debug("[context] user confirmed re-edit");
|
|
98
|
-
|
|
98
|
+
hideForEditor();
|
|
99
99
|
resolve(true);
|
|
100
100
|
}}
|
|
101
101
|
onCancel={() => {
|
|
@@ -104,7 +104,7 @@ export const contextCommand: Command = {
|
|
|
104
104
|
resolve(false);
|
|
105
105
|
}}
|
|
106
106
|
/>
|
|
107
|
-
));
|
|
107
|
+
), ctx.renderer);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
110
|
logger.debug("[context] shouldReEdit", { shouldReEdit, iteration: editorIteration });
|
|
@@ -112,7 +112,6 @@ export const contextCommand: Command = {
|
|
|
112
112
|
if (shouldReEdit) {
|
|
113
113
|
yamlContent = result.content;
|
|
114
114
|
logger.debug("[context] continuing to next iteration");
|
|
115
|
-
await new Promise((r) => setTimeout(r, 50));
|
|
116
115
|
continue;
|
|
117
116
|
} else {
|
|
118
117
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -14,13 +14,13 @@ export const deleteCommand: Command = {
|
|
|
14
14
|
|
|
15
15
|
const confirmAndDelete = async (personaId: string, displayName: string) => {
|
|
16
16
|
const confirmed = await new Promise<boolean>((resolve) => {
|
|
17
|
-
ctx.showOverlay((hideOverlay) => (
|
|
17
|
+
ctx.showOverlay((hideOverlay, _hideForEditor) => (
|
|
18
18
|
<ConfirmOverlay
|
|
19
19
|
message={`Delete "${displayName}"?\nThis cannot be undone.`}
|
|
20
20
|
onConfirm={() => { hideOverlay(); resolve(true); }}
|
|
21
21
|
onCancel={() => { hideOverlay(); resolve(false); }}
|
|
22
22
|
/>
|
|
23
|
-
));
|
|
23
|
+
), ctx.renderer);
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
if (confirmed) {
|
|
@@ -36,7 +36,7 @@ export const deleteCommand: Command = {
|
|
|
36
36
|
ctx.showNotification("No personas available to delete", "info");
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
ctx.showOverlay((hideOverlay) => (
|
|
39
|
+
ctx.showOverlay((hideOverlay, _hideForEditor) => (
|
|
40
40
|
<PersonaListOverlay
|
|
41
41
|
personas={deletable}
|
|
42
42
|
activePersonaId={null}
|
|
@@ -48,7 +48,7 @@ export const deleteCommand: Command = {
|
|
|
48
48
|
}}
|
|
49
49
|
onDismiss={hideOverlay}
|
|
50
50
|
/>
|
|
51
|
-
));
|
|
51
|
+
), ctx.renderer);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
|
package/tui/src/commands/dlq.ts
CHANGED
|
@@ -52,18 +52,17 @@ export const dlqCommand: Command = {
|
|
|
52
52
|
const errorMsg = parseError instanceof Error ? parseError.message : String(parseError);
|
|
53
53
|
|
|
54
54
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
55
|
-
ctx.showOverlay((hideOverlay) =>
|
|
55
|
+
ctx.showOverlay((hideOverlay, hideForEditor) =>
|
|
56
56
|
ConfirmOverlay({
|
|
57
57
|
message: `YAML error:\n${errorMsg}\n\nRe-edit?`,
|
|
58
|
-
onConfirm: () => {
|
|
58
|
+
onConfirm: () => { hideForEditor(); resolve(true); },
|
|
59
59
|
onCancel: () => { hideOverlay(); resolve(false); },
|
|
60
60
|
})
|
|
61
|
-
);
|
|
61
|
+
, ctx.renderer);
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
if (shouldReEdit) {
|
|
65
65
|
yamlContent = result.content;
|
|
66
|
-
await new Promise(r => setTimeout(r, 50));
|
|
67
66
|
continue;
|
|
68
67
|
}
|
|
69
68
|
|
|
@@ -7,6 +7,6 @@ export const helpCommand: Command = {
|
|
|
7
7
|
description: "Show help screen",
|
|
8
8
|
usage: "/help or /h",
|
|
9
9
|
execute: async (_args, ctx) => {
|
|
10
|
-
ctx.showOverlay((
|
|
10
|
+
ctx.showOverlay((_hideOverlay, _hideForEditor) => <HelpOverlay onDismiss={_hideOverlay} />, ctx.renderer);
|
|
11
11
|
},
|
|
12
12
|
};
|
package/tui/src/commands/me.tsx
CHANGED
|
@@ -112,12 +112,12 @@ export const meCommand: Command = {
|
|
|
112
112
|
logger.debug("[me] YAML parse error, prompting for re-edit", { iteration: editorIteration, error: errorMsg });
|
|
113
113
|
|
|
114
114
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
115
|
-
ctx.showOverlay((hideOverlay) => (
|
|
115
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
116
116
|
<ConfirmOverlay
|
|
117
117
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
118
118
|
onConfirm={() => {
|
|
119
119
|
logger.debug("[me] user confirmed re-edit");
|
|
120
|
-
|
|
120
|
+
hideForEditor();
|
|
121
121
|
resolve(true);
|
|
122
122
|
}}
|
|
123
123
|
onCancel={() => {
|
|
@@ -126,7 +126,7 @@ export const meCommand: Command = {
|
|
|
126
126
|
resolve(false);
|
|
127
127
|
}}
|
|
128
128
|
/>
|
|
129
|
-
));
|
|
129
|
+
), ctx.renderer);
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
logger.debug("[me] shouldReEdit", { shouldReEdit, iteration: editorIteration });
|
|
@@ -134,7 +134,6 @@ export const meCommand: Command = {
|
|
|
134
134
|
if (shouldReEdit) {
|
|
135
135
|
yamlContent = result.content;
|
|
136
136
|
logger.debug("[me] continuing to next iteration");
|
|
137
|
-
await new Promise(r => setTimeout(r, 50));
|
|
138
137
|
continue;
|
|
139
138
|
} else {
|
|
140
139
|
ctx.showNotification("Changes discarded", "info");
|
|
@@ -13,7 +13,7 @@ export const personaCommand: Command = {
|
|
|
13
13
|
const unarchived = ctx.ei.personas().filter(p => !p.is_archived);
|
|
14
14
|
|
|
15
15
|
if (args.length === 0) {
|
|
16
|
-
ctx.showOverlay((hideOverlay) => (
|
|
16
|
+
ctx.showOverlay((hideOverlay, _hideForEditor) => (
|
|
17
17
|
<PersonaListOverlay
|
|
18
18
|
personas={unarchived}
|
|
19
19
|
activePersonaId={ctx.ei.activePersonaId()}
|
|
@@ -25,7 +25,7 @@ export const personaCommand: Command = {
|
|
|
25
25
|
}}
|
|
26
26
|
onDismiss={hideOverlay}
|
|
27
27
|
/>
|
|
28
|
-
));
|
|
28
|
+
), ctx.renderer);
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -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) {
|
|
@@ -54,11 +54,11 @@ export const settingsCommand: Command = {
|
|
|
54
54
|
logger.debug("[settings] YAML parse error, prompting for re-edit", { iteration: editorIteration, error: errorMsg });
|
|
55
55
|
|
|
56
56
|
const shouldReEdit = await new Promise<boolean>((resolve) => {
|
|
57
|
-
ctx.showOverlay((hideOverlay) => (
|
|
57
|
+
ctx.showOverlay((hideOverlay, hideForEditor) => (
|
|
58
58
|
<ConfirmOverlay
|
|
59
59
|
message={`YAML parse error:\n${errorMsg}\n\nRe-edit?`}
|
|
60
60
|
onConfirm={() => {
|
|
61
|
-
|
|
61
|
+
hideForEditor();
|
|
62
62
|
resolve(true);
|
|
63
63
|
}}
|
|
64
64
|
onCancel={() => {
|
|
@@ -66,12 +66,11 @@ export const settingsCommand: Command = {
|
|
|
66
66
|
resolve(false);
|
|
67
67
|
}}
|
|
68
68
|
/>
|
|
69
|
-
));
|
|
69
|
+
), ctx.renderer);
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
if (shouldReEdit) {
|
|
73
73
|
yamlContent = result.content;
|
|
74
|
-
await new Promise(r => setTimeout(r, 50));
|
|
75
74
|
continue;
|
|
76
75
|
} else {
|
|
77
76
|
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
|
};
|
|
@@ -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");
|