ei-tui 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli/README.md +2 -0
- package/src/cli/install.ts +708 -0
- package/src/cli/retrieval.ts +22 -0
- package/src/cli/session-context.ts +98 -0
- package/src/cli.ts +2 -669
- package/src/core/bootstrap-tools.ts +486 -0
- package/src/core/handlers/document-segmentation.ts +1 -2
- package/src/core/handlers/heartbeat.ts +3 -2
- package/src/core/handlers/persona-response.ts +5 -4
- package/src/core/handlers/rooms.ts +6 -5
- package/src/core/integration-sync-manager.ts +482 -0
- package/src/core/message-manager.ts +2 -1
- package/src/core/migrations.ts +297 -0
- package/src/core/orchestrators/ceremony.ts +2 -1
- package/src/core/processor.ts +17 -1151
- package/src/core/room-manager.ts +17 -4
- package/src/core/state-manager.ts +2 -1
- package/src/core/types/entities.ts +1 -0
- package/src/core/utils/message-id.ts +15 -0
- package/src/integrations/claude-code/importer.ts +9 -30
- package/src/integrations/claude-code/types.ts +1 -1
- package/src/integrations/codex/importer.ts +6 -27
- package/src/integrations/codex/types.ts +1 -1
- package/src/integrations/constants.ts +3 -0
- package/src/integrations/cursor/importer.ts +9 -26
- package/src/integrations/cursor/types.ts +1 -1
- package/src/integrations/pi/importer.ts +235 -0
- package/src/integrations/pi/index.ts +3 -0
- package/src/integrations/pi/reader.ts +247 -0
- package/src/integrations/pi/types.ts +151 -0
- package/src/integrations/shared/message-converter.ts +41 -0
- package/src/integrations/slack/importer.ts +1 -1
- package/tui/README.md +1 -0
- package/tui/src/components/PromptInput.tsx +5 -1
- package/tui/src/util/yaml-settings.ts +28 -0
package/src/core/processor.ts
CHANGED
|
@@ -35,7 +35,11 @@ import { QueueProcessor } from "./queue-processor.js";
|
|
|
35
35
|
import { handlers } from "./handlers/index.js";
|
|
36
36
|
import { normalizeRoomMessages, getMessageContent } from "./handlers/utils.js";
|
|
37
37
|
import { sanitizeEiPersonaIdentifiers } from "./utils/identifier-utils.js";
|
|
38
|
+
import { qualifyEiMessage } from "./utils/message-id.js";
|
|
38
39
|
import { ContextStatus as ContextStatusEnum, RoomMode } from "./types.js";
|
|
40
|
+
import { bootstrapTools } from "./bootstrap-tools.js";
|
|
41
|
+
import { seedBuiltinFacts, migrateLearnedOn, migrateMessageIds, migrateSlackToMultiWorkspace, seedSettings } from "./migrations.js";
|
|
42
|
+
import { IntegrationSyncManager } from "./integration-sync-manager.js";
|
|
39
43
|
import { registerFindMemoryExecutor, registerFetchMemoryExecutor, registerFetchMessageExecutor, registerFileReadExecutor, registerPersonaNoteExecutors, buildPersonaNoteTools, SYSTEM_TOOLS } from "./tools/index.js";
|
|
40
44
|
import { createAddNoteExecutor, createClearNoteExecutor } from "./tools/builtin/persona-notes.js";
|
|
41
45
|
import { createFindMemoryExecutor } from "./tools/builtin/find-memory.js";
|
|
@@ -46,7 +50,6 @@ import { EMMETT_PERSONA_DEFINITION } from "../templates/emmett.js";
|
|
|
46
50
|
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueReflectionDrain, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction, queueTargetedPersonUpdate, queueTargetedTopicUpdate } from "./orchestrators/index.js";
|
|
47
51
|
import { finishDocumentBatch } from "./handlers/document-segmentation.js";
|
|
48
52
|
import { buildSynthesisPrompt } from "../prompts/synthesis/index.js";
|
|
49
|
-
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
50
53
|
import { DEFAULT_SEED_TRAITS } from "./constants/seed-traits.js";
|
|
51
54
|
|
|
52
55
|
// Static module imports
|
|
@@ -140,15 +143,8 @@ import {
|
|
|
140
143
|
import type { RoomCreationInput, RoomEntity, RoomMessage, RoomSummary } from "./types.js";
|
|
141
144
|
import { previewUnsource as _previewUnsource } from "../integrations/document/unsource.js";
|
|
142
145
|
import type { UnsourcePreview, UnsourceResult } from "../integrations/document/unsource.js";
|
|
143
|
-
import { isQualifiedMessageId, qualifyEiMessage, qualifyOpenCodeMessage } from "./utils/message-id.js";
|
|
144
|
-
|
|
145
|
-
import type { IOpenCodeReader } from "../integrations/opencode/types.js";
|
|
146
146
|
|
|
147
147
|
const DEFAULT_LOOP_INTERVAL_MS = 100;
|
|
148
|
-
const DEFAULT_OPENCODE_POLLING_MS = 60000;
|
|
149
|
-
const DEFAULT_CLAUDE_CODE_POLLING_MS = 60000;
|
|
150
|
-
const DEFAULT_CURSOR_POLLING_MS = 60000;
|
|
151
|
-
const DEFAULT_CODEX_POLLING_MS = 60000;
|
|
152
148
|
|
|
153
149
|
let processorInstanceCount = 0;
|
|
154
150
|
|
|
@@ -164,20 +160,11 @@ export class Processor {
|
|
|
164
160
|
private instanceId: number;
|
|
165
161
|
private currentRequest: LLMRequest | null = null;
|
|
166
162
|
private isTUI = false;
|
|
167
|
-
private lastOpenCodeSync = 0;
|
|
168
163
|
private lastDLQTrim = 0;
|
|
169
|
-
private openCodeImportInProgress = false;
|
|
170
|
-
private lastClaudeCodeSync = 0;
|
|
171
|
-
private claudeCodeImportInProgress = false;
|
|
172
|
-
private lastCursorSync = 0;
|
|
173
|
-
private cursorImportInProgress = false;
|
|
174
|
-
private lastCodexSync = 0;
|
|
175
|
-
private codexImportInProgress = false;
|
|
176
|
-
private lastSlackSync = 0;
|
|
177
|
-
private slackImportInProgress = false;
|
|
178
164
|
private pendingConflict: StateConflictData | null = null;
|
|
179
165
|
private storage: Storage | null = null;
|
|
180
166
|
private importAbortController = new AbortController();
|
|
167
|
+
private syncManager: IntegrationSyncManager | null = null;
|
|
181
168
|
private personaPreviewResolvers = new Map<string, { resolve: (r: PersonaGenerationResult) => void; reject: (e: Error) => void }>();
|
|
182
169
|
|
|
183
170
|
constructor(ei: Ei_Interface) {
|
|
@@ -249,11 +236,11 @@ export class Processor {
|
|
|
249
236
|
await this.bootstrapFirstRun();
|
|
250
237
|
}
|
|
251
238
|
this.bootstrapTools();
|
|
252
|
-
this.
|
|
253
|
-
this.
|
|
254
|
-
await this.
|
|
255
|
-
this.
|
|
256
|
-
this.
|
|
239
|
+
seedBuiltinFacts(this.stateManager);
|
|
240
|
+
migrateLearnedOn(this.stateManager);
|
|
241
|
+
await migrateMessageIds(this.stateManager, this.isTUI);
|
|
242
|
+
migrateSlackToMultiWorkspace(this.stateManager);
|
|
243
|
+
seedSettings(this.stateManager);
|
|
257
244
|
registerFindMemoryExecutor(createFindMemoryExecutor(this.searchHumanData.bind(this), this.getPersonaList.bind(this), this.stateManager.getHuman.bind(this.stateManager)));
|
|
258
245
|
registerFetchMemoryExecutor(createFetchMemoryExecutor(this.stateManager.getHuman.bind(this.stateManager)));
|
|
259
246
|
registerPersonaNoteExecutors(
|
|
@@ -281,6 +268,7 @@ export class Processor {
|
|
|
281
268
|
(roomId: string) => this.stateManager.getRoom(roomId)?.display_name ?? null
|
|
282
269
|
));
|
|
283
270
|
}
|
|
271
|
+
this.syncManager = new IntegrationSyncManager(this.stateManager, this.isTUI, this.storage, this.importAbortController, this.interface);
|
|
284
272
|
this.running = true;
|
|
285
273
|
console.log(`[Processor ${this.instanceId}] initialized, starting loop`);
|
|
286
274
|
this.runLoop();
|
|
@@ -310,7 +298,7 @@ export class Processor {
|
|
|
310
298
|
this.stateManager.persona_add(eiEntity);
|
|
311
299
|
|
|
312
300
|
const welcomeMessage: Message = {
|
|
313
|
-
id: crypto.randomUUID(),
|
|
301
|
+
id: qualifyEiMessage(crypto.randomUUID()),
|
|
314
302
|
role: "system",
|
|
315
303
|
content: EI_WELCOME_MESSAGE,
|
|
316
304
|
timestamp: new Date().toISOString(),
|
|
@@ -496,761 +484,8 @@ export class Processor {
|
|
|
496
484
|
return this.generateDocument(subject);
|
|
497
485
|
}
|
|
498
486
|
|
|
499
|
-
/**
|
|
500
|
-
* Seed built-in tool providers and tools if they don't exist yet.
|
|
501
|
-
* Called on every startup (after state load/restore) — safe to call repeatedly.
|
|
502
|
-
* New builtins added in future releases will be seeded automatically.
|
|
503
|
-
*/
|
|
504
487
|
private bootstrapTools(): void {
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
for (const name of ["find_memory", "fetch_memory", "fetch_message", "read_memory"]) {
|
|
508
|
-
const tool = this.stateManager.tools_getByName(name);
|
|
509
|
-
if (tool) this.stateManager.tools_remove(tool.id);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// --- Ei built-in provider ---
|
|
513
|
-
if (!this.stateManager.tools_getProviderById("ei")) {
|
|
514
|
-
const eiProvider: ToolProvider = {
|
|
515
|
-
id: "ei",
|
|
516
|
-
name: "ei",
|
|
517
|
-
display_name: "Ei Built-ins",
|
|
518
|
-
description: "Built-in tools that ship with Ei. No external API needed.",
|
|
519
|
-
builtin: true,
|
|
520
|
-
config: {},
|
|
521
|
-
enabled: true,
|
|
522
|
-
created_at: now,
|
|
523
|
-
};
|
|
524
|
-
this.stateManager.tools_addProvider(eiProvider);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// file_read tool (TUI only)
|
|
528
|
-
this.stateManager.tools_upsertBuiltin({
|
|
529
|
-
id: crypto.randomUUID(),
|
|
530
|
-
provider_id: "ei",
|
|
531
|
-
name: "file_read",
|
|
532
|
-
display_name: "Read File",
|
|
533
|
-
description:
|
|
534
|
-
"Read the contents of a file from the local filesystem. Use list_directory first to explore folder structure. Only available in the TUI.",
|
|
535
|
-
input_schema: {
|
|
536
|
-
type: "object",
|
|
537
|
-
properties: {
|
|
538
|
-
path: { type: "string", description: "Absolute or relative path to the file" },
|
|
539
|
-
},
|
|
540
|
-
required: ["path"],
|
|
541
|
-
},
|
|
542
|
-
runtime: "node",
|
|
543
|
-
builtin: true,
|
|
544
|
-
enabled: true,
|
|
545
|
-
created_at: now,
|
|
546
|
-
max_calls_per_interaction: 5,
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
// list_directory tool (TUI only)
|
|
550
|
-
this.stateManager.tools_upsertBuiltin({
|
|
551
|
-
id: crypto.randomUUID(),
|
|
552
|
-
provider_id: "ei",
|
|
553
|
-
name: "list_directory",
|
|
554
|
-
display_name: "List Directory",
|
|
555
|
-
description:
|
|
556
|
-
"List the contents of a directory on the local filesystem. Returns filenames prefixed with [FILE] or [DIR]. Use this to explore folder structure before reading files. Only available in the TUI.",
|
|
557
|
-
input_schema: {
|
|
558
|
-
type: "object",
|
|
559
|
-
properties: {
|
|
560
|
-
path: { type: "string", description: "Absolute or relative path to the directory (e.g. ~/Projects/myapp or /home/user/docs)" },
|
|
561
|
-
},
|
|
562
|
-
required: ["path"],
|
|
563
|
-
},
|
|
564
|
-
runtime: "node",
|
|
565
|
-
builtin: true,
|
|
566
|
-
enabled: true,
|
|
567
|
-
created_at: now,
|
|
568
|
-
max_calls_per_interaction: 5,
|
|
569
|
-
});
|
|
570
|
-
|
|
571
|
-
// directory_tree tool (TUI only)
|
|
572
|
-
this.stateManager.tools_upsertBuiltin({
|
|
573
|
-
id: crypto.randomUUID(),
|
|
574
|
-
provider_id: "ei",
|
|
575
|
-
name: "directory_tree",
|
|
576
|
-
display_name: "Directory Tree",
|
|
577
|
-
description:
|
|
578
|
-
"Show a recursive tree of a directory up to a configurable depth. Returns a JSON tree with name, type, and children fields. Default max_depth is 3, maximum is 8. Only available in the TUI.",
|
|
579
|
-
input_schema: {
|
|
580
|
-
type: "object",
|
|
581
|
-
properties: {
|
|
582
|
-
path: { type: "string", description: "Absolute or relative path to the directory" },
|
|
583
|
-
max_depth: { type: "number", description: "Maximum depth to recurse (1-8, default 3)" },
|
|
584
|
-
},
|
|
585
|
-
required: ["path"],
|
|
586
|
-
},
|
|
587
|
-
runtime: "node",
|
|
588
|
-
builtin: true,
|
|
589
|
-
enabled: true,
|
|
590
|
-
created_at: now,
|
|
591
|
-
max_calls_per_interaction: 3,
|
|
592
|
-
});
|
|
593
|
-
|
|
594
|
-
// search_files tool (TUI only)
|
|
595
|
-
this.stateManager.tools_upsertBuiltin({
|
|
596
|
-
id: crypto.randomUUID(),
|
|
597
|
-
provider_id: "ei",
|
|
598
|
-
name: "search_files",
|
|
599
|
-
display_name: "Search Files",
|
|
600
|
-
description:
|
|
601
|
-
"Recursively search for files by name pattern within a directory. Supports * wildcards. Returns matching absolute paths. Skips node_modules, .git, dist. Only available in the TUI.",
|
|
602
|
-
input_schema: {
|
|
603
|
-
type: "object",
|
|
604
|
-
properties: {
|
|
605
|
-
path: { type: "string", description: "Root directory to search from" },
|
|
606
|
-
pattern: { type: "string", description: "Filename glob pattern, e.g. \"*.ts\" or \"README*\"" },
|
|
607
|
-
},
|
|
608
|
-
required: ["path", "pattern"],
|
|
609
|
-
},
|
|
610
|
-
runtime: "node",
|
|
611
|
-
builtin: true,
|
|
612
|
-
enabled: true,
|
|
613
|
-
created_at: now,
|
|
614
|
-
max_calls_per_interaction: 3,
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// grep tool (TUI only)
|
|
618
|
-
this.stateManager.tools_upsertBuiltin({
|
|
619
|
-
id: crypto.randomUUID(),
|
|
620
|
-
provider_id: "ei",
|
|
621
|
-
name: "grep",
|
|
622
|
-
display_name: "Grep",
|
|
623
|
-
description:
|
|
624
|
-
"Search file contents for lines matching a regex pattern. Recursively searches a directory (or a single file). Skips binary files and node_modules. Returns matching file, line number, and text. Only available in the TUI.",
|
|
625
|
-
input_schema: {
|
|
626
|
-
type: "object",
|
|
627
|
-
properties: {
|
|
628
|
-
pattern: { type: "string", description: "Regex pattern to search for" },
|
|
629
|
-
path: { type: "string", description: "File or directory to search" },
|
|
630
|
-
include: { type: "string", description: "Optional glob to filter filenames, e.g. \"*.ts\"" },
|
|
631
|
-
case_insensitive: { type: "boolean", description: "Case-insensitive match (default false)" },
|
|
632
|
-
},
|
|
633
|
-
required: ["pattern", "path"],
|
|
634
|
-
},
|
|
635
|
-
runtime: "node",
|
|
636
|
-
builtin: true,
|
|
637
|
-
enabled: true,
|
|
638
|
-
created_at: now,
|
|
639
|
-
max_calls_per_interaction: 5,
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
// get_file_info tool (TUI only)
|
|
643
|
-
this.stateManager.tools_upsertBuiltin({
|
|
644
|
-
id: crypto.randomUUID(),
|
|
645
|
-
provider_id: "ei",
|
|
646
|
-
name: "get_file_info",
|
|
647
|
-
display_name: "Get File Info",
|
|
648
|
-
description:
|
|
649
|
-
"Get metadata about a file or directory: type, size, permissions, created/modified/accessed timestamps. Only available in the TUI.",
|
|
650
|
-
input_schema: {
|
|
651
|
-
type: "object",
|
|
652
|
-
properties: {
|
|
653
|
-
path: { type: "string", description: "Absolute or relative path to the file or directory" },
|
|
654
|
-
},
|
|
655
|
-
required: ["path"],
|
|
656
|
-
},
|
|
657
|
-
runtime: "node",
|
|
658
|
-
builtin: true,
|
|
659
|
-
enabled: true,
|
|
660
|
-
created_at: now,
|
|
661
|
-
max_calls_per_interaction: 5,
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
// web_fetch tool
|
|
665
|
-
this.stateManager.tools_upsertBuiltin({
|
|
666
|
-
id: crypto.randomUUID(),
|
|
667
|
-
provider_id: "ei",
|
|
668
|
-
name: "web_fetch",
|
|
669
|
-
display_name: "Web Fetch",
|
|
670
|
-
description:
|
|
671
|
-
"Fetch content from a URL and return the text. Useful for reading web pages, documentation, or public APIs. HTML is stripped to plain text. Only available in the TUI.",
|
|
672
|
-
input_schema: {
|
|
673
|
-
type: "object",
|
|
674
|
-
properties: {
|
|
675
|
-
url: { type: "string", description: "The URL to fetch (http or https only)" },
|
|
676
|
-
},
|
|
677
|
-
required: ["url"],
|
|
678
|
-
},
|
|
679
|
-
runtime: "node",
|
|
680
|
-
builtin: true,
|
|
681
|
-
enabled: true,
|
|
682
|
-
created_at: now,
|
|
683
|
-
max_calls_per_interaction: 3,
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
// --- Tavily Search provider ---
|
|
687
|
-
if (!this.stateManager.tools_getProviderById("tavily")) {
|
|
688
|
-
const tavilyProvider: ToolProvider = {
|
|
689
|
-
id: "tavily",
|
|
690
|
-
name: "tavily",
|
|
691
|
-
display_name: "Tavily Search",
|
|
692
|
-
description:
|
|
693
|
-
"Browser-compatible web search. Requires a Tavily API key (free tier: 1000 requests/month).",
|
|
694
|
-
builtin: true,
|
|
695
|
-
config: { api_key: "" },
|
|
696
|
-
enabled: false,
|
|
697
|
-
created_at: now,
|
|
698
|
-
};
|
|
699
|
-
this.stateManager.tools_addProvider(tavilyProvider);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// tavily_web_search
|
|
703
|
-
this.stateManager.tools_upsertBuiltin({
|
|
704
|
-
id: crypto.randomUUID(),
|
|
705
|
-
provider_id: "tavily",
|
|
706
|
-
name: "tavily_web_search",
|
|
707
|
-
display_name: "Web Search",
|
|
708
|
-
description:
|
|
709
|
-
"Search the web using Tavily. Use for current events, fact verification, or any topic that benefits from up-to-date information.",
|
|
710
|
-
input_schema: {
|
|
711
|
-
type: "object",
|
|
712
|
-
properties: {
|
|
713
|
-
query: { type: "string", description: "Search query" },
|
|
714
|
-
max_results: { type: "number", description: "Number of results (default: 5, max: 10)" },
|
|
715
|
-
},
|
|
716
|
-
required: ["query"],
|
|
717
|
-
},
|
|
718
|
-
runtime: "any",
|
|
719
|
-
builtin: true,
|
|
720
|
-
enabled: true,
|
|
721
|
-
created_at: now,
|
|
722
|
-
max_calls_per_interaction: 3,
|
|
723
|
-
});
|
|
724
|
-
|
|
725
|
-
// tavily_news_search
|
|
726
|
-
this.stateManager.tools_upsertBuiltin({
|
|
727
|
-
id: crypto.randomUUID(),
|
|
728
|
-
provider_id: "tavily",
|
|
729
|
-
name: "tavily_news_search",
|
|
730
|
-
display_name: "News Search",
|
|
731
|
-
description:
|
|
732
|
-
"Search recent news articles using Tavily. Use for current events and recent developments.",
|
|
733
|
-
input_schema: {
|
|
734
|
-
type: "object",
|
|
735
|
-
properties: {
|
|
736
|
-
query: { type: "string", description: "News search query" },
|
|
737
|
-
max_results: { type: "number", description: "Number of results (default: 5, max: 10)" },
|
|
738
|
-
},
|
|
739
|
-
required: ["query"],
|
|
740
|
-
},
|
|
741
|
-
runtime: "any",
|
|
742
|
-
builtin: true,
|
|
743
|
-
enabled: true,
|
|
744
|
-
created_at: now,
|
|
745
|
-
max_calls_per_interaction: 3,
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
// --- Spotify provider ---
|
|
749
|
-
if (!this.stateManager.tools_getProviderById("spotify")) {
|
|
750
|
-
const spotifyProvider: ToolProvider = {
|
|
751
|
-
id: "spotify",
|
|
752
|
-
name: "spotify",
|
|
753
|
-
display_name: "Spotify",
|
|
754
|
-
description:
|
|
755
|
-
"Access your Spotify playback and music library. Connect via Settings → Tool Kits → Spotify.",
|
|
756
|
-
builtin: true,
|
|
757
|
-
config: { spotify_refresh_token: "" },
|
|
758
|
-
enabled: false,
|
|
759
|
-
created_at: now,
|
|
760
|
-
};
|
|
761
|
-
this.stateManager.tools_addProvider(spotifyProvider);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// get_currently_playing
|
|
765
|
-
this.stateManager.tools_upsertBuiltin({
|
|
766
|
-
id: crypto.randomUUID(),
|
|
767
|
-
provider_id: "spotify",
|
|
768
|
-
name: "get_currently_playing",
|
|
769
|
-
display_name: "Currently Playing",
|
|
770
|
-
description:
|
|
771
|
-
"Get the song currently playing on the user's Spotify. Returns artist, title, album, playback state, and progress. Returns nothing_playing if nothing is active.",
|
|
772
|
-
input_schema: {
|
|
773
|
-
type: "object",
|
|
774
|
-
properties: {},
|
|
775
|
-
required: [],
|
|
776
|
-
},
|
|
777
|
-
runtime: "any",
|
|
778
|
-
builtin: true,
|
|
779
|
-
enabled: true,
|
|
780
|
-
created_at: now,
|
|
781
|
-
max_calls_per_interaction: 3,
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
// get_liked_songs
|
|
785
|
-
this.stateManager.tools_upsertBuiltin({
|
|
786
|
-
id: crypto.randomUUID(),
|
|
787
|
-
provider_id: "spotify",
|
|
788
|
-
name: "get_liked_songs",
|
|
789
|
-
display_name: "Liked Songs",
|
|
790
|
-
description:
|
|
791
|
-
"Get the user's full Spotify liked songs library. Returns an array of { artist, title, added_at }. Results are cached for 30 minutes. Ask the user before calling — it may return thousands of tracks.",
|
|
792
|
-
input_schema: {
|
|
793
|
-
type: "object",
|
|
794
|
-
properties: {},
|
|
795
|
-
required: [],
|
|
796
|
-
},
|
|
797
|
-
runtime: "any",
|
|
798
|
-
builtin: true,
|
|
799
|
-
enabled: true,
|
|
800
|
-
created_at: now,
|
|
801
|
-
max_calls_per_interaction: 1,
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
// submit_response tool — auto-injected for Heartbeat steps only (HandleHeartbeatCheck).
|
|
805
|
-
// PersonaResponse and RoomResponse agents now use natural Markdown output instead.
|
|
806
|
-
// Not user-configurable; invisible in the tools UI. Terminates the tool loop immediately
|
|
807
|
-
// when called; its arguments become response.parsed.
|
|
808
|
-
this.stateManager.tools_upsertBuiltin({
|
|
809
|
-
id: crypto.randomUUID(),
|
|
810
|
-
provider_id: "ei",
|
|
811
|
-
name: "submit_response",
|
|
812
|
-
display_name: "Submit Response",
|
|
813
|
-
description: "Submit your response to the conversation. Call this when you are ready to respond — after any research or tool use is complete.",
|
|
814
|
-
input_schema: {
|
|
815
|
-
type: "object",
|
|
816
|
-
properties: {
|
|
817
|
-
should_respond: {
|
|
818
|
-
type: "boolean",
|
|
819
|
-
description: "Whether you are responding (true) or staying silent (false)",
|
|
820
|
-
},
|
|
821
|
-
content: {
|
|
822
|
-
type: "string",
|
|
823
|
-
description: "Your response in Markdown. Required when should_respond is true. Use _underscores_ for actions or stage directions inline with your text.",
|
|
824
|
-
},
|
|
825
|
-
reason: {
|
|
826
|
-
type: "string",
|
|
827
|
-
description: "Why you are staying silent. Only used when should_respond is false.",
|
|
828
|
-
},
|
|
829
|
-
},
|
|
830
|
-
required: ["should_respond"],
|
|
831
|
-
additionalProperties: false,
|
|
832
|
-
},
|
|
833
|
-
runtime: "any",
|
|
834
|
-
builtin: true,
|
|
835
|
-
enabled: true,
|
|
836
|
-
is_submit: true,
|
|
837
|
-
max_calls_per_interaction: 1,
|
|
838
|
-
created_at: now,
|
|
839
|
-
});
|
|
840
|
-
|
|
841
|
-
this.stateManager.tools_upsertBuiltin({
|
|
842
|
-
id: crypto.randomUUID(),
|
|
843
|
-
provider_id: "ei",
|
|
844
|
-
name: "submit_heartbeat_check",
|
|
845
|
-
display_name: "Submit Heartbeat Decision",
|
|
846
|
-
description: "Submit your decision on whether to reach out with a message. Call this when you have decided.",
|
|
847
|
-
input_schema: {
|
|
848
|
-
type: "object",
|
|
849
|
-
properties: {
|
|
850
|
-
should_respond: { type: "boolean", description: "Whether you want to initiate a message" },
|
|
851
|
-
topic: { type: "string", description: "The specific topic you want to discuss (when should_respond is true)" },
|
|
852
|
-
message: { type: "string", description: "Your actual message to them (when should_respond is true)" },
|
|
853
|
-
},
|
|
854
|
-
required: ["should_respond"],
|
|
855
|
-
additionalProperties: false,
|
|
856
|
-
},
|
|
857
|
-
runtime: "any",
|
|
858
|
-
builtin: true,
|
|
859
|
-
enabled: true,
|
|
860
|
-
is_submit: true,
|
|
861
|
-
max_calls_per_interaction: 1,
|
|
862
|
-
created_at: now,
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
this.stateManager.tools_upsertBuiltin({
|
|
866
|
-
id: crypto.randomUUID(),
|
|
867
|
-
provider_id: "ei",
|
|
868
|
-
name: "submit_ei_heartbeat",
|
|
869
|
-
display_name: "Submit Ei Heartbeat Decision",
|
|
870
|
-
description: "Submit your choice of item to follow up on, or indicate nothing warrants reaching out.",
|
|
871
|
-
input_schema: {
|
|
872
|
-
type: "object",
|
|
873
|
-
properties: {
|
|
874
|
-
should_respond: { type: "boolean", description: "Whether Ei wants to check in about an item" },
|
|
875
|
-
id: { type: "string", description: "ID of the item you chose (when should_respond is true)" },
|
|
876
|
-
my_response: { type: "string", description: "The check-in message (for Person/Topic/Persona items)" },
|
|
877
|
-
},
|
|
878
|
-
required: ["should_respond"],
|
|
879
|
-
additionalProperties: false,
|
|
880
|
-
},
|
|
881
|
-
runtime: "any",
|
|
882
|
-
builtin: true,
|
|
883
|
-
enabled: true,
|
|
884
|
-
is_submit: true,
|
|
885
|
-
max_calls_per_interaction: 1,
|
|
886
|
-
created_at: now,
|
|
887
|
-
});
|
|
888
|
-
|
|
889
|
-
this.stateManager.tools_upsertBuiltin({
|
|
890
|
-
id: crypto.randomUUID(),
|
|
891
|
-
provider_id: "ei",
|
|
892
|
-
name: "submit_dedup_decisions",
|
|
893
|
-
display_name: "Submit Dedup Decisions",
|
|
894
|
-
description: "Submit your merge, remove, and add decisions for this cluster of records.",
|
|
895
|
-
input_schema: {
|
|
896
|
-
type: "object",
|
|
897
|
-
properties: {
|
|
898
|
-
update: {
|
|
899
|
-
type: "array",
|
|
900
|
-
description: "Records to update with merged data. Must include at least one (the canonical record).",
|
|
901
|
-
items: {
|
|
902
|
-
type: "object",
|
|
903
|
-
properties: {
|
|
904
|
-
id: { type: "string" },
|
|
905
|
-
type: { type: "string", enum: ["topic", "person", "trait"] },
|
|
906
|
-
name: { type: "string" },
|
|
907
|
-
description: { type: "string" },
|
|
908
|
-
sentiment: { type: "number" },
|
|
909
|
-
strength: { type: "number" },
|
|
910
|
-
confidence: { type: "number" },
|
|
911
|
-
exposure_current: { type: "number" },
|
|
912
|
-
exposure_desired: { type: "number" },
|
|
913
|
-
relationship: { type: "string" },
|
|
914
|
-
category: { type: "string" },
|
|
915
|
-
last_updated: { type: "string" },
|
|
916
|
-
},
|
|
917
|
-
required: ["id", "type", "name", "description"],
|
|
918
|
-
additionalProperties: false,
|
|
919
|
-
},
|
|
920
|
-
},
|
|
921
|
-
remove: {
|
|
922
|
-
type: "array",
|
|
923
|
-
description: "Duplicates to remove. Each must reference its canonical record via replaced_by.",
|
|
924
|
-
items: {
|
|
925
|
-
type: "object",
|
|
926
|
-
properties: {
|
|
927
|
-
to_be_removed: { type: "string" },
|
|
928
|
-
replaced_by: { type: "string" },
|
|
929
|
-
},
|
|
930
|
-
required: ["to_be_removed", "replaced_by"],
|
|
931
|
-
additionalProperties: false,
|
|
932
|
-
},
|
|
933
|
-
},
|
|
934
|
-
add: {
|
|
935
|
-
type: "array",
|
|
936
|
-
description: "New records to create. Only when merging reveals a missing concept.",
|
|
937
|
-
items: {
|
|
938
|
-
type: "object",
|
|
939
|
-
properties: {
|
|
940
|
-
type: { type: "string", enum: ["topic", "person", "trait"] },
|
|
941
|
-
name: { type: "string" },
|
|
942
|
-
description: { type: "string" },
|
|
943
|
-
sentiment: { type: "number" },
|
|
944
|
-
strength: { type: "number" },
|
|
945
|
-
confidence: { type: "number" },
|
|
946
|
-
exposure_current: { type: "number" },
|
|
947
|
-
exposure_desired: { type: "number" },
|
|
948
|
-
relationship: { type: "string" },
|
|
949
|
-
category: { type: "string" },
|
|
950
|
-
},
|
|
951
|
-
required: ["type", "name", "description"],
|
|
952
|
-
additionalProperties: false,
|
|
953
|
-
},
|
|
954
|
-
},
|
|
955
|
-
},
|
|
956
|
-
required: ["update", "remove", "add"],
|
|
957
|
-
additionalProperties: false,
|
|
958
|
-
},
|
|
959
|
-
runtime: "any",
|
|
960
|
-
builtin: true,
|
|
961
|
-
enabled: true,
|
|
962
|
-
is_submit: true,
|
|
963
|
-
max_calls_per_interaction: 1,
|
|
964
|
-
created_at: now,
|
|
965
|
-
});
|
|
966
|
-
|
|
967
|
-
// --- Reconcile pass: prune stale tool references from persona tool lists ---
|
|
968
|
-
// Build manifest of all tool IDs currently in state (everything seeded above).
|
|
969
|
-
const manifestIds = new Set(this.stateManager.tools_getAll().map(t => t.id));
|
|
970
|
-
|
|
971
|
-
for (const persona of this.stateManager.persona_getAll()) {
|
|
972
|
-
if (!persona.tools?.length) continue;
|
|
973
|
-
const pruned = persona.tools.filter(id => manifestIds.has(id));
|
|
974
|
-
if (pruned.length !== persona.tools.length) {
|
|
975
|
-
const removed = persona.tools.length - pruned.length;
|
|
976
|
-
this.stateManager.persona_update(persona.id, { tools: pruned });
|
|
977
|
-
console.log(`[Processor] Pruned ${removed} stale tool reference(s) from persona "${persona.display_name}"`);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
/**
|
|
983
|
-
* Seed 25 built-in facts if they don't exist yet.
|
|
984
|
-
* Called on every startup — safe to call repeatedly.
|
|
985
|
-
* New facts are created with empty descriptions and validated_date.
|
|
986
|
-
*/
|
|
987
|
-
private seedBuiltinFacts(): void {
|
|
988
|
-
const human = this.stateManager.getHuman();
|
|
989
|
-
const existingFactNames = new Set(human.facts.map(f => f.name));
|
|
990
|
-
|
|
991
|
-
// BUILT_IN_FACTS imported at top of file
|
|
992
|
-
const now = new Date().toISOString();
|
|
993
|
-
let seededCount = 0;
|
|
994
|
-
|
|
995
|
-
for (const builtInFact of BUILT_IN_FACTS) {
|
|
996
|
-
if (existingFactNames.has(builtInFact.name)) continue;
|
|
997
|
-
|
|
998
|
-
const newFact: Fact = {
|
|
999
|
-
id: crypto.randomUUID(),
|
|
1000
|
-
name: builtInFact.name,
|
|
1001
|
-
description: '',
|
|
1002
|
-
sentiment: 0,
|
|
1003
|
-
validated_date: '',
|
|
1004
|
-
last_updated: now,
|
|
1005
|
-
learned_on: now,
|
|
1006
|
-
};
|
|
1007
|
-
human.facts.push(newFact);
|
|
1008
|
-
seededCount++;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
if (seededCount > 0) {
|
|
1012
|
-
this.stateManager.setHuman(human);
|
|
1013
|
-
console.log(`[Processor] Seeded ${seededCount} built-in facts`);
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
private migrateLearnedOn(): void {
|
|
1018
|
-
const human = this.stateManager.getHuman();
|
|
1019
|
-
|
|
1020
|
-
const backfill = <T extends { learned_on?: string; last_updated: string }>(items: T[]): T[] =>
|
|
1021
|
-
items.map(item => item.learned_on ? item : { ...item, learned_on: item.last_updated });
|
|
1022
|
-
|
|
1023
|
-
const facts = backfill(human.facts);
|
|
1024
|
-
const topics = backfill(human.topics);
|
|
1025
|
-
const people = backfill(human.people);
|
|
1026
|
-
|
|
1027
|
-
const changed =
|
|
1028
|
-
facts.some((f, i) => f !== human.facts[i]) ||
|
|
1029
|
-
topics.some((t, i) => t !== human.topics[i]) ||
|
|
1030
|
-
people.some((p, i) => p !== human.people[i]);
|
|
1031
|
-
|
|
1032
|
-
if (changed) {
|
|
1033
|
-
this.stateManager.setHuman({ ...human, facts, topics, people });
|
|
1034
|
-
console.log("[Processor] Backfilled learned_on for existing data items");
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
private async migrateMessageIds(): Promise<void> {
|
|
1039
|
-
try {
|
|
1040
|
-
let msgRewrites = 0;
|
|
1041
|
-
let quoteRewrites = 0;
|
|
1042
|
-
|
|
1043
|
-
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1044
|
-
|
|
1045
|
-
const personas = this.stateManager.persona_getAll();
|
|
1046
|
-
for (const persona of personas) {
|
|
1047
|
-
for (const msg of this.stateManager.messages_get(persona.id)) {
|
|
1048
|
-
if (!msg.external && UUID_PATTERN.test(msg.id)) {
|
|
1049
|
-
this.stateManager.messages_update(persona.id, msg.id, { id: qualifyEiMessage(msg.id) });
|
|
1050
|
-
msgRewrites++;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
const rooms = this.stateManager.getRoomList();
|
|
1056
|
-
for (const room of rooms) {
|
|
1057
|
-
for (const msg of this.stateManager.getRoomMessages(room.id).slice()) {
|
|
1058
|
-
if (UUID_PATTERN.test(msg.id)) {
|
|
1059
|
-
this.stateManager.updateRoomMessage(room.id, msg.id, { id: qualifyEiMessage(msg.id) });
|
|
1060
|
-
msgRewrites++;
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
const human = this.stateManager.getHuman();
|
|
1066
|
-
const quotes = human.quotes ?? [];
|
|
1067
|
-
|
|
1068
|
-
const eiUuidMap = new Map<string, string>();
|
|
1069
|
-
for (const persona of personas) {
|
|
1070
|
-
for (const msg of this.stateManager.messages_get(persona.id)) {
|
|
1071
|
-
if (msg.id.startsWith("ei:")) eiUuidMap.set(msg.id.slice(3), msg.id);
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
for (const room of rooms) {
|
|
1075
|
-
for (const msg of this.stateManager.getRoomMessages(room.id)) {
|
|
1076
|
-
if (msg.id.startsWith("ei:")) eiUuidMap.set(msg.id.slice(3), msg.id);
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
const MSG_PATTERN = /^msg_[a-zA-Z0-9]+$/;
|
|
1081
|
-
|
|
1082
|
-
let openCodeReader: IOpenCodeReader | null = null;
|
|
1083
|
-
if (this.isTUI) {
|
|
1084
|
-
const { createOpenCodeReader } = await import("../integrations/opencode/reader-factory.js");
|
|
1085
|
-
openCodeReader = await createOpenCodeReader().catch(() => null);
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
const updatedQuotes: typeof quotes = [];
|
|
1089
|
-
for (const quote of quotes) {
|
|
1090
|
-
const mid = quote.message_id;
|
|
1091
|
-
if (!mid || isQualifiedMessageId(mid)) {
|
|
1092
|
-
updatedQuotes.push(quote);
|
|
1093
|
-
continue;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
if (MSG_PATTERN.test(mid)) {
|
|
1097
|
-
if (openCodeReader) {
|
|
1098
|
-
const ocWindow = await openCodeReader.getMessageById(mid).catch(() => null);
|
|
1099
|
-
if (ocWindow) {
|
|
1100
|
-
const { getMachineId } = await import("../integrations/machine-id.js");
|
|
1101
|
-
updatedQuotes.push({ ...quote, message_id: qualifyOpenCodeMessage(getMachineId(), ocWindow.session.id, mid) });
|
|
1102
|
-
quoteRewrites++;
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
updatedQuotes.push(quote);
|
|
1107
|
-
continue;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
if (UUID_PATTERN.test(mid)) {
|
|
1111
|
-
const fqId = eiUuidMap.get(mid);
|
|
1112
|
-
if (fqId) {
|
|
1113
|
-
updatedQuotes.push({ ...quote, message_id: fqId });
|
|
1114
|
-
quoteRewrites++;
|
|
1115
|
-
continue;
|
|
1116
|
-
}
|
|
1117
|
-
updatedQuotes.push(quote);
|
|
1118
|
-
continue;
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
updatedQuotes.push(quote);
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
if (quoteRewrites > 0) {
|
|
1125
|
-
this.stateManager.setHuman({ ...human, quotes: updatedQuotes });
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
if (msgRewrites > 0 || quoteRewrites > 0) {
|
|
1129
|
-
console.log(`[Processor] migrateMessageIds: rewrote ${msgRewrites} message IDs, ${quoteRewrites} quote message_ids`);
|
|
1130
|
-
}
|
|
1131
|
-
} catch (err) {
|
|
1132
|
-
console.error("[Processor] migrateMessageIds failed, continuing:", err);
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
|
|
1136
|
-
private migrateSlackToMultiWorkspace(): void {
|
|
1137
|
-
const human = this.stateManager.getHuman();
|
|
1138
|
-
const slack = human.settings?.slack as Record<string, unknown> | undefined;
|
|
1139
|
-
if (!slack) return;
|
|
1140
|
-
|
|
1141
|
-
const hasLegacyAuth = "auth" in slack && slack.auth != null;
|
|
1142
|
-
const hasLegacyIntegration = "integration" in slack;
|
|
1143
|
-
if (!hasLegacyAuth && !hasLegacyIntegration) return;
|
|
1144
|
-
|
|
1145
|
-
const legacyAuth = slack.auth as Record<string, unknown> | undefined;
|
|
1146
|
-
const workspaceId = (legacyAuth?.workspace_id as string | undefined) ?? "unknown";
|
|
1147
|
-
|
|
1148
|
-
const migratedWorkspace: Record<string, unknown> = {
|
|
1149
|
-
integration: slack.integration,
|
|
1150
|
-
extraction_model: slack.extraction_model,
|
|
1151
|
-
last_sync: slack.last_sync,
|
|
1152
|
-
backfill_days: slack.backfill_days,
|
|
1153
|
-
broadcast_threshold: slack.broadcast_threshold,
|
|
1154
|
-
channel_overrides: slack.channel_overrides,
|
|
1155
|
-
channels: slack.channels,
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
|
-
if (legacyAuth) {
|
|
1159
|
-
migratedWorkspace.auth = {
|
|
1160
|
-
type: "oauth",
|
|
1161
|
-
token: legacyAuth.token,
|
|
1162
|
-
refresh_token: legacyAuth.refresh_token,
|
|
1163
|
-
workspace_name: legacyAuth.workspace_name,
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
this.stateManager.setHuman({
|
|
1168
|
-
...human,
|
|
1169
|
-
settings: {
|
|
1170
|
-
...human.settings,
|
|
1171
|
-
slack: {
|
|
1172
|
-
polling_interval_ms: slack.polling_interval_ms as number | undefined,
|
|
1173
|
-
workspaces: { [workspaceId]: migratedWorkspace } as unknown as import("../integrations/slack/types.js").SlackSettings["workspaces"],
|
|
1174
|
-
},
|
|
1175
|
-
},
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
console.log(`[Processor] migrateSlackToMultiWorkspace: migrated legacy slack settings to workspaces[${workspaceId}]`);
|
|
1179
|
-
}
|
|
1180
|
-
|
|
1181
|
-
private seedSettings(): void {
|
|
1182
|
-
const human = this.stateManager.getHuman();
|
|
1183
|
-
let modified = false;
|
|
1184
|
-
|
|
1185
|
-
if (!human.settings) {
|
|
1186
|
-
human.settings = {};
|
|
1187
|
-
modified = true;
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
if (!human.settings.opencode) {
|
|
1191
|
-
human.settings.opencode = {
|
|
1192
|
-
integration: false,
|
|
1193
|
-
polling_interval_ms: 60000,
|
|
1194
|
-
};
|
|
1195
|
-
modified = true;
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
if (!human.settings.claudeCode) {
|
|
1199
|
-
human.settings.claudeCode = {
|
|
1200
|
-
integration: false,
|
|
1201
|
-
polling_interval_ms: 60000,
|
|
1202
|
-
};
|
|
1203
|
-
modified = true;
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
if (!human.settings.codex) {
|
|
1207
|
-
human.settings.codex = {
|
|
1208
|
-
integration: false,
|
|
1209
|
-
polling_interval_ms: 60000,
|
|
1210
|
-
};
|
|
1211
|
-
modified = true;
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
if (!human.settings.ceremony) {
|
|
1215
|
-
human.settings.ceremony = {
|
|
1216
|
-
time: "09:00",
|
|
1217
|
-
};
|
|
1218
|
-
modified = true;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
if (!human.settings.backup) {
|
|
1222
|
-
human.settings.backup = {
|
|
1223
|
-
enabled: false,
|
|
1224
|
-
max_backups: 24,
|
|
1225
|
-
interval_ms: 3600000,
|
|
1226
|
-
};
|
|
1227
|
-
modified = true;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
if (human.settings.default_heartbeat_ms == null) {
|
|
1231
|
-
human.settings.default_heartbeat_ms = 1800000;
|
|
1232
|
-
modified = true;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
if (human.settings.default_context_window_ms == null) {
|
|
1236
|
-
human.settings.default_context_window_ms = 28800000;
|
|
1237
|
-
modified = true;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (human.settings.message_min_count == null) {
|
|
1241
|
-
human.settings.message_min_count = 0;
|
|
1242
|
-
modified = true;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
if (human.settings.message_max_age_days == null) {
|
|
1246
|
-
human.settings.message_max_age_days = 0;
|
|
1247
|
-
modified = true;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
if (modified) {
|
|
1251
|
-
this.stateManager.setHuman(human);
|
|
1252
|
-
console.log(`[Processor] Seeded missing settings`);
|
|
1253
|
-
}
|
|
488
|
+
bootstrapTools(this.stateManager);
|
|
1254
489
|
}
|
|
1255
490
|
|
|
1256
491
|
async stop(): Promise<void> {
|
|
@@ -1280,26 +515,7 @@ export class Processor {
|
|
|
1280
515
|
this.running = false;
|
|
1281
516
|
this.queueProcessor.abort();
|
|
1282
517
|
this.importAbortController.abort();
|
|
1283
|
-
|
|
1284
|
-
console.log(`[Processor ${this.instanceId}] Clearing openCodeImportInProgress flag`);
|
|
1285
|
-
this.openCodeImportInProgress = false;
|
|
1286
|
-
}
|
|
1287
|
-
if (this.claudeCodeImportInProgress) {
|
|
1288
|
-
console.log(`[Processor ${this.instanceId}] Clearing claudeCodeImportInProgress flag`);
|
|
1289
|
-
this.claudeCodeImportInProgress = false;
|
|
1290
|
-
}
|
|
1291
|
-
if (this.cursorImportInProgress) {
|
|
1292
|
-
console.log(`[Processor ${this.instanceId}] Clearing cursorImportInProgress flag`);
|
|
1293
|
-
this.cursorImportInProgress = false;
|
|
1294
|
-
}
|
|
1295
|
-
if (this.codexImportInProgress) {
|
|
1296
|
-
console.log(`[Processor ${this.instanceId}] Clearing codexImportInProgress flag`);
|
|
1297
|
-
this.codexImportInProgress = false;
|
|
1298
|
-
}
|
|
1299
|
-
if (this.slackImportInProgress) {
|
|
1300
|
-
console.log(`[Processor ${this.instanceId}] Clearing slackImportInProgress flag`);
|
|
1301
|
-
this.slackImportInProgress = false;
|
|
1302
|
-
}
|
|
518
|
+
this.syncManager?.resetImportFlags();
|
|
1303
519
|
await this.stateManager.flush();
|
|
1304
520
|
console.log(`[Processor ${this.instanceId}] pause() complete (main loop stopped, state flushed)`);
|
|
1305
521
|
}
|
|
@@ -1310,6 +526,7 @@ export class Processor {
|
|
|
1310
526
|
throw new Error(`Cannot resume a stopped processor (instanceId: ${this.instanceId})`);
|
|
1311
527
|
}
|
|
1312
528
|
this.importAbortController = new AbortController();
|
|
529
|
+
this.syncManager?.updateAbortController(this.importAbortController);
|
|
1313
530
|
this.running = true;
|
|
1314
531
|
this.runLoop();
|
|
1315
532
|
console.log(`[Processor ${this.instanceId}] resume() complete (main loop restarted)`);
|
|
@@ -1514,56 +731,8 @@ const toolNextSteps = new Set([
|
|
|
1514
731
|
|
|
1515
732
|
const human = this.stateManager.getHuman();
|
|
1516
733
|
|
|
1517
|
-
if (
|
|
1518
|
-
this.
|
|
1519
|
-
human.settings?.opencode?.integration &&
|
|
1520
|
-
this.stateManager.queue_length() === 0
|
|
1521
|
-
) {
|
|
1522
|
-
await this.checkAndSyncOpenCode(human, now);
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
if (this.isTUI && human.settings?.backup?.enabled) {
|
|
1526
|
-
await this.checkAndRunRollingBackup(human, now);
|
|
1527
|
-
}
|
|
1528
|
-
if (
|
|
1529
|
-
this.isTUI &&
|
|
1530
|
-
human.settings?.claudeCode?.integration &&
|
|
1531
|
-
this.stateManager.queue_length() === 0
|
|
1532
|
-
) {
|
|
1533
|
-
await this.checkAndSyncClaudeCode(human, now);
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
|
-
if (
|
|
1537
|
-
this.isTUI &&
|
|
1538
|
-
human.settings?.cursor?.integration &&
|
|
1539
|
-
this.stateManager.queue_length() === 0
|
|
1540
|
-
) {
|
|
1541
|
-
await this.checkAndSyncCursor(human, now);
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
if (
|
|
1545
|
-
this.isTUI &&
|
|
1546
|
-
human.settings?.codex?.integration &&
|
|
1547
|
-
this.stateManager.queue_length() === 0
|
|
1548
|
-
) {
|
|
1549
|
-
await this.checkAndSyncCodex(human, now);
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
if (
|
|
1553
|
-
this.isTUI &&
|
|
1554
|
-
human.settings?.personaHistory?.integration &&
|
|
1555
|
-
!human.settings.personaHistory.complete &&
|
|
1556
|
-
this.stateManager.queue_length() === 0
|
|
1557
|
-
) {
|
|
1558
|
-
await this.checkAndSyncPersonaHistory(human);
|
|
1559
|
-
}
|
|
1560
|
-
|
|
1561
|
-
if (
|
|
1562
|
-
this.isTUI &&
|
|
1563
|
-
Object.values(human.settings?.slack?.workspaces ?? {}).some(ws => ws.integration && ws.auth) &&
|
|
1564
|
-
this.stateManager.queue_length() === 0
|
|
1565
|
-
) {
|
|
1566
|
-
await this.checkAndSyncSlack(human, now);
|
|
734
|
+
if (this.syncManager) {
|
|
735
|
+
await this.syncManager.checkAll(human, now);
|
|
1567
736
|
}
|
|
1568
737
|
|
|
1569
738
|
if (human.settings?.ceremony && shouldStartCeremony(human.settings.ceremony, this.stateManager)) {
|
|
@@ -1621,309 +790,6 @@ const toolNextSteps = new Set([
|
|
|
1621
790
|
}
|
|
1622
791
|
}
|
|
1623
792
|
|
|
1624
|
-
private async checkAndRunRollingBackup(human: HumanEntity, now: number): Promise<void> {
|
|
1625
|
-
if (!this.storage) return;
|
|
1626
|
-
const cfg = human.settings!.backup!;
|
|
1627
|
-
const intervalMs = cfg.interval_ms ?? 3_600_000;
|
|
1628
|
-
const maxBackups = cfg.max_backups ?? 24;
|
|
1629
|
-
const lastBackup = cfg.last_backup ? new Date(cfg.last_backup).getTime() : 0;
|
|
1630
|
-
|
|
1631
|
-
if (now - lastBackup < intervalMs) return;
|
|
1632
|
-
|
|
1633
|
-
this.stateManager.setHuman({
|
|
1634
|
-
...this.stateManager.getHuman(),
|
|
1635
|
-
settings: {
|
|
1636
|
-
...this.stateManager.getHuman().settings,
|
|
1637
|
-
backup: { ...cfg, last_backup: new Date(now).toISOString() },
|
|
1638
|
-
},
|
|
1639
|
-
});
|
|
1640
|
-
|
|
1641
|
-
const state = this.stateManager.getStorageState();
|
|
1642
|
-
try {
|
|
1643
|
-
await this.storage.saveRollingBackup(state, maxBackups);
|
|
1644
|
-
console.log(`[Processor] Rolling backup saved (max=${maxBackups})`);
|
|
1645
|
-
} catch (err) {
|
|
1646
|
-
console.warn(`[Processor] Rolling backup failed:`, err);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
private async checkAndSyncOpenCode(human: HumanEntity, now: number): Promise<void> {
|
|
1651
|
-
if (this.openCodeImportInProgress) {
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
const opencode = human.settings?.opencode;
|
|
1656
|
-
const pollingInterval = opencode?.polling_interval_ms ?? DEFAULT_OPENCODE_POLLING_MS;
|
|
1657
|
-
const lastSync = opencode?.last_sync ? new Date(opencode.last_sync).getTime() : 0;
|
|
1658
|
-
const timeSinceSync = now - lastSync;
|
|
1659
|
-
|
|
1660
|
-
if (timeSinceSync < pollingInterval && this.lastOpenCodeSync > 0) {
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
this.lastOpenCodeSync = now;
|
|
1665
|
-
const syncTimestamp = new Date().toISOString();
|
|
1666
|
-
this.stateManager.setHuman({
|
|
1667
|
-
...this.stateManager.getHuman(),
|
|
1668
|
-
settings: {
|
|
1669
|
-
...this.stateManager.getHuman().settings,
|
|
1670
|
-
opencode: {
|
|
1671
|
-
...opencode,
|
|
1672
|
-
last_sync: syncTimestamp,
|
|
1673
|
-
},
|
|
1674
|
-
},
|
|
1675
|
-
});
|
|
1676
|
-
|
|
1677
|
-
this.openCodeImportInProgress = true;
|
|
1678
|
-
import("../integrations/opencode/importer.js")
|
|
1679
|
-
.then(({ importOpenCodeSessions }) =>
|
|
1680
|
-
importOpenCodeSessions({
|
|
1681
|
-
stateManager: this.stateManager,
|
|
1682
|
-
interface: this.interface,
|
|
1683
|
-
signal: this.importAbortController.signal,
|
|
1684
|
-
})
|
|
1685
|
-
)
|
|
1686
|
-
.then((result) => {
|
|
1687
|
-
if (result.sessionsProcessed > 0) {
|
|
1688
|
-
console.log(
|
|
1689
|
-
`[Processor] OpenCode sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
1690
|
-
`${result.messagesImported} messages imported, ` +
|
|
1691
|
-
`${result.extractionScansQueued} extraction scans queued`
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
})
|
|
1695
|
-
.catch((err) => {
|
|
1696
|
-
console.warn(`[Processor] OpenCode sync failed:`, err);
|
|
1697
|
-
})
|
|
1698
|
-
.finally(() => {
|
|
1699
|
-
this.openCodeImportInProgress = false;
|
|
1700
|
-
});
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
private async checkAndSyncClaudeCode(human: HumanEntity, now: number): Promise<void> {
|
|
1704
|
-
if (this.claudeCodeImportInProgress) {
|
|
1705
|
-
return;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
const claudeCode = human.settings?.claudeCode;
|
|
1709
|
-
const pollingInterval = claudeCode?.polling_interval_ms ?? DEFAULT_CLAUDE_CODE_POLLING_MS;
|
|
1710
|
-
const lastSync = claudeCode?.last_sync ? new Date(claudeCode.last_sync).getTime() : 0;
|
|
1711
|
-
const timeSinceSync = now - lastSync;
|
|
1712
|
-
|
|
1713
|
-
if (timeSinceSync < pollingInterval && this.lastClaudeCodeSync > 0) {
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
this.lastClaudeCodeSync = now;
|
|
1718
|
-
const syncTimestamp = new Date().toISOString();
|
|
1719
|
-
this.stateManager.setHuman({
|
|
1720
|
-
...this.stateManager.getHuman(),
|
|
1721
|
-
settings: {
|
|
1722
|
-
...this.stateManager.getHuman().settings,
|
|
1723
|
-
claudeCode: {
|
|
1724
|
-
...claudeCode,
|
|
1725
|
-
last_sync: syncTimestamp,
|
|
1726
|
-
},
|
|
1727
|
-
},
|
|
1728
|
-
});
|
|
1729
|
-
|
|
1730
|
-
this.claudeCodeImportInProgress = true;
|
|
1731
|
-
import("../integrations/claude-code/importer.js")
|
|
1732
|
-
.then(({ importClaudeCodeSessions }) =>
|
|
1733
|
-
importClaudeCodeSessions({
|
|
1734
|
-
stateManager: this.stateManager,
|
|
1735
|
-
interface: this.interface,
|
|
1736
|
-
signal: this.importAbortController.signal,
|
|
1737
|
-
})
|
|
1738
|
-
)
|
|
1739
|
-
.then((result) => {
|
|
1740
|
-
if (result.sessionsProcessed > 0) {
|
|
1741
|
-
console.log(
|
|
1742
|
-
`[Processor] Claude Code sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
1743
|
-
`${result.messagesImported} messages imported, ` +
|
|
1744
|
-
`${result.extractionScansQueued} extraction scans queued`
|
|
1745
|
-
);
|
|
1746
|
-
}
|
|
1747
|
-
})
|
|
1748
|
-
.catch((err) => {
|
|
1749
|
-
console.warn(`[Processor] Claude Code sync failed:`, err);
|
|
1750
|
-
})
|
|
1751
|
-
.finally(() => {
|
|
1752
|
-
this.claudeCodeImportInProgress = false;
|
|
1753
|
-
});
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
private async checkAndSyncCursor(human: HumanEntity, now: number): Promise<void> {
|
|
1757
|
-
if (this.cursorImportInProgress) {
|
|
1758
|
-
return;
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
const cursor = human.settings?.cursor;
|
|
1762
|
-
const pollingInterval = cursor?.polling_interval_ms ?? DEFAULT_CURSOR_POLLING_MS;
|
|
1763
|
-
const lastSync = cursor?.last_sync ? new Date(cursor.last_sync).getTime() : 0;
|
|
1764
|
-
const timeSinceSync = now - lastSync;
|
|
1765
|
-
|
|
1766
|
-
if (timeSinceSync < pollingInterval && this.lastCursorSync > 0) {
|
|
1767
|
-
return;
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
this.lastCursorSync = now;
|
|
1771
|
-
const syncTimestamp = new Date().toISOString();
|
|
1772
|
-
this.stateManager.setHuman({
|
|
1773
|
-
...this.stateManager.getHuman(),
|
|
1774
|
-
settings: {
|
|
1775
|
-
...this.stateManager.getHuman().settings,
|
|
1776
|
-
cursor: {
|
|
1777
|
-
...cursor,
|
|
1778
|
-
last_sync: syncTimestamp,
|
|
1779
|
-
},
|
|
1780
|
-
},
|
|
1781
|
-
});
|
|
1782
|
-
|
|
1783
|
-
this.cursorImportInProgress = true;
|
|
1784
|
-
import("../integrations/cursor/importer.js")
|
|
1785
|
-
.then(({ importCursorSessions }) =>
|
|
1786
|
-
importCursorSessions({
|
|
1787
|
-
stateManager: this.stateManager,
|
|
1788
|
-
interface: this.interface,
|
|
1789
|
-
signal: this.importAbortController.signal,
|
|
1790
|
-
})
|
|
1791
|
-
)
|
|
1792
|
-
.then((result) => {
|
|
1793
|
-
if (result.sessionsProcessed > 0) {
|
|
1794
|
-
console.log(
|
|
1795
|
-
`[Processor] Cursor sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
1796
|
-
`${result.messagesImported} messages imported, ` +
|
|
1797
|
-
`${result.extractionScansQueued} extraction scans queued`
|
|
1798
|
-
);
|
|
1799
|
-
}
|
|
1800
|
-
})
|
|
1801
|
-
.catch((err) => {
|
|
1802
|
-
console.warn(`[Processor] Cursor sync failed:`, err);
|
|
1803
|
-
})
|
|
1804
|
-
.finally(() => {
|
|
1805
|
-
this.cursorImportInProgress = false;
|
|
1806
|
-
});
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
private async checkAndSyncCodex(human: HumanEntity, now: number): Promise<void> {
|
|
1810
|
-
if (this.codexImportInProgress) {
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
const codex = human.settings?.codex;
|
|
1815
|
-
const pollingInterval = codex?.polling_interval_ms ?? DEFAULT_CODEX_POLLING_MS;
|
|
1816
|
-
const lastSync = codex?.last_sync ? new Date(codex.last_sync).getTime() : 0;
|
|
1817
|
-
const timeSinceSync = now - lastSync;
|
|
1818
|
-
|
|
1819
|
-
if (timeSinceSync < pollingInterval && this.lastCodexSync > 0) {
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
this.lastCodexSync = now;
|
|
1824
|
-
const syncTimestamp = new Date().toISOString();
|
|
1825
|
-
const currentHuman = this.stateManager.getHuman();
|
|
1826
|
-
this.stateManager.setHuman({
|
|
1827
|
-
...currentHuman,
|
|
1828
|
-
settings: {
|
|
1829
|
-
...currentHuman.settings,
|
|
1830
|
-
codex: {
|
|
1831
|
-
...codex,
|
|
1832
|
-
last_sync: syncTimestamp,
|
|
1833
|
-
},
|
|
1834
|
-
},
|
|
1835
|
-
});
|
|
1836
|
-
|
|
1837
|
-
this.codexImportInProgress = true;
|
|
1838
|
-
import("../integrations/codex/importer.js")
|
|
1839
|
-
.then(({ importCodexSessions }) =>
|
|
1840
|
-
importCodexSessions({
|
|
1841
|
-
stateManager: this.stateManager,
|
|
1842
|
-
interface: this.interface,
|
|
1843
|
-
signal: this.importAbortController.signal,
|
|
1844
|
-
})
|
|
1845
|
-
)
|
|
1846
|
-
.then((result) => {
|
|
1847
|
-
if (result.sessionsProcessed > 0) {
|
|
1848
|
-
console.log(
|
|
1849
|
-
`[Processor] Codex sync complete: ${result.sessionsProcessed} sessions, ` +
|
|
1850
|
-
`${result.messagesImported} messages imported, ` +
|
|
1851
|
-
`${result.extractionScansQueued} extraction scans queued`
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
})
|
|
1855
|
-
.catch((err) => {
|
|
1856
|
-
console.warn(`[Processor] Codex sync failed:`, err);
|
|
1857
|
-
})
|
|
1858
|
-
.finally(() => {
|
|
1859
|
-
this.codexImportInProgress = false;
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
private async checkAndSyncSlack(human: HumanEntity, now: number): Promise<void> {
|
|
1864
|
-
if (this.slackImportInProgress) return;
|
|
1865
|
-
|
|
1866
|
-
const slack = human.settings?.slack;
|
|
1867
|
-
const pollingInterval = slack?.polling_interval_ms ?? 60_000;
|
|
1868
|
-
|
|
1869
|
-
if (now - this.lastSlackSync < pollingInterval && this.lastSlackSync > 0) return;
|
|
1870
|
-
|
|
1871
|
-
this.lastSlackSync = now;
|
|
1872
|
-
|
|
1873
|
-
this.slackImportInProgress = true;
|
|
1874
|
-
import("../integrations/slack/importer.js")
|
|
1875
|
-
.then(({ importSlackChannel }) =>
|
|
1876
|
-
importSlackChannel({
|
|
1877
|
-
stateManager: this.stateManager,
|
|
1878
|
-
interface: this.interface,
|
|
1879
|
-
signal: this.importAbortController.signal,
|
|
1880
|
-
})
|
|
1881
|
-
)
|
|
1882
|
-
.then((result) => {
|
|
1883
|
-
if (result.channelProcessed) {
|
|
1884
|
-
console.log(
|
|
1885
|
-
`[Processor] Slack sync: #${result.channelProcessed} — ` +
|
|
1886
|
-
`${result.messagesImported} messages, ${result.threadsProcessed} threads, ` +
|
|
1887
|
-
`${result.scansQueued} scans queued`
|
|
1888
|
-
);
|
|
1889
|
-
}
|
|
1890
|
-
})
|
|
1891
|
-
.catch((err) => {
|
|
1892
|
-
const msg = err instanceof Error ? err.message : JSON.stringify(err);
|
|
1893
|
-
const stack = err instanceof Error ? err.stack : undefined;
|
|
1894
|
-
console.warn(`[Processor] Slack sync failed: ${msg}${stack ? `\n${stack}` : ''}`);
|
|
1895
|
-
})
|
|
1896
|
-
.finally(() => {
|
|
1897
|
-
this.slackImportInProgress = false;
|
|
1898
|
-
});
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
private personaHistoryImportInProgress = false;
|
|
1902
|
-
|
|
1903
|
-
private async checkAndSyncPersonaHistory(_human: HumanEntity): Promise<void> {
|
|
1904
|
-
if (this.personaHistoryImportInProgress) return;
|
|
1905
|
-
|
|
1906
|
-
this.personaHistoryImportInProgress = true;
|
|
1907
|
-
import("../integrations/persona-history/importer.js")
|
|
1908
|
-
.then(({ importPersonaHistory }) =>
|
|
1909
|
-
importPersonaHistory({ stateManager: this.stateManager })
|
|
1910
|
-
)
|
|
1911
|
-
.then((result) => {
|
|
1912
|
-
if (result.scansQueued > 0) {
|
|
1913
|
-
console.log(
|
|
1914
|
-
`[Processor] PersonaHistory: ${result.scansQueued} scans queued` +
|
|
1915
|
-
(result.complete ? " — import complete" : "")
|
|
1916
|
-
);
|
|
1917
|
-
}
|
|
1918
|
-
})
|
|
1919
|
-
.catch((err) => {
|
|
1920
|
-
console.warn(`[Processor] PersonaHistory sync failed:`, err);
|
|
1921
|
-
})
|
|
1922
|
-
.finally(() => {
|
|
1923
|
-
this.personaHistoryImportInProgress = false;
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
|
-
|
|
1927
793
|
private augmentRoomRequest(request: LLMRequest): LLMRequest {
|
|
1928
794
|
if (request.next_step !== LLMNextStep.HandleRoomResponse) return request;
|
|
1929
795
|
|