ei-tui 0.7.2 → 0.8.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/package.json +1 -1
- package/src/cli/commands/facts.ts +2 -1
- package/src/cli/commands/people.ts +1 -0
- package/src/cli/commands/topics.ts +1 -0
- package/src/cli/mcp.ts +3 -3
- package/src/cli/persona-filter.ts +1 -1
- package/src/cli/retrieval.ts +4 -0
- package/src/cli.ts +1 -1
- package/src/core/handlers/human-matching.ts +2 -0
- package/src/core/llm-client.ts +4 -3
- package/src/core/orchestrators/human-extraction.ts +125 -0
- package/src/core/orchestrators/index.ts +2 -0
- package/src/core/orchestrators/room-extraction.ts +1 -1
- package/src/core/processor.ts +9 -1
- package/src/core/state-manager.ts +9 -9
- package/src/integrations/claude-code/importer.ts +2 -1
- package/src/integrations/cursor/importer.ts +2 -1
- package/src/integrations/machine-id.ts +5 -0
- package/src/integrations/opencode/importer.ts +2 -1
- package/src/prompts/human/index.ts +1 -0
- package/src/prompts/human/person-update.ts +54 -7
- package/src/prompts/human/types.ts +7 -0
- package/tui/src/commands/capture.tsx +102 -4
- package/tui/src/components/WelcomeOverlay.tsx +1 -1
- package/tui/src/context/ei.tsx +66 -28
- package/tui/src/storage/file.ts +10 -1
- package/tui/src/util/help-content.ts +2 -0
- package/tui/src/util/yaml-provider.ts +4 -4
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ export async function execute(query: string, limit: number, options: { recent?:
|
|
|
20
20
|
name: fact.name,
|
|
21
21
|
description: fact.description,
|
|
22
22
|
sentiment: fact.sentiment,
|
|
23
|
-
|
|
23
|
+
validated_date: fact.validated_date,
|
|
24
|
+
sources: fact.sources,
|
|
24
25
|
}));
|
|
25
26
|
}
|
package/src/cli/mcp.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { retrieveBalanced, lookupById, loadLatestState, type BalancedResult } from "./retrieval.js";
|
|
5
|
-
import type { StorageState } from "../core/types
|
|
5
|
+
import type { StorageState } from "../core/types.js";
|
|
6
6
|
import { resolvePersonaId, filterByPersona, filterTypeSpecificByPersona, filterBySource, filterTypeSpecificBySource } from "./persona-filter.js";
|
|
7
7
|
|
|
8
8
|
// Exported so tests can inject their own transport
|
|
@@ -35,7 +35,7 @@ export function createMcpServer(): McpServer {
|
|
|
35
35
|
.string()
|
|
36
36
|
.optional()
|
|
37
37
|
.describe(
|
|
38
|
-
"Filter to entities from a specific source. Prefix match against namespaced source identifiers (e.g. 'cursor', 'opencode', 'opencode:ses_abc123')."
|
|
38
|
+
"Filter to entities from a specific source. Prefix match against namespaced source identifiers (e.g. 'cursor', 'opencode', 'opencode:my-machine', 'opencode:my-machine:ses_abc123')."
|
|
39
39
|
),
|
|
40
40
|
limit: z
|
|
41
41
|
.number()
|
|
@@ -104,7 +104,7 @@ export function createMcpServer(): McpServer {
|
|
|
104
104
|
.string()
|
|
105
105
|
.optional()
|
|
106
106
|
.describe(
|
|
107
|
-
"Filter to entities from a specific source. Prefix match against namespaced source identifiers (e.g. 'cursor', 'opencode', 'opencode:ses_abc123'). If the entity does not match, returns not found."
|
|
107
|
+
"Filter to entities from a specific source. Prefix match against namespaced source identifiers (e.g. 'cursor', 'opencode', 'opencode:my-machine', 'opencode:my-machine:ses_abc123'). If the entity does not match, returns not found."
|
|
108
108
|
),
|
|
109
109
|
},
|
|
110
110
|
},
|
package/src/cli/retrieval.ts
CHANGED
|
@@ -94,6 +94,8 @@ export interface FactResult {
|
|
|
94
94
|
name: string;
|
|
95
95
|
description: string;
|
|
96
96
|
sentiment: number;
|
|
97
|
+
validated_date?: string;
|
|
98
|
+
sources?: string[];
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export interface PersonResult {
|
|
@@ -172,6 +174,8 @@ function mapFact(fact: Fact): FactResult {
|
|
|
172
174
|
name: fact.name,
|
|
173
175
|
description: fact.description,
|
|
174
176
|
sentiment: fact.sentiment,
|
|
177
|
+
validated_date: fact.validated_date,
|
|
178
|
+
sources: fact.sources,
|
|
175
179
|
};
|
|
176
180
|
}
|
|
177
181
|
|
package/src/cli.ts
CHANGED
|
@@ -66,7 +66,7 @@ Options:
|
|
|
66
66
|
--number, -n Maximum number of results (default: 10)
|
|
67
67
|
--recent, -r Sort by last_mentioned date (most recent first)
|
|
68
68
|
--persona, -p Filter to entities a specific persona has learned about
|
|
69
|
-
--source, -s Filter to entities from a specific source (prefix match, e.g. "cursor", "opencode:ses_abc123")
|
|
69
|
+
--source, -s Filter to entities from a specific source (prefix match, e.g. "cursor", "opencode:my-machine", "opencode:my-machine:ses_abc123")
|
|
70
70
|
--id Look up entity by ID (accepts value or stdin)
|
|
71
71
|
--install Register Ei with OpenCode, Claude Code, and Cursor
|
|
72
72
|
--help, -h Show this help message
|
|
@@ -52,6 +52,7 @@ export function handleTopicMatch(response: LLMResponse, state: StateManager): vo
|
|
|
52
52
|
roomId,
|
|
53
53
|
messages_context,
|
|
54
54
|
messages_analyze,
|
|
55
|
+
sources: response.request.data.sources as string[] | undefined,
|
|
55
56
|
candidateName: response.request.data.candidateName as string,
|
|
56
57
|
candidateDescription: response.request.data.candidateDescription as string,
|
|
57
58
|
candidateCategory: response.request.data.candidateCategory as string,
|
|
@@ -99,6 +100,7 @@ export function handlePersonMatch(response: LLMResponse, state: StateManager): v
|
|
|
99
100
|
roomId,
|
|
100
101
|
messages_context,
|
|
101
102
|
messages_analyze,
|
|
103
|
+
sources: response.request.data.sources as string[] | undefined,
|
|
102
104
|
candidateName: response.request.data.candidateName as string,
|
|
103
105
|
candidateDescription: response.request.data.candidateDescription as string,
|
|
104
106
|
candidateRelationship: response.request.data.candidateRelationship as string,
|
package/src/core/llm-client.ts
CHANGED
|
@@ -54,7 +54,7 @@ function buildResolvedModel(account: ProviderAccount, model: ModelConfig): Resol
|
|
|
54
54
|
const apiModelId = model.model_id ?? model.name;
|
|
55
55
|
return {
|
|
56
56
|
provider: account.name,
|
|
57
|
-
model: apiModelId === "
|
|
57
|
+
model: apiModelId === "default" ? undefined : apiModelId,
|
|
58
58
|
config: {
|
|
59
59
|
name: account.name,
|
|
60
60
|
baseURL: account.url,
|
|
@@ -77,6 +77,7 @@ export function resolveModelById(
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export function getDisplayName(account: ProviderAccount, model: ModelConfig): string {
|
|
80
|
+
if (model.name === "default") return account.name;
|
|
80
81
|
return `${account.name}:${model.name}`;
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -119,7 +120,7 @@ export function resolveModel(modelSpec?: string, accounts?: ProviderAccount[]):
|
|
|
119
120
|
(acc) => acc.name.toLowerCase() === searchName.toLowerCase() && acc.enabled && acc.type === "llm"
|
|
120
121
|
);
|
|
121
122
|
if (matchingAccount) {
|
|
122
|
-
const matchingModel = matchingAccount.models?.find((m) => m.name === model);
|
|
123
|
+
const matchingModel = matchingAccount.models?.find((m) => m.name === model || m.model_id === model);
|
|
123
124
|
if (matchingModel) {
|
|
124
125
|
return buildResolvedModel(matchingAccount, matchingModel);
|
|
125
126
|
}
|
|
@@ -162,7 +163,7 @@ function findModelAndAccount(
|
|
|
162
163
|
const account = accounts.find(
|
|
163
164
|
(a) => a.name.toLowerCase() === providerName.toLowerCase() && a.enabled
|
|
164
165
|
);
|
|
165
|
-
const model = account?.models?.find((m) => m.name === modelName);
|
|
166
|
+
const model = account?.models?.find((m) => m.name === modelName || m.model_id === modelName);
|
|
166
167
|
return { model, account };
|
|
167
168
|
}
|
|
168
169
|
// Try matching by model UUID first
|
|
@@ -12,8 +12,10 @@ import {
|
|
|
12
12
|
type TopicScanCandidate,
|
|
13
13
|
type ItemMatchResult,
|
|
14
14
|
type ParticipantContext,
|
|
15
|
+
type PersonaEntitySnapshot,
|
|
15
16
|
} from "../../prompts/human/index.js";
|
|
16
17
|
import { buildValidatePrompt } from "../../prompts/ceremony/dedup.js";
|
|
18
|
+
import { normalizeRoomMessages } from "../handlers/utils.js";
|
|
17
19
|
import { chunkExtractionContext } from "./extraction-chunker.js";
|
|
18
20
|
import { getEmbeddingService, findTopK, getTopicEmbeddingText } from "../embedding-service.js";
|
|
19
21
|
import { resolveTokenLimit } from "../llm-client.js";
|
|
@@ -582,6 +584,18 @@ export function queuePersonUpdate(
|
|
|
582
584
|
|
|
583
585
|
const primaryPersonaIdForUpdate = context.personaId.split("|")[0];
|
|
584
586
|
|
|
587
|
+
const linkedPersonaId = !isNewItem
|
|
588
|
+
? (existingItem?.identifiers ?? []).find(i => i.type.toLowerCase() === 'ei persona')?.value
|
|
589
|
+
: undefined;
|
|
590
|
+
const linkedPersonaEntity = linkedPersonaId ? state.persona_getById(linkedPersonaId) : undefined;
|
|
591
|
+
const personaEntitySnapshot: PersonaEntitySnapshot | undefined = linkedPersonaEntity
|
|
592
|
+
? {
|
|
593
|
+
long_description: linkedPersonaEntity.long_description ?? '',
|
|
594
|
+
traits: (linkedPersonaEntity.traits ?? []).map(t => ({ name: t.name, description: t.description })),
|
|
595
|
+
topics: (linkedPersonaEntity.topics ?? []).map(t => ({ name: t.name, perspective: t.perspective })),
|
|
596
|
+
}
|
|
597
|
+
: undefined;
|
|
598
|
+
|
|
585
599
|
for (const chunk of chunks) {
|
|
586
600
|
const prompt = buildPersonUpdatePrompt({
|
|
587
601
|
existing_item: existingItem,
|
|
@@ -593,6 +607,7 @@ export function queuePersonUpdate(
|
|
|
593
607
|
persona_name: chunk.personaDisplayName,
|
|
594
608
|
participant_context: buildParticipantContext(primaryPersonaIdForUpdate, state),
|
|
595
609
|
known_identifier_types: userIdentifierTypes,
|
|
610
|
+
persona_entity: personaEntitySnapshot,
|
|
596
611
|
});
|
|
597
612
|
|
|
598
613
|
state.queue_enqueue({
|
|
@@ -622,6 +637,116 @@ export function queuePersonUpdate(
|
|
|
622
637
|
return chunks.length;
|
|
623
638
|
}
|
|
624
639
|
|
|
640
|
+
export function queueTargetedPersonUpdate(
|
|
641
|
+
personId: string,
|
|
642
|
+
personaId: string,
|
|
643
|
+
state: StateManager,
|
|
644
|
+
roomId?: string
|
|
645
|
+
): number {
|
|
646
|
+
const existingItem = state.getHuman().people.find(p => p.id === personId) ?? null;
|
|
647
|
+
if (!existingItem) {
|
|
648
|
+
console.warn(`[queueTargetedPersonUpdate] Person ${personId} not found`);
|
|
649
|
+
return 0;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
let allMessages: Message[];
|
|
653
|
+
let contextPersonaId: string;
|
|
654
|
+
let displayName: string;
|
|
655
|
+
|
|
656
|
+
if (roomId) {
|
|
657
|
+
const room = state.getRoom(roomId);
|
|
658
|
+
if (!room) {
|
|
659
|
+
console.warn(`[queueTargetedPersonUpdate] Room ${roomId} not found`);
|
|
660
|
+
return 0;
|
|
661
|
+
}
|
|
662
|
+
allMessages = normalizeRoomMessages(state.getRoomMessages(roomId), state);
|
|
663
|
+
contextPersonaId = room.persona_ids.join("|");
|
|
664
|
+
displayName = room.display_name;
|
|
665
|
+
} else {
|
|
666
|
+
const persona = state.persona_getById(personaId);
|
|
667
|
+
if (!persona) {
|
|
668
|
+
console.warn(`[queueTargetedPersonUpdate] Persona ${personaId} not found`);
|
|
669
|
+
return 0;
|
|
670
|
+
}
|
|
671
|
+
allMessages = state.messages_get(personaId);
|
|
672
|
+
contextPersonaId = personaId;
|
|
673
|
+
displayName = persona.display_name;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (allMessages.length === 0) return 0;
|
|
677
|
+
|
|
678
|
+
const model = state.getHuman().settings?.default_model;
|
|
679
|
+
const context: ExtractionContext & {
|
|
680
|
+
candidateName: string;
|
|
681
|
+
candidateDescription: string;
|
|
682
|
+
candidateRelationship: string;
|
|
683
|
+
extraction_model?: string;
|
|
684
|
+
} = {
|
|
685
|
+
personaId: contextPersonaId,
|
|
686
|
+
personaDisplayName: displayName,
|
|
687
|
+
messages_context: [],
|
|
688
|
+
messages_analyze: allMessages,
|
|
689
|
+
candidateName: existingItem.name,
|
|
690
|
+
candidateDescription: existingItem.description,
|
|
691
|
+
candidateRelationship: existingItem.relationship,
|
|
692
|
+
extraction_model: model,
|
|
693
|
+
roomId,
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
const matchResult: ItemMatchResult = { matched_guid: personId };
|
|
697
|
+
return queuePersonUpdate(matchResult, context, state, existingItem);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export function queueTargetedTopicUpdate(
|
|
701
|
+
topicId: string,
|
|
702
|
+
personaId: string,
|
|
703
|
+
state: StateManager,
|
|
704
|
+
roomId?: string
|
|
705
|
+
): number {
|
|
706
|
+
const existingItem = state.getHuman().topics.find(t => t.id === topicId) ?? null;
|
|
707
|
+
if (!existingItem) {
|
|
708
|
+
console.warn(`[queueTargetedTopicUpdate] Topic ${topicId} not found`);
|
|
709
|
+
return 0;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
let allMessages: Message[];
|
|
713
|
+
let contextPersonaId: string;
|
|
714
|
+
let displayName: string;
|
|
715
|
+
|
|
716
|
+
if (roomId) {
|
|
717
|
+
const room = state.getRoom(roomId);
|
|
718
|
+
if (!room) {
|
|
719
|
+
console.warn(`[queueTargetedTopicUpdate] Room ${roomId} not found`);
|
|
720
|
+
return 0;
|
|
721
|
+
}
|
|
722
|
+
allMessages = normalizeRoomMessages(state.getRoomMessages(roomId), state);
|
|
723
|
+
contextPersonaId = room.persona_ids.join("|");
|
|
724
|
+
displayName = room.display_name;
|
|
725
|
+
} else {
|
|
726
|
+
const persona = state.persona_getById(personaId);
|
|
727
|
+
if (!persona) {
|
|
728
|
+
console.warn(`[queueTargetedTopicUpdate] Persona ${personaId} not found`);
|
|
729
|
+
return 0;
|
|
730
|
+
}
|
|
731
|
+
allMessages = state.messages_get(personaId);
|
|
732
|
+
contextPersonaId = personaId;
|
|
733
|
+
displayName = persona.display_name;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (allMessages.length === 0) return 0;
|
|
737
|
+
|
|
738
|
+
const model = state.getHuman().settings?.default_model;
|
|
739
|
+
const context: ExtractionContext = {
|
|
740
|
+
personaId: contextPersonaId,
|
|
741
|
+
personaDisplayName: displayName,
|
|
742
|
+
messages_context: [],
|
|
743
|
+
messages_analyze: allMessages,
|
|
744
|
+
roomId,
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
return queueDirectTopicUpdate(existingItem, context, state, { extraction_model: model });
|
|
748
|
+
}
|
|
749
|
+
|
|
625
750
|
export async function queueTopicValidate(
|
|
626
751
|
newTopic: Topic,
|
|
627
752
|
state: StateManager,
|
|
@@ -58,7 +58,7 @@ function buildRoomParticipantContext(roomId: string, state: StateManager): Parti
|
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
function getRoomVisibleMessages(state: StateManager, roomId: string): Message[] {
|
|
61
|
+
export function getRoomVisibleMessages(state: StateManager, roomId: string): Message[] {
|
|
62
62
|
const room = state.getRoom(roomId);
|
|
63
63
|
if (!room) return [];
|
|
64
64
|
const rawMessages = room.mode === RoomMode.FreeForAll
|
package/src/core/processor.ts
CHANGED
|
@@ -39,7 +39,7 @@ import { ContextStatus as ContextStatusEnum, RoomMode } from "./types.js";
|
|
|
39
39
|
import { registerReadMemoryExecutor, registerFileReadExecutor } from "./tools/index.js";
|
|
40
40
|
import { createReadMemoryExecutor } from "./tools/builtin/read-memory.js";
|
|
41
41
|
import { EI_WELCOME_MESSAGE, EI_PERSONA_DEFINITION } from "../templates/welcome.js";
|
|
42
|
-
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction } from "./orchestrators/index.js";
|
|
42
|
+
import { shouldStartCeremony, startCeremony, handleCeremonyProgress, queueUserDedupRequest, queueRoomCapture, queuePersonaCapture, checkAndQueueRoomExtraction, queueTargetedPersonUpdate, queueTargetedTopicUpdate } from "./orchestrators/index.js";
|
|
43
43
|
import { BUILT_IN_FACTS } from "./constants/built-in-facts.js";
|
|
44
44
|
import { DEFAULT_SEED_TRAITS } from "./constants/seed-traits.js";
|
|
45
45
|
|
|
@@ -1994,6 +1994,14 @@ const toolNextSteps = new Set([
|
|
|
1994
1994
|
queueRoomCapture(this.stateManager, roomId);
|
|
1995
1995
|
}
|
|
1996
1996
|
|
|
1997
|
+
captureTargetedPerson(personId: string, personaId: string, roomId?: string): number {
|
|
1998
|
+
return queueTargetedPersonUpdate(personId, personaId, this.stateManager, roomId);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
captureTargetedTopic(topicId: string, personaId: string, roomId?: string): number {
|
|
2002
|
+
return queueTargetedTopicUpdate(topicId, personaId, this.stateManager, roomId);
|
|
2003
|
+
}
|
|
2004
|
+
|
|
1997
2005
|
async submitOneShot(guid: string, systemPrompt: string, userPrompt: string): Promise<void> {
|
|
1998
2006
|
return submitOneShot(
|
|
1999
2007
|
this.stateManager,
|
|
@@ -373,11 +373,18 @@ export class StateManager {
|
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
for (const m of account.models) {
|
|
377
|
+
if (m.name === "(default)") {
|
|
378
|
+
m.name = "default";
|
|
379
|
+
if (m.model_id === "(default)") m.model_id = undefined;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
376
383
|
// If still no models, create a placeholder
|
|
377
384
|
if (account.models.length === 0) {
|
|
378
|
-
const model = { id: crypto.randomUUID(), name: "
|
|
385
|
+
const model = { id: crypto.randomUUID(), name: "default" };
|
|
379
386
|
account.models.push(model);
|
|
380
|
-
modelLookup.set(`${account.name}:
|
|
387
|
+
modelLookup.set(`${account.name}:default`, model.id);
|
|
381
388
|
account.default_model = model.id;
|
|
382
389
|
}
|
|
383
390
|
|
|
@@ -816,13 +823,6 @@ export class StateManager {
|
|
|
816
823
|
|
|
817
824
|
messages_remove(personaId: string, messageIds: string[]): Message[] {
|
|
818
825
|
const result = this.personaState.messages_remove(personaId, messageIds);
|
|
819
|
-
const removedIds = new Set(result.map(m => m.id));
|
|
820
|
-
const quotes = this.humanState.get().quotes ?? [];
|
|
821
|
-
for (const quote of quotes) {
|
|
822
|
-
if (quote.message_id && removedIds.has(quote.message_id)) {
|
|
823
|
-
quote.message_id = null;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
826
|
this.scheduleSave();
|
|
827
827
|
return result;
|
|
828
828
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
type ExtractionContext,
|
|
13
13
|
} from "../../core/orchestrators/human-extraction.js";
|
|
14
14
|
import { isProcessRunning } from "../process-check.js";
|
|
15
|
+
import { getMachineId } from "../machine-id.js";
|
|
15
16
|
|
|
16
17
|
// =============================================================================
|
|
17
18
|
// Export Types
|
|
@@ -268,7 +269,7 @@ export async function importClaudeCodeSessions(
|
|
|
268
269
|
personaDisplayName: persona.display_name,
|
|
269
270
|
messages_context: contextMsgs,
|
|
270
271
|
messages_analyze: toAnalyze,
|
|
271
|
-
sources: [`claudecode:${targetSession.id}`],
|
|
272
|
+
sources: [`claudecode:${getMachineId()}:${targetSession.id}`],
|
|
272
273
|
};
|
|
273
274
|
|
|
274
275
|
const ccSettings = stateManager.getHuman().settings?.claudeCode;
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from "./types.js";
|
|
9
9
|
import { CursorReader } from "./reader.js";
|
|
10
10
|
import { isProcessRunning } from "../process-check.js";
|
|
11
|
+
import { getMachineId } from "../machine-id.js";
|
|
11
12
|
import {
|
|
12
13
|
queueAllScans,
|
|
13
14
|
type ExtractionContext,
|
|
@@ -227,7 +228,7 @@ export async function importCursorSessions(
|
|
|
227
228
|
personaDisplayName: persona.display_name,
|
|
228
229
|
messages_context: contextMsgs,
|
|
229
230
|
messages_analyze: toAnalyze,
|
|
230
|
-
sources: [`cursor:${targetSession.id}`],
|
|
231
|
+
sources: [`cursor:${getMachineId()}:${targetSession.id}`],
|
|
231
232
|
};
|
|
232
233
|
|
|
233
234
|
queueAllScans(context, stateManager, { external_filter: "only" });
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type ExtractionContext,
|
|
10
10
|
} from "../../core/orchestrators/human-extraction.js";
|
|
11
11
|
import { isProcessRunning } from "../process-check.js";
|
|
12
|
+
import { getMachineId } from "../machine-id.js";
|
|
12
13
|
|
|
13
14
|
// =============================================================================
|
|
14
15
|
// Constants
|
|
@@ -250,7 +251,7 @@ export async function importOpenCodeSessions(
|
|
|
250
251
|
personaDisplayName: persona.display_name,
|
|
251
252
|
messages_context: contextMsgs,
|
|
252
253
|
messages_analyze: toAnalyze,
|
|
253
|
-
sources: [`opencode:${targetSession.id}`],
|
|
254
|
+
sources: [`opencode:${getMachineId()}:${targetSession.id}`],
|
|
254
255
|
};
|
|
255
256
|
|
|
256
257
|
if (!signal?.aborted) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PromptOutput, ParticipantContext } from "./types.js";
|
|
1
|
+
import type { PromptOutput, ParticipantContext, PersonaEntitySnapshot } from "./types.js";
|
|
2
2
|
import type { Person, Message } from "../../core/types.js";
|
|
3
3
|
import { formatMessagesAsPlaceholders } from "../message-utils.js";
|
|
4
4
|
|
|
@@ -12,6 +12,7 @@ export interface PersonUpdatePromptData {
|
|
|
12
12
|
persona_name: string;
|
|
13
13
|
participant_context?: ParticipantContext;
|
|
14
14
|
known_identifier_types?: string[];
|
|
15
|
+
persona_entity?: PersonaEntitySnapshot;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
function participantContextSection(ctx: ParticipantContext | undefined): string {
|
|
@@ -46,6 +47,9 @@ export function buildPersonUpdatePrompt(data: PersonUpdatePromptData): PromptOut
|
|
|
46
47
|
const personaName = data.persona_name;
|
|
47
48
|
const isNewItem = data.existing_item === null;
|
|
48
49
|
const humanName = data.participant_context?.human_name;
|
|
50
|
+
const isEiPersona = !isNewItem && (data.existing_item?.identifiers ?? []).some(
|
|
51
|
+
i => i.type.toLowerCase() === 'ei persona'
|
|
52
|
+
);
|
|
49
53
|
|
|
50
54
|
const builtInTypes = ['Full Name', 'First Name', 'Nickname', 'Email', 'GitHub', 'Discord',
|
|
51
55
|
'Roblox', 'Reddit', 'Twitter', 'FF14', 'Relationship', 'Ei Persona'];
|
|
@@ -99,19 +103,61 @@ Detail you add should:
|
|
|
99
103
|
2. **NOT** already be present in the record above`;
|
|
100
104
|
|
|
101
105
|
// ── DESCRIPTION SECTION ───────────────────────────────────────────────────
|
|
102
|
-
//
|
|
103
|
-
//
|
|
106
|
+
// Three modes:
|
|
107
|
+
// isNewItem → brief, factual bootstrap (1-3 sentences)
|
|
108
|
+
// isEiPersona → living identity log (accumulate, never truncate)
|
|
109
|
+
// existing regular → synthesize to current-state (3-4 sentences)
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
let descriptionSection: string;
|
|
112
|
+
|
|
113
|
+
if (isNewItem) {
|
|
114
|
+
descriptionSection = `A concise summary of who this person is and how they relate to the HUMAN USER. Keep it brief and factual — only what you can confirm from the conversation.
|
|
107
115
|
|
|
108
116
|
- Capture who this person IS — their role in the user's life
|
|
109
117
|
- Be useful to a persona who's never heard this person's name before
|
|
110
118
|
- 1-3 sentences maximum
|
|
111
119
|
- If you know their birth date or birth year, include it as a date (e.g. "born 1986-10-28") — never as a current age (ages change, dates don't)
|
|
112
120
|
|
|
113
|
-
**ABSOLUTELY VITAL**: Do **NOT** embellish. Record only what the user actually said or demonstrated
|
|
114
|
-
|
|
121
|
+
**ABSOLUTELY VITAL**: Do **NOT** embellish. Record only what the user actually said or demonstrated.`;
|
|
122
|
+
|
|
123
|
+
} else if (isEiPersona) {
|
|
124
|
+
const entityRef = data.persona_entity
|
|
125
|
+
? `## This Persona's Defined Identity (for reference)
|
|
126
|
+
|
|
127
|
+
The following is ${personName}'s current self-definition — their traits, topics, and long description as set in the Persona editor. Use this as a **baseline**, not a ceiling.
|
|
128
|
+
|
|
129
|
+
**Long description:**
|
|
130
|
+
${data.persona_entity.long_description}
|
|
131
|
+
|
|
132
|
+
**Traits:**
|
|
133
|
+
${data.persona_entity.traits.map(t => `- **${t.name}**: ${t.description}`).join('\n')}
|
|
134
|
+
|
|
135
|
+
**Topics:**
|
|
136
|
+
${data.persona_entity.topics.map(t => `- **${t.name}**: ${t.perspective}`).join('\n')}
|
|
137
|
+
|
|
138
|
+
`
|
|
139
|
+
: '';
|
|
140
|
+
|
|
141
|
+
descriptionSection = `${entityRef}This record is the HUMAN USER's **observed experience** of ${personName} over time — not the Persona's own definition. Think of it as field notes from someone who has been talking with this Persona across many conversations.
|
|
142
|
+
|
|
143
|
+
## Your job: add, never truncate
|
|
144
|
+
|
|
145
|
+
The description is allowed to grow. **Never remove or summarize away existing content.**
|
|
146
|
+
|
|
147
|
+
Add anything from the Most Recent Messages that:
|
|
148
|
+
- Extends or nuances what's already known (new behaviors, new opinions, recurring themes)
|
|
149
|
+
- Agrees with or contradicts the Persona's defined identity — both are worth capturing
|
|
150
|
+
- Reveals how the HUMAN USER experiences or relates to this Persona specifically
|
|
151
|
+
|
|
152
|
+
**Do NOT:**
|
|
153
|
+
- Synthesize the existing description down to fewer sentences
|
|
154
|
+
- Replace specific observations with vague summaries
|
|
155
|
+
- Discard detail to "keep it brief" — brevity is wrong here
|
|
156
|
+
|
|
157
|
+
If the new messages add nothing meaningful, return \`{}\`. Otherwise, return the **full updated description** — existing content preserved, new observations woven in.`;
|
|
158
|
+
|
|
159
|
+
} else {
|
|
160
|
+
descriptionSection = `A concise summary of who this person is and how they relate to the HUMAN USER. Personas use this to recognize this person and engage meaningfully when they come up.
|
|
115
161
|
|
|
116
162
|
## CRITICAL: Synthesize, don't accumulate
|
|
117
163
|
|
|
@@ -134,6 +180,7 @@ The description should NOT:
|
|
|
134
180
|
- Record someone's age — record their birth date or birth year instead (e.g. "born 1981" not "age 44")
|
|
135
181
|
|
|
136
182
|
**ABSOLUTELY VITAL**: Do **NOT** embellish — personas use their own voice. Record what the user actually said or demonstrated, not your interpretation of its emotional significance.`;
|
|
183
|
+
}
|
|
137
184
|
|
|
138
185
|
// ── IDENTIFIERS ───────────────────────────────────────────────────────────
|
|
139
186
|
|
|
@@ -12,6 +12,13 @@ export interface ParticipantContext {
|
|
|
12
12
|
human_age?: number; // calculated from Birthday fact — omitted if not set
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** Snapshot of a linked Persona entity — passed to person-update when the Person record has an 'Ei Persona' identifier. */
|
|
16
|
+
export interface PersonaEntitySnapshot {
|
|
17
|
+
long_description: string;
|
|
18
|
+
traits: Array<{ name: string; description: string }>;
|
|
19
|
+
topics: Array<{ name: string; perspective: string }>;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
interface BaseScanPromptData {
|
|
16
23
|
messages_context: Message[];
|
|
17
24
|
messages_analyze: Message[];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { PersonPickerOverlay } from "../components/PersonPickerOverlay.js";
|
|
1
2
|
import type { Command } from "./registry";
|
|
2
3
|
|
|
3
4
|
export const captureCommand: Command = {
|
|
4
5
|
name: "capture",
|
|
5
6
|
aliases: [],
|
|
6
7
|
description: "Trigger extraction on current chat",
|
|
7
|
-
usage: "/capture | /capture opencode",
|
|
8
|
+
usage: "/capture | /capture opencode | /capture person <name> | /capture topic <name>",
|
|
8
9
|
|
|
9
10
|
async execute(args, ctx) {
|
|
10
11
|
if (args.length === 0) {
|
|
@@ -18,12 +19,110 @@ export const captureCommand: Command = {
|
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
const subcommand = args[0].toLowerCase();
|
|
23
|
+
|
|
24
|
+
if (subcommand === "person" || subcommand === "people") {
|
|
25
|
+
const searchTerm = args.slice(1).join(" ").trim();
|
|
26
|
+
if (!searchTerm) {
|
|
27
|
+
ctx.showNotification("Usage: /capture person <name>", "error");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const human = await ctx.ei.getHuman();
|
|
31
|
+
const matches = (human.people ?? []).filter(p =>
|
|
32
|
+
p.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
33
|
+
);
|
|
34
|
+
if (matches.length === 0) {
|
|
35
|
+
ctx.showNotification(`No person matching "${searchTerm}" found`, "warn");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const firePerson = (id: string, name: string) => {
|
|
40
|
+
const queued = ctx.ei.captureTargetedPerson(id);
|
|
41
|
+
if (queued === 0) {
|
|
42
|
+
ctx.showNotification(`No messages to scan for "${name}"`, "warn");
|
|
43
|
+
} else {
|
|
44
|
+
ctx.showNotification(`Re-scan queued for "${name}" (${queued} chunk${queued !== 1 ? "s" : ""})`, "info");
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (matches.length === 1) {
|
|
49
|
+
firePerson(matches[0].id, matches[0].name);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ctx.showOverlay((hideOverlay) => (
|
|
54
|
+
<PersonPickerOverlay
|
|
55
|
+
title={`Multiple matches for "${searchTerm}" — pick one:`}
|
|
56
|
+
people={matches.map(p => ({
|
|
57
|
+
id: p.id,
|
|
58
|
+
name: p.name,
|
|
59
|
+
relationship: p.relationship,
|
|
60
|
+
description: p.description,
|
|
61
|
+
}))}
|
|
62
|
+
onSelect={(picked) => {
|
|
63
|
+
hideOverlay();
|
|
64
|
+
firePerson(picked.id, picked.name);
|
|
65
|
+
}}
|
|
66
|
+
onDismiss={hideOverlay}
|
|
67
|
+
/>
|
|
68
|
+
), ctx.renderer);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (subcommand === "topic") {
|
|
73
|
+
const searchTerm = args.slice(1).join(" ").trim();
|
|
74
|
+
if (!searchTerm) {
|
|
75
|
+
ctx.showNotification("Usage: /capture topic <name>", "error");
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const human = await ctx.ei.getHuman();
|
|
79
|
+
const matches = (human.topics ?? []).filter(t =>
|
|
80
|
+
t.name.toLowerCase().includes(searchTerm.toLowerCase())
|
|
81
|
+
);
|
|
82
|
+
if (matches.length === 0) {
|
|
83
|
+
ctx.showNotification(`No topic matching "${searchTerm}" found`, "warn");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const fireTopic = (id: string, name: string) => {
|
|
88
|
+
const queued = ctx.ei.captureTargetedTopic(id);
|
|
89
|
+
if (queued === 0) {
|
|
90
|
+
ctx.showNotification(`No messages to scan for "${name}"`, "warn");
|
|
91
|
+
} else {
|
|
92
|
+
ctx.showNotification(`Re-scan queued for "${name}" (${queued} chunk${queued !== 1 ? "s" : ""})`, "info");
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (matches.length === 1) {
|
|
97
|
+
fireTopic(matches[0].id, matches[0].name);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
ctx.showOverlay((hideOverlay) => (
|
|
102
|
+
<PersonPickerOverlay
|
|
103
|
+
title={`Multiple matches for "${searchTerm}" — pick one:`}
|
|
104
|
+
people={matches.map(t => ({
|
|
105
|
+
id: t.id,
|
|
106
|
+
name: t.name,
|
|
107
|
+
relationship: t.category,
|
|
108
|
+
description: t.description,
|
|
109
|
+
}))}
|
|
110
|
+
onSelect={(picked) => {
|
|
111
|
+
hideOverlay();
|
|
112
|
+
fireTopic(picked.id, picked.name);
|
|
113
|
+
}}
|
|
114
|
+
onDismiss={hideOverlay}
|
|
115
|
+
/>
|
|
116
|
+
), ctx.renderer);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
21
120
|
const integrationMap: Record<string, "opencode" | "claudeCode" | "cursor"> = {
|
|
22
121
|
opencode: "opencode",
|
|
23
122
|
claudecode: "claudeCode",
|
|
24
123
|
cursor: "cursor",
|
|
25
124
|
};
|
|
26
|
-
const integrationKey = integrationMap[
|
|
125
|
+
const integrationKey = integrationMap[subcommand];
|
|
27
126
|
if (integrationKey) {
|
|
28
127
|
const human = await ctx.ei.getHuman();
|
|
29
128
|
const intSettings = human.settings?.[integrationKey];
|
|
@@ -31,7 +130,6 @@ export const captureCommand: Command = {
|
|
|
31
130
|
ctx.showNotification(`${args[0]} integration not enabled. Enable in /settings.`, "warn");
|
|
32
131
|
return;
|
|
33
132
|
}
|
|
34
|
-
// Reset last_sync to epoch to force immediate scan on next processor tick
|
|
35
133
|
await ctx.ei.updateHuman({
|
|
36
134
|
settings: {
|
|
37
135
|
...human.settings,
|
|
@@ -45,6 +143,6 @@ export const captureCommand: Command = {
|
|
|
45
143
|
return;
|
|
46
144
|
}
|
|
47
145
|
|
|
48
|
-
ctx.showNotification("
|
|
146
|
+
ctx.showNotification("Usage: /capture | /capture person <name> | /capture topic <name> | /capture opencode|claudecode|cursor", "warn");
|
|
49
147
|
},
|
|
50
148
|
};
|
|
@@ -54,7 +54,7 @@ export function WelcomeOverlay(props: WelcomeOverlayProps) {
|
|
|
54
54
|
Options:
|
|
55
55
|
</text>
|
|
56
56
|
<text fg="#93a1a1">
|
|
57
|
-
1. Start
|
|
57
|
+
1. Start LMStudio (port 1234) or Ollama (port 11434)
|
|
58
58
|
</text>
|
|
59
59
|
<text fg="#93a1a1">
|
|
60
60
|
2. Run /provider new to configure a cloud provider
|
package/tui/src/context/ei.tsx
CHANGED
|
@@ -139,6 +139,8 @@ export interface EiContextValue {
|
|
|
139
139
|
markAllRoomMessagesRead: () => Promise<number>;
|
|
140
140
|
captureRoom: () => void;
|
|
141
141
|
capturePersona: () => void;
|
|
142
|
+
captureTargetedPerson: (personId: string) => number;
|
|
143
|
+
captureTargetedTopic: (topicId: string) => number;
|
|
142
144
|
sendSilenceMessage: (silenceReason?: string) => Promise<void>;
|
|
143
145
|
humanRoomMessagePending: () => boolean;
|
|
144
146
|
getArchivedRooms: () => RoomSummary[];
|
|
@@ -655,6 +657,24 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
655
657
|
processor.capturePersona(personaId);
|
|
656
658
|
};
|
|
657
659
|
|
|
660
|
+
const captureTargetedPerson = (personId: string): number => {
|
|
661
|
+
if (!processor) return 0;
|
|
662
|
+
const roomId = store.activeRoomId;
|
|
663
|
+
if (roomId) return processor.captureTargetedPerson(personId, '', roomId);
|
|
664
|
+
const personaId = store.activePersonaId;
|
|
665
|
+
if (!personaId) return 0;
|
|
666
|
+
return processor.captureTargetedPerson(personId, personaId);
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const captureTargetedTopic = (topicId: string): number => {
|
|
670
|
+
if (!processor) return 0;
|
|
671
|
+
const roomId = store.activeRoomId;
|
|
672
|
+
if (roomId) return processor.captureTargetedTopic(topicId, '', roomId);
|
|
673
|
+
const personaId = store.activePersonaId;
|
|
674
|
+
if (!personaId) return 0;
|
|
675
|
+
return processor.captureTargetedTopic(topicId, personaId);
|
|
676
|
+
};
|
|
677
|
+
|
|
658
678
|
const resolveRoomName = (nameOrAlias: string): string | null => {
|
|
659
679
|
if (!processor) return null;
|
|
660
680
|
return processor.resolveRoomName(nameOrAlias);
|
|
@@ -721,41 +741,57 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
721
741
|
logger.info("E2E_SKIP_LOCAL_DETECT active, skipping local LLM check");
|
|
722
742
|
setShowWelcomeOverlay(true);
|
|
723
743
|
} else if (!hasAccounts) {
|
|
724
|
-
logger.info("No LLM accounts configured, checking for local
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
744
|
+
logger.info("No LLM accounts configured, checking for local LLMs...");
|
|
745
|
+
|
|
746
|
+
const candidates = [
|
|
747
|
+
{ name: "LMStudio", url: "http://127.0.0.1:1234/v1" },
|
|
748
|
+
{ name: "Ollama", url: "http://127.0.0.1:11434/v1" },
|
|
749
|
+
];
|
|
750
|
+
|
|
751
|
+
const detected = await Promise.all(
|
|
752
|
+
candidates.map(async (candidate) => {
|
|
753
|
+
try {
|
|
754
|
+
const response = await fetch(`${candidate.url}/models`, {
|
|
755
|
+
method: "GET",
|
|
756
|
+
signal: AbortSignal.timeout(3000),
|
|
757
|
+
});
|
|
758
|
+
return response.ok ? candidate : null;
|
|
759
|
+
} catch {
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
})
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
const found = detected.filter(Boolean) as typeof candidates;
|
|
766
|
+
|
|
767
|
+
if (found.length > 0) {
|
|
768
|
+
const accounts: ProviderAccount[] = found.map((candidate) => {
|
|
732
769
|
const defaultModelId = crypto.randomUUID();
|
|
733
|
-
|
|
770
|
+
return {
|
|
734
771
|
id: crypto.randomUUID(),
|
|
735
|
-
name:
|
|
772
|
+
name: candidate.name,
|
|
736
773
|
type: "llm" as ProviderType,
|
|
737
|
-
url:
|
|
774
|
+
url: candidate.url,
|
|
738
775
|
enabled: true,
|
|
739
776
|
created_at: new Date().toISOString(),
|
|
740
777
|
default_model: defaultModelId,
|
|
741
|
-
models: [{ id: defaultModelId, name: "
|
|
778
|
+
models: [{ id: defaultModelId, name: "default" }],
|
|
742
779
|
};
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
logger.info("No local LLM found, showing welcome overlay");
|
|
780
|
+
});
|
|
781
|
+
const firstDefaultModelId = accounts[0].default_model!;
|
|
782
|
+
const currentHuman = await processor!.getHuman();
|
|
783
|
+
await processor!.updateHuman({
|
|
784
|
+
settings: {
|
|
785
|
+
...currentHuman.settings,
|
|
786
|
+
accounts,
|
|
787
|
+
default_model: firstDefaultModelId,
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
const names = found.map((c) => c.name).join(" and ");
|
|
791
|
+
showNotification(`${names} detected and configured!`, "info");
|
|
792
|
+
logger.info(`Auto-configured: ${names}`);
|
|
793
|
+
} else {
|
|
794
|
+
logger.info("No local LLMs found, showing welcome overlay");
|
|
759
795
|
setShowWelcomeOverlay(true);
|
|
760
796
|
}
|
|
761
797
|
}
|
|
@@ -952,6 +988,8 @@ export const EiProvider: ParentComponent = (props) => {
|
|
|
952
988
|
markAllRoomMessagesRead,
|
|
953
989
|
captureRoom,
|
|
954
990
|
capturePersona,
|
|
991
|
+
captureTargetedPerson,
|
|
992
|
+
captureTargetedTopic,
|
|
955
993
|
sendSilenceMessage,
|
|
956
994
|
humanRoomMessagePending,
|
|
957
995
|
getArchivedRooms,
|
package/tui/src/storage/file.ts
CHANGED
|
@@ -178,7 +178,16 @@ export class FileStorage implements Storage {
|
|
|
178
178
|
while (Date.now() - startTime < LOCK_TIMEOUT_MS) {
|
|
179
179
|
const lockFile = Bun.file(lockPath);
|
|
180
180
|
if (await lockFile.exists()) {
|
|
181
|
-
|
|
181
|
+
// Read may throw if another writer deleted the lock between exists() and text() —
|
|
182
|
+
// treat that as "lock is gone, proceed to acquire" by falling through.
|
|
183
|
+
let lockContent: string;
|
|
184
|
+
try {
|
|
185
|
+
lockContent = await lockFile.text();
|
|
186
|
+
} catch {
|
|
187
|
+
// Lock vanished in the race window — retry from top to re-check state cleanly.
|
|
188
|
+
await new Promise((r) => setTimeout(r, LOCK_RETRY_DELAY_MS));
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
182
191
|
const lockTime = parseInt(lockContent, 10);
|
|
183
192
|
if (!isNaN(lockTime) && Date.now() - lockTime > LOCK_TIMEOUT_MS) {
|
|
184
193
|
try {
|
|
@@ -85,6 +85,8 @@ ROOM COMMANDS
|
|
|
85
85
|
|
|
86
86
|
/capture
|
|
87
87
|
Force-extract quotes, topics, and people from the current chat now.
|
|
88
|
+
/capture person <name> Re-scan all messages for a specific person.
|
|
89
|
+
/capture topic <name> Re-scan all messages for a specific topic.
|
|
88
90
|
|
|
89
91
|
EXTENDED COMMANDS
|
|
90
92
|
/tools
|
|
@@ -77,8 +77,8 @@ export function newProviderToYAML(name?: string): string {
|
|
|
77
77
|
|
|
78
78
|
const modelsYAML = [
|
|
79
79
|
"models:",
|
|
80
|
-
" - name:
|
|
81
|
-
" model_id:
|
|
80
|
+
" - name: default",
|
|
81
|
+
" model_id: default",
|
|
82
82
|
" token_limit: null",
|
|
83
83
|
" max_output_tokens: null",
|
|
84
84
|
" thinking_budget: null",
|
|
@@ -168,8 +168,8 @@ export function providerToYAML(account: ProviderAccount): string {
|
|
|
168
168
|
modelLines.push(` _delete: false`);
|
|
169
169
|
}
|
|
170
170
|
} else {
|
|
171
|
-
modelLines.push(" - name:
|
|
172
|
-
modelLines.push(` model_id:
|
|
171
|
+
modelLines.push(" - name: default");
|
|
172
|
+
modelLines.push(` model_id: default`);
|
|
173
173
|
modelLines.push(` token_limit: null`);
|
|
174
174
|
modelLines.push(` max_output_tokens: null`);
|
|
175
175
|
modelLines.push(` thinking_budget: null`);
|