ei-tui 1.6.9 → 1.7.1
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 +4 -0
- package/package.json +2 -1
- package/skills/coding-harness-reflect/SKILL.md +230 -0
- package/skills/ei-curate/SKILL.md +174 -0
- package/skills/ei-curate/references/cli.md +160 -0
- package/skills/ei-curate/references/provenance.md +88 -0
- package/skills/ei-curate/references/recipes.md +144 -0
- package/skills/ei-curate/references/talking-to-the-user.md +71 -0
- package/skills/ei-persona/SKILL.md +238 -0
- package/skills/ei-persona/references/cli.md +265 -0
- package/skills/ei-persona/references/recipes.md +247 -0
- package/skills/ei-persona/references/talking-to-the-user.md +107 -0
- package/src/cli/README.md +72 -2
- package/src/cli/corrections-endpoints.ts +297 -0
- package/src/cli/corrections-writer.ts +138 -0
- package/src/cli/install.ts +117 -25
- package/src/cli/mcp.ts +80 -1
- package/src/cli/persona-corrections.ts +442 -0
- package/src/cli/retrieval.ts +46 -2
- package/src/cli.ts +131 -1
- package/src/core/corrections.ts +233 -0
- package/src/core/handlers/human-extraction.ts +64 -15
- package/src/core/handlers/human-matching.ts +2 -2
- package/src/core/orchestrators/human-extraction.ts +1 -11
- package/src/core/persona-manager.ts +18 -7
- package/src/core/persona-tools.ts +92 -0
- package/src/core/processor.ts +113 -1
- package/src/core/state/human.ts +10 -0
- package/src/core/state/personas.ts +8 -0
- package/src/core/state-manager.ts +11 -0
- package/src/core/utils/identifier-utils.ts +3 -2
- package/src/prompts/human/person-scan.ts +2 -0
- package/src/prompts/human/person-update.ts +14 -3
- package/src/storage/file-lock.ts +120 -0
- package/tui/README.md +2 -0
- package/tui/src/commands/provider.tsx +1 -1
- package/tui/src/components/WelcomeOverlay.tsx +3 -3
- package/tui/src/context/ei.tsx +14 -0
- package/tui/src/index.tsx +13 -1
- package/tui/src/storage/file.ts +15 -83
- package/tui/src/util/instance-lock.ts +3 -2
- package/tui/src/util/yaml-persona.ts +7 -38
package/tui/src/storage/file.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { StorageState } from "../../../src/core/types";
|
|
2
2
|
import type { Storage } from "../../../src/storage/interface";
|
|
3
3
|
import { encodeAllEmbeddings, decodeAllEmbeddings } from "../../../src/storage/embeddings";
|
|
4
|
+
import { withLock, atomicWrite } from "../../../src/storage/file-lock";
|
|
4
5
|
import { join } from "path";
|
|
5
6
|
import { mkdir, rename, unlink, readdir } from "fs/promises";
|
|
6
7
|
import { resolveDataPath } from "../util/resolve-data-path.js";
|
|
@@ -8,8 +9,6 @@ import { resolveDataPath } from "../util/resolve-data-path.js";
|
|
|
8
9
|
const STATE_FILE = "state.json";
|
|
9
10
|
const BACKUP_FILE = "state.backup.json";
|
|
10
11
|
const BACKUPS_DIR = "backups";
|
|
11
|
-
const LOCK_TIMEOUT_MS = 5000;
|
|
12
|
-
const LOCK_RETRY_DELAY_MS = 50;
|
|
13
12
|
|
|
14
13
|
export class FileStorage implements Storage {
|
|
15
14
|
private readonly dataPath: string;
|
|
@@ -39,9 +38,9 @@ export class FileStorage implements Storage {
|
|
|
39
38
|
const filePath = join(this.dataPath, STATE_FILE);
|
|
40
39
|
state.timestamp = new Date().toISOString();
|
|
41
40
|
|
|
42
|
-
await
|
|
41
|
+
await withLock(filePath, async () => {
|
|
43
42
|
try {
|
|
44
|
-
await
|
|
43
|
+
await atomicWrite(filePath, JSON.stringify(encodeAllEmbeddings(state), null, 2));
|
|
45
44
|
} catch (e) {
|
|
46
45
|
if (this.isQuotaError(e)) {
|
|
47
46
|
throw new Error("STORAGE_SAVE_FAILED: Disk quota exceeded");
|
|
@@ -89,7 +88,7 @@ export class FileStorage implements Storage {
|
|
|
89
88
|
async saveBackup(state: StorageState): Promise<void> {
|
|
90
89
|
await this.ensureDataDir();
|
|
91
90
|
const backupPath = join(this.dataPath, BACKUP_FILE);
|
|
92
|
-
await
|
|
91
|
+
await atomicWrite(backupPath, JSON.stringify(encodeAllEmbeddings(state), null, 2));
|
|
93
92
|
}
|
|
94
93
|
|
|
95
94
|
/**
|
|
@@ -130,7 +129,7 @@ export class FileStorage implements Storage {
|
|
|
130
129
|
].join("") + ".json";
|
|
131
130
|
|
|
132
131
|
const destPath = join(backupsPath, name);
|
|
133
|
-
await
|
|
132
|
+
await atomicWrite(destPath, JSON.stringify(encodeAllEmbeddings(state), null, 2));
|
|
134
133
|
|
|
135
134
|
// Prune: keep only the newest maxBackups files
|
|
136
135
|
const entries = await readdir(backupsPath);
|
|
@@ -149,8 +148,16 @@ export class FileStorage implements Storage {
|
|
|
149
148
|
private async ensureDataDir(): Promise<void> {
|
|
150
149
|
try {
|
|
151
150
|
await mkdir(this.dataPath, { recursive: true });
|
|
152
|
-
} catch {
|
|
153
|
-
|
|
151
|
+
} catch (e: any) {
|
|
152
|
+
if (e?.code === 'EACCES' || e?.code === 'EPERM') {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Cannot create data directory: ${this.dataPath}\n` +
|
|
155
|
+
`Fix options:\n` +
|
|
156
|
+
` - Fix Permissions (sudo chown $USER $EI_DATA_PATH)\n` +
|
|
157
|
+
` - Change Data Path (EI_DATA_PATH=~/ei-data ei-tui)`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
// Other errors (e.g., race conditions) are safe to ignore
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
|
|
@@ -160,79 +167,4 @@ export class FileStorage implements Storage {
|
|
|
160
167
|
(e.message.includes("ENOSPC") || e.message.includes("quota"))
|
|
161
168
|
);
|
|
162
169
|
}
|
|
163
|
-
|
|
164
|
-
private async atomicWrite(filePath: string, content: string): Promise<void> {
|
|
165
|
-
const tempPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
166
|
-
try {
|
|
167
|
-
await Bun.write(tempPath, content);
|
|
168
|
-
await rename(tempPath, filePath);
|
|
169
|
-
} catch (e) {
|
|
170
|
-
try {
|
|
171
|
-
await unlink(tempPath);
|
|
172
|
-
} catch {}
|
|
173
|
-
throw e;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private getLockPath(filePath: string): string {
|
|
178
|
-
return `${filePath}.lock`;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private async acquireLock(filePath: string): Promise<boolean> {
|
|
182
|
-
const lockPath = this.getLockPath(filePath);
|
|
183
|
-
const startTime = Date.now();
|
|
184
|
-
|
|
185
|
-
while (Date.now() - startTime < LOCK_TIMEOUT_MS) {
|
|
186
|
-
const lockFile = Bun.file(lockPath);
|
|
187
|
-
if (await lockFile.exists()) {
|
|
188
|
-
// Read may throw if another writer deleted the lock between exists() and text() —
|
|
189
|
-
// treat that as "lock is gone, proceed to acquire" by falling through.
|
|
190
|
-
let lockContent: string;
|
|
191
|
-
try {
|
|
192
|
-
lockContent = await lockFile.text();
|
|
193
|
-
} catch {
|
|
194
|
-
// Lock vanished in the race window — retry from top to re-check state cleanly.
|
|
195
|
-
await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAY_MS));
|
|
196
|
-
continue;
|
|
197
|
-
}
|
|
198
|
-
const lockTime = parseInt(lockContent, 10);
|
|
199
|
-
if (!isNaN(lockTime) && Date.now() - lockTime > LOCK_TIMEOUT_MS) {
|
|
200
|
-
try {
|
|
201
|
-
await unlink(lockPath);
|
|
202
|
-
} catch {}
|
|
203
|
-
} else {
|
|
204
|
-
await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAY_MS));
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
await Bun.write(lockPath, Date.now().toString());
|
|
211
|
-
return true;
|
|
212
|
-
} catch {
|
|
213
|
-
await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAY_MS));
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private async releaseLock(filePath: string): Promise<void> {
|
|
221
|
-
const lockPath = this.getLockPath(filePath);
|
|
222
|
-
try {
|
|
223
|
-
await unlink(lockPath);
|
|
224
|
-
} catch {}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private async withLock<T>(filePath: string, fn: () => Promise<T>): Promise<T> {
|
|
228
|
-
const acquired = await this.acquireLock(filePath);
|
|
229
|
-
if (!acquired) {
|
|
230
|
-
throw new Error("STORAGE_LOCK_TIMEOUT: Could not acquire file lock");
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
return await fn();
|
|
234
|
-
} finally {
|
|
235
|
-
await this.releaseLock(filePath);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
170
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { unlink } from "fs/promises";
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
|
+
import { unlink, mkdir } from "fs/promises";
|
|
3
3
|
|
|
4
4
|
const LOCK_FILE = "ei.lock";
|
|
5
5
|
|
|
@@ -69,6 +69,7 @@ export class InstanceLock {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
private async writeLock(): Promise<void> {
|
|
72
|
+
await mkdir(dirname(this.lockPath), { recursive: true });
|
|
72
73
|
const data: LockData = {
|
|
73
74
|
pid: process.pid,
|
|
74
75
|
started: new Date().toISOString(),
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
ProviderAccount,
|
|
8
8
|
} from "../../../src/core/types.js";
|
|
9
9
|
import { modelGuidToDisplay, displayToModelGuid } from "./yaml-shared.js";
|
|
10
|
+
import { buildPersonaToolsMap, resolvePersonaToolsFromMap, preserveHiddenToolGrants } from "../../../src/core/persona-tools.js";
|
|
10
11
|
import { parseDuration, formatDuration } from "./duration.js";
|
|
11
12
|
|
|
12
13
|
const PLACEHOLDER_LONG_DESC = "Detailed description of this persona's personality, background, and role";
|
|
@@ -62,43 +63,6 @@ const PLACEHOLDER_TOPIC: YAMLPersonaTopic = {
|
|
|
62
63
|
exposure_desired: 0.5,
|
|
63
64
|
};
|
|
64
65
|
|
|
65
|
-
function buildPersonaToolsMap(
|
|
66
|
-
enabledToolIds: string[],
|
|
67
|
-
allTools: ToolDefinition[],
|
|
68
|
-
allProviders: import('../../../src/core/types.js').ToolProvider[]
|
|
69
|
-
): Record<string, Record<string, boolean>> | undefined {
|
|
70
|
-
if (allTools.length === 0) return undefined;
|
|
71
|
-
const enabledSet = new Set(enabledToolIds);
|
|
72
|
-
const result: Record<string, Record<string, boolean>> = {};
|
|
73
|
-
for (const provider of allProviders.filter(p => p.enabled)) {
|
|
74
|
-
const providerTools = allTools.filter(t => t.provider_id === provider.id);
|
|
75
|
-
if (providerTools.length === 0) continue;
|
|
76
|
-
result[provider.display_name] = Object.fromEntries(
|
|
77
|
-
providerTools.map(t => [t.display_name, enabledSet.has(t.id)])
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
return Object.keys(result).length > 0 ? result : undefined;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function resolvePersonaToolsFromMap(
|
|
84
|
-
toolsMap: Record<string, Record<string, boolean>> | undefined,
|
|
85
|
-
allTools: ToolDefinition[],
|
|
86
|
-
allProviders: import('../../../src/core/types.js').ToolProvider[]
|
|
87
|
-
): string[] | undefined {
|
|
88
|
-
if (!toolsMap) return undefined;
|
|
89
|
-
const enabledIds: string[] = [];
|
|
90
|
-
for (const [providerDisplayName, toolToggles] of Object.entries(toolsMap)) {
|
|
91
|
-
const provider = allProviders.find(p => p.display_name === providerDisplayName);
|
|
92
|
-
if (!provider) continue;
|
|
93
|
-
for (const [toolDisplayName, enabled] of Object.entries(toolToggles)) {
|
|
94
|
-
if (!enabled) continue;
|
|
95
|
-
const tool = allTools.find(t => t.provider_id === provider.id && t.display_name === toolDisplayName);
|
|
96
|
-
if (tool) enabledIds.push(tool.id);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return enabledIds.length > 0 ? enabledIds : [];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
66
|
export function newPersonaToYAML(name: string, allTools?: ToolDefinition[], allProviders?: import('../../../src/core/types.js').ToolProvider[]): string {
|
|
103
67
|
const toolsMap = buildPersonaToolsMap([], allTools ?? [], allProviders ?? []);
|
|
104
68
|
|
|
@@ -337,7 +301,12 @@ export function personaFromYAML(yamlContent: string, original: PersonaEntity, al
|
|
|
337
301
|
pause_until: data.pause_until,
|
|
338
302
|
is_static: data.is_static ?? false,
|
|
339
303
|
include_message_timestamps: data.include_message_timestamps ?? false,
|
|
340
|
-
tools:
|
|
304
|
+
tools: preserveHiddenToolGrants(
|
|
305
|
+
resolvePersonaToolsFromMap(data.tools, allTools ?? [], allProviders ?? []),
|
|
306
|
+
original.tools,
|
|
307
|
+
allTools ?? [],
|
|
308
|
+
allProviders ?? []
|
|
309
|
+
),
|
|
341
310
|
last_updated: new Date().toISOString(),
|
|
342
311
|
};
|
|
343
312
|
|