ei-tui 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/package.json +63 -0
  4. package/src/README.md +96 -0
  5. package/src/cli/README.md +47 -0
  6. package/src/cli/commands/facts.ts +25 -0
  7. package/src/cli/commands/people.ts +25 -0
  8. package/src/cli/commands/quotes.ts +19 -0
  9. package/src/cli/commands/topics.ts +25 -0
  10. package/src/cli/commands/traits.ts +25 -0
  11. package/src/cli/retrieval.ts +269 -0
  12. package/src/cli.ts +176 -0
  13. package/src/core/AGENTS.md +104 -0
  14. package/src/core/embedding-service.ts +241 -0
  15. package/src/core/handlers/index.ts +1057 -0
  16. package/src/core/index.ts +4 -0
  17. package/src/core/llm-client.ts +265 -0
  18. package/src/core/model-context-windows.ts +49 -0
  19. package/src/core/orchestrators/ceremony.ts +500 -0
  20. package/src/core/orchestrators/extraction-chunker.ts +138 -0
  21. package/src/core/orchestrators/human-extraction.ts +457 -0
  22. package/src/core/orchestrators/index.ts +28 -0
  23. package/src/core/orchestrators/persona-generation.ts +76 -0
  24. package/src/core/orchestrators/persona-topics.ts +117 -0
  25. package/src/core/personas/index.ts +5 -0
  26. package/src/core/personas/opencode-agent.ts +81 -0
  27. package/src/core/processor.ts +1413 -0
  28. package/src/core/queue-processor.ts +197 -0
  29. package/src/core/state/checkpoints.ts +68 -0
  30. package/src/core/state/human.ts +176 -0
  31. package/src/core/state/index.ts +5 -0
  32. package/src/core/state/personas.ts +217 -0
  33. package/src/core/state/queue.ts +144 -0
  34. package/src/core/state-manager.ts +347 -0
  35. package/src/core/types.ts +421 -0
  36. package/src/core/utils/decay.ts +33 -0
  37. package/src/index.ts +1 -0
  38. package/src/integrations/opencode/importer.ts +896 -0
  39. package/src/integrations/opencode/index.ts +16 -0
  40. package/src/integrations/opencode/json-reader.ts +304 -0
  41. package/src/integrations/opencode/reader-factory.ts +35 -0
  42. package/src/integrations/opencode/sqlite-reader.ts +189 -0
  43. package/src/integrations/opencode/types.ts +244 -0
  44. package/src/prompts/AGENTS.md +62 -0
  45. package/src/prompts/ceremony/description-check.ts +47 -0
  46. package/src/prompts/ceremony/expire.ts +30 -0
  47. package/src/prompts/ceremony/explore.ts +60 -0
  48. package/src/prompts/ceremony/index.ts +11 -0
  49. package/src/prompts/ceremony/types.ts +42 -0
  50. package/src/prompts/generation/descriptions.ts +91 -0
  51. package/src/prompts/generation/index.ts +15 -0
  52. package/src/prompts/generation/persona.ts +155 -0
  53. package/src/prompts/generation/seeds.ts +31 -0
  54. package/src/prompts/generation/types.ts +47 -0
  55. package/src/prompts/heartbeat/check.ts +179 -0
  56. package/src/prompts/heartbeat/ei.ts +208 -0
  57. package/src/prompts/heartbeat/index.ts +15 -0
  58. package/src/prompts/heartbeat/types.ts +70 -0
  59. package/src/prompts/human/fact-scan.ts +152 -0
  60. package/src/prompts/human/index.ts +32 -0
  61. package/src/prompts/human/item-match.ts +74 -0
  62. package/src/prompts/human/item-update.ts +322 -0
  63. package/src/prompts/human/person-scan.ts +115 -0
  64. package/src/prompts/human/topic-scan.ts +135 -0
  65. package/src/prompts/human/trait-scan.ts +115 -0
  66. package/src/prompts/human/types.ts +127 -0
  67. package/src/prompts/index.ts +90 -0
  68. package/src/prompts/message-utils.ts +39 -0
  69. package/src/prompts/persona/index.ts +16 -0
  70. package/src/prompts/persona/topics-match.ts +69 -0
  71. package/src/prompts/persona/topics-scan.ts +98 -0
  72. package/src/prompts/persona/topics-update.ts +157 -0
  73. package/src/prompts/persona/traits.ts +117 -0
  74. package/src/prompts/persona/types.ts +74 -0
  75. package/src/prompts/response/index.ts +147 -0
  76. package/src/prompts/response/sections.ts +355 -0
  77. package/src/prompts/response/types.ts +38 -0
  78. package/src/prompts/validation/ei.ts +93 -0
  79. package/src/prompts/validation/index.ts +6 -0
  80. package/src/prompts/validation/types.ts +22 -0
  81. package/src/storage/crypto.ts +96 -0
  82. package/src/storage/index.ts +5 -0
  83. package/src/storage/interface.ts +9 -0
  84. package/src/storage/local.ts +79 -0
  85. package/src/storage/merge.ts +69 -0
  86. package/src/storage/remote.ts +145 -0
  87. package/src/templates/welcome.ts +91 -0
  88. package/tui/README.md +62 -0
  89. package/tui/bunfig.toml +4 -0
  90. package/tui/src/app.tsx +55 -0
  91. package/tui/src/commands/archive.tsx +93 -0
  92. package/tui/src/commands/context.tsx +124 -0
  93. package/tui/src/commands/delete.tsx +71 -0
  94. package/tui/src/commands/details.tsx +41 -0
  95. package/tui/src/commands/editor.tsx +46 -0
  96. package/tui/src/commands/help.tsx +12 -0
  97. package/tui/src/commands/me.tsx +145 -0
  98. package/tui/src/commands/model.ts +47 -0
  99. package/tui/src/commands/new.ts +31 -0
  100. package/tui/src/commands/pause.ts +46 -0
  101. package/tui/src/commands/persona.tsx +58 -0
  102. package/tui/src/commands/provider.tsx +124 -0
  103. package/tui/src/commands/quit.ts +22 -0
  104. package/tui/src/commands/quotes.tsx +172 -0
  105. package/tui/src/commands/registry.test.ts +137 -0
  106. package/tui/src/commands/registry.ts +130 -0
  107. package/tui/src/commands/resume.ts +39 -0
  108. package/tui/src/commands/setsync.tsx +43 -0
  109. package/tui/src/commands/settings.tsx +83 -0
  110. package/tui/src/components/ConfirmOverlay.tsx +51 -0
  111. package/tui/src/components/ConflictOverlay.tsx +78 -0
  112. package/tui/src/components/HelpOverlay.tsx +69 -0
  113. package/tui/src/components/Layout.tsx +24 -0
  114. package/tui/src/components/MessageList.tsx +174 -0
  115. package/tui/src/components/PersonaListOverlay.tsx +186 -0
  116. package/tui/src/components/PromptInput.tsx +145 -0
  117. package/tui/src/components/ProviderListOverlay.tsx +208 -0
  118. package/tui/src/components/QuotesOverlay.tsx +157 -0
  119. package/tui/src/components/Sidebar.tsx +95 -0
  120. package/tui/src/components/StatusBar.tsx +77 -0
  121. package/tui/src/components/WelcomeOverlay.tsx +73 -0
  122. package/tui/src/context/ei.tsx +623 -0
  123. package/tui/src/context/keyboard.tsx +164 -0
  124. package/tui/src/context/overlay.tsx +53 -0
  125. package/tui/src/index.tsx +8 -0
  126. package/tui/src/storage/file.ts +185 -0
  127. package/tui/src/util/duration.ts +32 -0
  128. package/tui/src/util/editor.ts +188 -0
  129. package/tui/src/util/logger.ts +109 -0
  130. package/tui/src/util/persona-editor.tsx +181 -0
  131. package/tui/src/util/provider-editor.tsx +168 -0
  132. package/tui/src/util/syntax.ts +35 -0
  133. package/tui/src/util/yaml-serializers.ts +755 -0
@@ -0,0 +1,144 @@
1
+ import type { LLMRequest, LLMNextStep, QueueFailResult } from "../types.js";
2
+
3
+ const BASE_BACKOFF_MS = 2_000;
4
+ const MAX_BACKOFF_MS = 30_000;
5
+
6
+ function extractHTTPStatus(error: string): number | null {
7
+ const match = error.match(/\((\d{3})\)/);
8
+ return match ? parseInt(match[1], 10) : null;
9
+ }
10
+
11
+ function isPermanentError(error: string): boolean {
12
+ const status = extractHTTPStatus(error);
13
+ if (status !== null) {
14
+ // 4xx are permanent EXCEPT 429 (rate limit) and 408 (request timeout)
15
+ return status >= 400 && status < 500 && status !== 429 && status !== 408;
16
+ }
17
+ // Pattern-based fallback for non-HTTP errors
18
+ return /bad request|invalid api key|unauthorized|forbidden/i.test(error);
19
+ }
20
+
21
+ function calculateBackoff(attempts: number): number {
22
+ return Math.min(BASE_BACKOFF_MS * Math.pow(2, attempts - 1), MAX_BACKOFF_MS);
23
+ }
24
+
25
+ export class QueueState {
26
+ private queue: LLMRequest[] = [];
27
+ private paused = false;
28
+
29
+ load(queue: LLMRequest[]): void {
30
+ this.queue = queue;
31
+ }
32
+
33
+ export(): LLMRequest[] {
34
+ return this.queue;
35
+ }
36
+
37
+ enqueue(request: Omit<LLMRequest, "id" | "created_at" | "attempts">): string {
38
+ const id = crypto.randomUUID();
39
+ const fullRequest: LLMRequest = {
40
+ ...request,
41
+ id,
42
+ created_at: new Date().toISOString(),
43
+ attempts: 0,
44
+ };
45
+ this.queue.push(fullRequest);
46
+ return id;
47
+ }
48
+
49
+ peekHighest(): LLMRequest | null {
50
+ if (this.paused || this.queue.length === 0) return null;
51
+ const now = new Date().toISOString();
52
+ const available = this.queue.filter(r => !r.retry_after || r.retry_after <= now);
53
+ if (available.length === 0) return null;
54
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
55
+ const sorted = [...available].sort(
56
+ (a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]
57
+ );
58
+ return sorted[0] ?? null;
59
+ }
60
+
61
+ complete(id: string): void {
62
+ const idx = this.queue.findIndex((r) => r.id === id);
63
+ if (idx >= 0) {
64
+ this.queue.splice(idx, 1);
65
+ }
66
+ }
67
+
68
+ fail(id: string, error?: string, permanent?: boolean): QueueFailResult {
69
+ const idx = this.queue.findIndex((r) => r.id === id);
70
+ if (idx < 0) return { dropped: false };
71
+ const request = this.queue[idx];
72
+ request.attempts++;
73
+ request.last_attempt = new Date().toISOString();
74
+ if (error) {
75
+ request.data._lastError = error;
76
+ }
77
+
78
+ // No error string and not flagged permanent = just increment, no classification
79
+ if (!error && !permanent) {
80
+ return { dropped: false };
81
+ }
82
+
83
+ const shouldDrop = permanent || (error ? isPermanentError(error) : false);
84
+
85
+ if (shouldDrop) {
86
+ this.queue.splice(idx, 1);
87
+ return { dropped: true };
88
+ }
89
+
90
+ // Transient error — apply exponential backoff, never drop
91
+ const delay = calculateBackoff(request.attempts);
92
+ request.retry_after = new Date(Date.now() + delay).toISOString();
93
+ return { dropped: false, retryDelay: delay };
94
+ }
95
+
96
+ getValidations(): LLMRequest[] {
97
+ return this.queue.filter(
98
+ (r) => r.next_step === ("handleEiValidation" as LLMNextStep)
99
+ );
100
+ }
101
+
102
+ clearValidations(ids: string[]): void {
103
+ const idSet = new Set(ids);
104
+ this.queue = this.queue.filter((r) => !idSet.has(r.id));
105
+ }
106
+
107
+ clearPersonaResponses(personaId: string, nextStep: string): string[] {
108
+ const removedIds: string[] = [];
109
+ this.queue = this.queue.filter((r) => {
110
+ if (r.next_step === nextStep && r.data.personaId === personaId) {
111
+ removedIds.push(r.id);
112
+ return false;
113
+ }
114
+ return true;
115
+ });
116
+ return removedIds;
117
+ }
118
+
119
+ length(): number {
120
+ return this.queue.length;
121
+ }
122
+
123
+ pause(): void {
124
+ this.paused = true;
125
+ }
126
+
127
+ resume(): void {
128
+ this.paused = false;
129
+ }
130
+
131
+ isPaused(): boolean {
132
+ return this.paused;
133
+ }
134
+
135
+ hasPendingCeremonies(): boolean {
136
+ return this.queue.some(r => r.data.ceremony_progress === true);
137
+ }
138
+
139
+ clear(): number {
140
+ const count = this.queue.length;
141
+ this.queue = [];
142
+ return count;
143
+ }
144
+ }
@@ -0,0 +1,347 @@
1
+ import type {
2
+ HumanEntity,
3
+ PersonaEntity,
4
+ Message,
5
+ Fact,
6
+ Trait,
7
+ Topic,
8
+ Person,
9
+ Quote,
10
+ LLMRequest,
11
+ StorageState,
12
+ ContextStatus,
13
+ QueueFailResult,
14
+ } from "./types.js";
15
+ import type { Storage } from "../storage/interface.js";
16
+ import {
17
+ HumanState,
18
+ PersonaState,
19
+ QueueState,
20
+ PersistenceState,
21
+ createDefaultHumanEntity,
22
+ } from "./state/index.js";
23
+
24
+ export class StateManager {
25
+ private humanState = new HumanState();
26
+ private personaState = new PersonaState();
27
+ private queueState = new QueueState();
28
+ private persistenceState = new PersistenceState();
29
+
30
+ async initialize(storage: Storage): Promise<void> {
31
+ this.persistenceState.setStorage(storage);
32
+
33
+ const state = await this.persistenceState.load();
34
+
35
+ if (state) {
36
+ this.humanState.load(state.human);
37
+ this.personaState.load(state.personas);
38
+ this.queueState.load(state.queue);
39
+ } else {
40
+ this.humanState.load(createDefaultHumanEntity());
41
+ }
42
+ }
43
+
44
+ private buildStorageState(): StorageState {
45
+ return {
46
+ version: 1,
47
+ timestamp: new Date().toISOString(),
48
+ human: this.humanState.get(),
49
+ personas: this.personaState.export(),
50
+ queue: this.queueState.export(),
51
+ };
52
+ }
53
+
54
+ private scheduleSave(): void {
55
+ this.persistenceState.scheduleSave(this.buildStorageState());
56
+ }
57
+
58
+ getHuman(): HumanEntity {
59
+ return this.humanState.get();
60
+ }
61
+
62
+ setHuman(entity: HumanEntity): void {
63
+ this.humanState.set(entity);
64
+ this.scheduleSave();
65
+ }
66
+
67
+ human_fact_upsert(fact: Fact): void {
68
+ this.humanState.fact_upsert(fact);
69
+ this.scheduleSave();
70
+ }
71
+
72
+ human_fact_remove(id: string): boolean {
73
+ const result = this.humanState.fact_remove(id);
74
+ this.scheduleSave();
75
+ return result;
76
+ }
77
+
78
+ human_trait_upsert(trait: Trait): void {
79
+ this.humanState.trait_upsert(trait);
80
+ this.scheduleSave();
81
+ }
82
+
83
+ human_trait_remove(id: string): boolean {
84
+ const result = this.humanState.trait_remove(id);
85
+ this.scheduleSave();
86
+ return result;
87
+ }
88
+
89
+ human_topic_upsert(topic: Topic): void {
90
+ this.humanState.topic_upsert(topic);
91
+ this.scheduleSave();
92
+ }
93
+
94
+ human_topic_remove(id: string): boolean {
95
+ const result = this.humanState.topic_remove(id);
96
+ this.scheduleSave();
97
+ return result;
98
+ }
99
+
100
+ human_person_upsert(person: Person): void {
101
+ this.humanState.person_upsert(person);
102
+ this.scheduleSave();
103
+ }
104
+
105
+ human_person_remove(id: string): boolean {
106
+ const result = this.humanState.person_remove(id);
107
+ this.scheduleSave();
108
+ return result;
109
+ }
110
+
111
+ human_quote_add(quote: Quote): void {
112
+ this.humanState.quote_add(quote);
113
+ this.scheduleSave();
114
+ }
115
+
116
+ human_quote_update(id: string, updates: Partial<Quote>): boolean {
117
+ const result = this.humanState.quote_update(id, updates);
118
+ this.scheduleSave();
119
+ return result;
120
+ }
121
+
122
+ human_quote_remove(id: string): boolean {
123
+ const result = this.humanState.quote_remove(id);
124
+ this.scheduleSave();
125
+ return result;
126
+ }
127
+
128
+ human_quote_getForMessage(messageId: string): Quote[] {
129
+ return this.humanState.quote_getForMessage(messageId);
130
+ }
131
+
132
+ human_quote_getForDataItem(dataItemId: string): Quote[] {
133
+ return this.humanState.quote_getForDataItem(dataItemId);
134
+ }
135
+
136
+ persona_getAll(): PersonaEntity[] {
137
+ return this.personaState.getAll();
138
+ }
139
+
140
+ persona_getById(personaId: string): PersonaEntity | null {
141
+ return this.personaState.getById(personaId);
142
+ }
143
+
144
+ persona_getByName(nameOrAlias: string): PersonaEntity | null {
145
+ return this.personaState.getByName(nameOrAlias);
146
+ }
147
+
148
+ persona_add(entity: PersonaEntity): void {
149
+ this.personaState.add(entity);
150
+ this.scheduleSave();
151
+ }
152
+
153
+ persona_update(personaId: string, updates: Partial<PersonaEntity>): boolean {
154
+ const result = this.personaState.update(personaId, updates);
155
+ this.scheduleSave();
156
+ return result;
157
+ }
158
+
159
+ persona_archive(personaId: string): boolean {
160
+ const result = this.personaState.archive(personaId);
161
+ this.scheduleSave();
162
+ return result;
163
+ }
164
+
165
+ persona_unarchive(personaId: string): boolean {
166
+ const result = this.personaState.unarchive(personaId);
167
+ this.scheduleSave();
168
+ return result;
169
+ }
170
+
171
+ persona_delete(personaId: string): boolean {
172
+ const result = this.personaState.delete(personaId);
173
+ this.scheduleSave();
174
+ return result;
175
+ }
176
+
177
+ persona_setContextBoundary(personaId: string, timestamp: string | null): void {
178
+ this.personaState.update(personaId, {
179
+ context_boundary: timestamp ?? undefined,
180
+ });
181
+ this.scheduleSave();
182
+ }
183
+
184
+ messages_get(personaId: string): Message[] {
185
+ return this.personaState.messages_get(personaId);
186
+ }
187
+
188
+ messages_append(personaId: string, message: Message): void {
189
+ this.personaState.messages_append(personaId, message);
190
+ this.scheduleSave();
191
+ }
192
+
193
+ messages_sort(personaId: string): void {
194
+ this.personaState.messages_sort(personaId);
195
+ this.scheduleSave();
196
+ }
197
+
198
+ messages_setContextStatus(
199
+ personaId: string,
200
+ messageId: string,
201
+ status: ContextStatus
202
+ ): boolean {
203
+ const result = this.personaState.messages_setContextStatus(personaId, messageId, status);
204
+ this.scheduleSave();
205
+ return result;
206
+ }
207
+
208
+ messages_markRead(personaId: string, messageId: string): boolean {
209
+ const result = this.personaState.messages_markRead(personaId, messageId);
210
+ this.scheduleSave();
211
+ return result;
212
+ }
213
+
214
+ messages_markPendingAsRead(personaId: string): number {
215
+ const result = this.personaState.messages_markPendingAsRead(personaId);
216
+ this.scheduleSave();
217
+ return result;
218
+ }
219
+
220
+ messages_countUnread(personaId: string): number {
221
+ return this.personaState.messages_countUnread(personaId);
222
+ }
223
+
224
+ messages_markAllRead(personaId: string): number {
225
+ const result = this.personaState.messages_markAllRead(personaId);
226
+ this.scheduleSave();
227
+ return result;
228
+ }
229
+
230
+ messages_remove(personaId: string, messageIds: string[]): Message[] {
231
+ const result = this.personaState.messages_remove(personaId, messageIds);
232
+ const removedIds = new Set(result.map(m => m.id));
233
+ const quotes = this.humanState.get().quotes ?? [];
234
+ for (const quote of quotes) {
235
+ if (quote.message_id && removedIds.has(quote.message_id)) {
236
+ quote.message_id = null;
237
+ }
238
+ }
239
+ this.scheduleSave();
240
+ return result;
241
+ }
242
+
243
+ messages_getUnextracted(personaId: string, flag: "f" | "r" | "p" | "o", limit?: number): Message[] {
244
+ return this.personaState.messages_getUnextracted(personaId, flag, limit);
245
+ }
246
+
247
+ messages_markExtracted(personaId: string, messageIds: string[], flag: "f" | "r" | "p" | "o"): number {
248
+ const result = this.personaState.messages_markExtracted(personaId, messageIds, flag);
249
+ this.scheduleSave();
250
+ return result;
251
+ }
252
+
253
+ queue_enqueue(request: Omit<LLMRequest, "id" | "created_at" | "attempts">): string {
254
+ const requestWithModel = {
255
+ ...request,
256
+ model: request.model ?? this.humanState.get().settings?.default_model,
257
+ };
258
+ const id = this.queueState.enqueue(requestWithModel);
259
+ this.scheduleSave();
260
+ return id;
261
+ }
262
+
263
+ queue_peekHighest(): LLMRequest | null {
264
+ return this.queueState.peekHighest();
265
+ }
266
+
267
+ queue_complete(id: string): void {
268
+ this.queueState.complete(id);
269
+ this.scheduleSave();
270
+ }
271
+
272
+ queue_fail(id: string, error?: string, permanent?: boolean): QueueFailResult {
273
+ const result = this.queueState.fail(id, error, permanent);
274
+ this.scheduleSave();
275
+ return result;
276
+ }
277
+
278
+ queue_getValidations(): LLMRequest[] {
279
+ return this.queueState.getValidations();
280
+ }
281
+
282
+ queue_clearValidations(ids: string[]): void {
283
+ this.queueState.clearValidations(ids);
284
+ this.scheduleSave();
285
+ }
286
+
287
+ queue_clearPersonaResponses(personaId: string, nextStep: string): string[] {
288
+ const result = this.queueState.clearPersonaResponses(personaId, nextStep);
289
+ this.scheduleSave();
290
+ return result;
291
+ }
292
+
293
+ queue_length(): number {
294
+ return this.queueState.length();
295
+ }
296
+
297
+ queue_pause(): void {
298
+ this.queueState.pause();
299
+ this.scheduleSave();
300
+ }
301
+
302
+ queue_resume(): void {
303
+ this.queueState.resume();
304
+ this.scheduleSave();
305
+ }
306
+
307
+ queue_isPaused(): boolean {
308
+ return this.queueState.isPaused();
309
+ }
310
+
311
+ queue_hasPendingCeremonies(): boolean {
312
+ return this.queueState.hasPendingCeremonies();
313
+ }
314
+
315
+ queue_clear(): number {
316
+ const result = this.queueState.clear();
317
+ this.scheduleSave();
318
+ return result;
319
+ }
320
+
321
+ async flush(): Promise<void> {
322
+ await this.persistenceState.flush();
323
+ }
324
+
325
+ async moveToBackup(): Promise<void> {
326
+ await this.persistenceState.moveToBackup();
327
+ }
328
+
329
+ async loadBackup(): Promise<StorageState | null> {
330
+ return this.persistenceState.loadBackup();
331
+ }
332
+
333
+ hasExistingData(): boolean {
334
+ return this.persistenceState.hasExistingData();
335
+ }
336
+
337
+ restoreFromState(state: StorageState): void {
338
+ this.humanState.load(state.human);
339
+ this.personaState.load(state.personas);
340
+ this.queueState.load(state.queue);
341
+ this.scheduleSave();
342
+ }
343
+
344
+ getStorageState(): StorageState {
345
+ return this.buildStorageState();
346
+ }
347
+ }