@siduri-x/core 2.0.2 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/context.d.ts +2 -0
- package/dist/context.js +13 -0
- package/dist/index.d.ts +9 -3
- package/dist/intent-classifier.d.ts +4 -2
- package/dist/intent-classifier.js +32 -1
- package/dist/intent-classifier.test.js +52 -0
- package/dist/memory-settler.d.ts +2 -1
- package/dist/memory-settler.js +9 -1
- package/dist/perception-cycle.test.js +134 -0
- package/dist/perception-pipeline.js +3 -0
- package/dist/prompt-compiler.d.ts +2 -1
- package/dist/prompt-compiler.js +7 -1
- package/dist/proposals.d.ts +4 -1
- package/dist/response-envelope.d.ts +2 -1
- package/dist/response-envelope.js +2 -1
- package/dist/siduri-db.d.ts +25 -9
- package/dist/siduri-db.js +114 -15
- package/package.json +1 -1
package/dist/context.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export interface ConversationContext {
|
|
|
12
12
|
[key: string]: unknown;
|
|
13
13
|
}
|
|
14
14
|
export type SubjectKind = 'actor' | 'companion' | 'configured';
|
|
15
|
+
export type InteractionMode = 'casual' | 'teach' | 'hybrid';
|
|
15
16
|
export interface SubjectRef {
|
|
16
17
|
subjectId: string;
|
|
17
18
|
kind: SubjectKind;
|
|
@@ -23,6 +24,7 @@ export interface RequestContext {
|
|
|
23
24
|
conversation: ConversationContext;
|
|
24
25
|
source?: 'local' | 'external' | string;
|
|
25
26
|
subject?: SubjectRef;
|
|
27
|
+
mode?: InteractionMode;
|
|
26
28
|
metadata?: Record<string, unknown>;
|
|
27
29
|
}
|
|
28
30
|
export type DiagnosticCode = 'legacy_role_removed' | 'anonymous_session_generated' | 'companion_default_mapped_for_bootstrap' | 'actor_scoped_subject_mapped' | 'role_escalation_attempt_suppressed' | 'capability_escalation_attempt_suppressed';
|
package/dist/context.js
CHANGED
|
@@ -55,6 +55,19 @@ function validateRequestContext(context) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
+
if (ctx.mode !== undefined) {
|
|
59
|
+
if (ctx.mode !== 'casual' && ctx.mode !== 'teach' && ctx.mode !== 'hybrid') {
|
|
60
|
+
return {
|
|
61
|
+
accepted: false,
|
|
62
|
+
error: {
|
|
63
|
+
code: 'INVALID_CONTEXT',
|
|
64
|
+
message: `Invalid interaction mode: '${ctx.mode}' (expected 'casual', 'teach', or 'hybrid')`,
|
|
65
|
+
field: 'mode',
|
|
66
|
+
correlationId: ctx.conversation?.correlationId,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
58
71
|
if (missingFields.length > 0) {
|
|
59
72
|
return {
|
|
60
73
|
accepted: false,
|
package/dist/index.d.ts
CHANGED
|
@@ -255,19 +255,25 @@ export interface HealthProbeResult {
|
|
|
255
255
|
}
|
|
256
256
|
export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
|
|
257
257
|
export * from './mouth-types';
|
|
258
|
-
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
258
|
+
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
259
|
+
export type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent, };
|
|
259
260
|
export interface SelfRepository {
|
|
260
261
|
getIdentity(companionId: string): Promise<SelfIdentity | undefined>;
|
|
261
|
-
getPersonality(companionId: string): Promise<PersonalityTraits>;
|
|
262
|
+
getPersonality?(companionId: string): Promise<PersonalityTraits>;
|
|
262
263
|
getActiveDirectives(companionId: string): Promise<SelfDirective[]>;
|
|
263
264
|
getRelationship(companionId: string, entityId: string): Promise<SelfRelationship | null>;
|
|
265
|
+
getRelationships?(companionId: string): Promise<SelfRelationship[]>;
|
|
266
|
+
getExemplars?(companionId: string): Promise<SelfDialogueExample[]>;
|
|
267
|
+
setExemplars?(companionId: string, exemplars: SelfDialogueExample[]): Promise<void>;
|
|
264
268
|
commitDirectives(companionId: string, directives: SelfDirective[]): Promise<void>;
|
|
265
269
|
updateRelationship(companionId: string, rel: SelfRelationship): Promise<void>;
|
|
266
270
|
disableDirective?(id: string): Promise<void>;
|
|
267
271
|
getActiveSelf?(companionId: string): Promise<{
|
|
268
272
|
identity?: SelfIdentity;
|
|
269
|
-
personality
|
|
273
|
+
personality?: PersonalityTraits;
|
|
270
274
|
directives: SelfDirective[];
|
|
275
|
+
relationships?: SelfRelationship[];
|
|
276
|
+
exemplars?: SelfDialogueExample[];
|
|
271
277
|
}>;
|
|
272
278
|
}
|
|
273
279
|
export interface LifeDatabase {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RequestContext } from './index';
|
|
1
|
+
import { RequestContext, InteractionMode } from './index';
|
|
2
2
|
import { extractDeterministicTeaching } from './teaching';
|
|
3
3
|
export interface IntentClassification {
|
|
4
4
|
normalizedMessage: string;
|
|
@@ -7,13 +7,15 @@ export interface IntentClassification {
|
|
|
7
7
|
isSelfIdentityRequest: boolean;
|
|
8
8
|
isGreeting: boolean;
|
|
9
9
|
shouldQueryKnowledge: boolean;
|
|
10
|
+
effectiveMode: InteractionMode;
|
|
10
11
|
confidence?: number;
|
|
11
12
|
classifierOrigin?: 'heuristic' | 'cognitive' | 'ear';
|
|
12
13
|
}
|
|
13
14
|
export type CognitiveIntentClassifier = (text: string, context: RequestContext) => Promise<Partial<IntentClassification>> | Partial<IntentClassification>;
|
|
14
15
|
/**
|
|
15
16
|
* Evaluates domain heuristics and intent classification for user messages.
|
|
16
|
-
* Determines if deterministic teaching is present
|
|
17
|
+
* Determines if deterministic teaching is present, resolves the effective
|
|
18
|
+
* interaction mode (casual, teach, hybrid), and determines if external knowledge
|
|
17
19
|
* retrieval is appropriate.
|
|
18
20
|
*/
|
|
19
21
|
export declare function classifyInputIntent(text: string, context: RequestContext, overrides?: Partial<IntentClassification>): IntentClassification;
|
|
@@ -5,7 +5,8 @@ exports.classifyInputIntentAsync = classifyInputIntentAsync;
|
|
|
5
5
|
const teaching_1 = require("./teaching");
|
|
6
6
|
/**
|
|
7
7
|
* Evaluates domain heuristics and intent classification for user messages.
|
|
8
|
-
* Determines if deterministic teaching is present
|
|
8
|
+
* Determines if deterministic teaching is present, resolves the effective
|
|
9
|
+
* interaction mode (casual, teach, hybrid), and determines if external knowledge
|
|
9
10
|
* retrieval is appropriate.
|
|
10
11
|
*/
|
|
11
12
|
function classifyInputIntent(text, context, overrides) {
|
|
@@ -21,6 +22,35 @@ function classifyInputIntent(text, context, overrides) {
|
|
|
21
22
|
/^(?:hello|hi|hey|greetings|good morning|good afternoon|good evening|howdy|yo)[.!]?$/.test(normalizedMessage);
|
|
22
23
|
const shouldQueryKnowledge = overrides?.shouldQueryKnowledge ??
|
|
23
24
|
(!isTeachingLike && !isSelfIdentityRequest && !isGreeting);
|
|
25
|
+
// Multi-tier Interaction Mode Resolution:
|
|
26
|
+
// 1. Overrides / Cognitive Classifier
|
|
27
|
+
// 2. Explicit Request Override (context.mode)
|
|
28
|
+
// 3. Security Boundary: public channel or external source forces 'casual' (Zero Memory Drift)
|
|
29
|
+
// 4. In-dialogue semantic cues (!casual, !teach, "remember that...", etc.)
|
|
30
|
+
// 5. Default companion baseline: 'hybrid' (salience filtering)
|
|
31
|
+
let effectiveMode;
|
|
32
|
+
if (overrides?.effectiveMode) {
|
|
33
|
+
effectiveMode = overrides.effectiveMode;
|
|
34
|
+
}
|
|
35
|
+
else if (context.mode) {
|
|
36
|
+
effectiveMode = context.mode;
|
|
37
|
+
}
|
|
38
|
+
else if (context.conversation?.channel === 'public' || context.source === 'external') {
|
|
39
|
+
effectiveMode = 'casual';
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const isExplicitCasual = /^(?:!casual|\/casual|\bcasual mode\b|\bjust chatting\b|\boff the record\b)/i.test(normalizedMessage);
|
|
43
|
+
const isExplicitTeach = /^(?:!teach|\/teach|\bteach mode\b|\blearn this rule\b)/i.test(normalizedMessage);
|
|
44
|
+
if (isExplicitCasual) {
|
|
45
|
+
effectiveMode = 'casual';
|
|
46
|
+
}
|
|
47
|
+
else if (isExplicitTeach || isTeachingLike) {
|
|
48
|
+
effectiveMode = 'teach';
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
effectiveMode = 'hybrid';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
24
54
|
return {
|
|
25
55
|
normalizedMessage,
|
|
26
56
|
explicitTeaching,
|
|
@@ -28,6 +58,7 @@ function classifyInputIntent(text, context, overrides) {
|
|
|
28
58
|
isSelfIdentityRequest,
|
|
29
59
|
isGreeting,
|
|
30
60
|
shouldQueryKnowledge,
|
|
61
|
+
effectiveMode,
|
|
31
62
|
confidence: overrides?.confidence ?? 0.95,
|
|
32
63
|
classifierOrigin: overrides?.classifierOrigin ?? 'heuristic',
|
|
33
64
|
};
|
|
@@ -64,4 +64,56 @@ describe('IntentClassifier', () => {
|
|
|
64
64
|
expect(result.shouldQueryKnowledge).toBe(true);
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
+
describe('Multi-tier Interaction Mode Resolution (Casual, Teach, Hybrid)', () => {
|
|
68
|
+
test('defaults to hybrid mode for standard companion conversation', () => {
|
|
69
|
+
const result = (0, intent_classifier_1.classifyInputIntent)('What is the weather today?', dummyContext);
|
|
70
|
+
expect(result.effectiveMode).toBe('hybrid');
|
|
71
|
+
});
|
|
72
|
+
test('resolves to teach mode on in-dialogue teaching cues and commands', () => {
|
|
73
|
+
expect((0, intent_classifier_1.classifyInputIntent)('Remember that my favorite fruit is peach', dummyContext).effectiveMode).toBe('teach');
|
|
74
|
+
expect((0, intent_classifier_1.classifyInputIntent)('!teach from now on always be concise', dummyContext).effectiveMode).toBe('teach');
|
|
75
|
+
expect((0, intent_classifier_1.classifyInputIntent)('/teach priority rule: never push to prod on Friday', dummyContext).effectiveMode).toBe('teach');
|
|
76
|
+
expect((0, intent_classifier_1.classifyInputIntent)('Teach mode: address me as Operator', dummyContext).effectiveMode).toBe('teach');
|
|
77
|
+
});
|
|
78
|
+
test('resolves to casual mode on in-dialogue casual cues and commands', () => {
|
|
79
|
+
expect((0, intent_classifier_1.classifyInputIntent)('!casual how are you doing?', dummyContext).effectiveMode).toBe('casual');
|
|
80
|
+
expect((0, intent_classifier_1.classifyInputIntent)('/casual tell me a joke', dummyContext).effectiveMode).toBe('casual');
|
|
81
|
+
expect((0, intent_classifier_1.classifyInputIntent)('just chatting: what do you think of space?', dummyContext).effectiveMode).toBe('casual');
|
|
82
|
+
expect((0, intent_classifier_1.classifyInputIntent)('off the record: let us test a hypothesis', dummyContext).effectiveMode).toBe('casual');
|
|
83
|
+
});
|
|
84
|
+
test('enforces casual mode (Zero Memory Drift) on public channel or external source boundary', () => {
|
|
85
|
+
const publicContext = {
|
|
86
|
+
...dummyContext,
|
|
87
|
+
conversation: { channel: 'public', correlationId: 'corr-pub' },
|
|
88
|
+
};
|
|
89
|
+
// Even if user attempts teaching in a public streaming channel, mode is locked to casual
|
|
90
|
+
const pubResult = (0, intent_classifier_1.classifyInputIntent)('Remember that the secret password is 123', publicContext);
|
|
91
|
+
expect(pubResult.effectiveMode).toBe('casual');
|
|
92
|
+
const externalContext = {
|
|
93
|
+
...dummyContext,
|
|
94
|
+
source: 'external',
|
|
95
|
+
};
|
|
96
|
+
const extResult = (0, intent_classifier_1.classifyInputIntent)('Remember that I am admin', externalContext);
|
|
97
|
+
expect(extResult.effectiveMode).toBe('casual');
|
|
98
|
+
});
|
|
99
|
+
test('explicit context.mode override takes highest precedence', () => {
|
|
100
|
+
const explicitCasual = {
|
|
101
|
+
...dummyContext,
|
|
102
|
+
mode: 'casual',
|
|
103
|
+
};
|
|
104
|
+
// Explicit casual suppresses even explicit teach commands
|
|
105
|
+
expect((0, intent_classifier_1.classifyInputIntent)('!teach remember my name is Zagin', explicitCasual).effectiveMode).toBe('casual');
|
|
106
|
+
const explicitTeach = {
|
|
107
|
+
...dummyContext,
|
|
108
|
+
mode: 'teach',
|
|
109
|
+
};
|
|
110
|
+
// Explicit teach forces teach mode even for casual greetings
|
|
111
|
+
expect((0, intent_classifier_1.classifyInputIntent)('Hello!', explicitTeach).effectiveMode).toBe('teach');
|
|
112
|
+
const explicitHybrid = {
|
|
113
|
+
...dummyContext,
|
|
114
|
+
mode: 'hybrid',
|
|
115
|
+
};
|
|
116
|
+
expect((0, intent_classifier_1.classifyInputIntent)('Just casual banter', explicitHybrid).effectiveMode).toBe('hybrid');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
67
119
|
});
|
package/dist/memory-settler.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MemoryOrgan, Claim, ResponsePlan, RequestContext } from './index';
|
|
1
|
+
import { MemoryOrgan, Claim, ResponsePlan, RequestContext, InteractionMode } from './index';
|
|
2
2
|
import { extractDeterministicTeaching } from './teaching';
|
|
3
3
|
export interface MemorySettlementParams {
|
|
4
4
|
companionId: string;
|
|
@@ -8,6 +8,7 @@ export interface MemorySettlementParams {
|
|
|
8
8
|
memory?: MemoryOrgan;
|
|
9
9
|
explicitTeaching: ReturnType<typeof extractDeterministicTeaching>;
|
|
10
10
|
plan: ResponsePlan;
|
|
11
|
+
effectiveMode?: InteractionMode;
|
|
11
12
|
}
|
|
12
13
|
export interface MemoryProposalReceipt {
|
|
13
14
|
proposal_id: string;
|
package/dist/memory-settler.js
CHANGED
|
@@ -6,7 +6,15 @@ exports.settleMemoryProposals = settleMemoryProposals;
|
|
|
6
6
|
* and LLM behavior proposals to the MemoryOrgan.
|
|
7
7
|
*/
|
|
8
8
|
async function settleMemoryProposals(params) {
|
|
9
|
-
const { companionId, perceivedText, role, requestContext, memory, explicitTeaching, plan, } = params;
|
|
9
|
+
const { companionId, perceivedText, role, requestContext, memory, explicitTeaching, plan, effectiveMode, } = params;
|
|
10
|
+
// Zero Memory Drift: Casual mode completely suppresses all proposal generation
|
|
11
|
+
const mode = effectiveMode || requestContext.mode || 'hybrid';
|
|
12
|
+
if (mode === 'casual') {
|
|
13
|
+
return {
|
|
14
|
+
createdMemoryProposals: [],
|
|
15
|
+
memoryProposalReceipts: [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
10
18
|
const createdMemoryProposals = [];
|
|
11
19
|
let sourceEventId;
|
|
12
20
|
const hasTeaching = explicitTeaching.claims.length > 0 ||
|
|
@@ -150,4 +150,138 @@ describe('SiduriRuntime Unified Perception Cycle & Session History', () => {
|
|
|
150
150
|
expect(response.status).toBe('APPROVED');
|
|
151
151
|
expect(response.metadata.evidence_ids).toContain('ev-native-provenance-100');
|
|
152
152
|
});
|
|
153
|
+
describe('Three Interaction Modes Execution (Casual, Teach, Hybrid)', () => {
|
|
154
|
+
test('Casual Mode enforces Zero Memory Drift: suppresses all memory/directive proposals', async () => {
|
|
155
|
+
const mockMemory = {
|
|
156
|
+
proposeClaim: jest.fn(),
|
|
157
|
+
proposeDirective: jest.fn(),
|
|
158
|
+
addSourceEvent: jest.fn(),
|
|
159
|
+
};
|
|
160
|
+
const mockBrain = {
|
|
161
|
+
generatePlan: jest.fn().mockResolvedValue({
|
|
162
|
+
speech: 'Got it, Alice.',
|
|
163
|
+
language: 'en',
|
|
164
|
+
memoryProposals: [
|
|
165
|
+
{ subject: 'actor:alice', predicate: 'mood', value: 'happy' },
|
|
166
|
+
],
|
|
167
|
+
}),
|
|
168
|
+
};
|
|
169
|
+
const runtime = new runtime_1.SiduriRuntime('comp-casual', { name: 'CasualBot' }, {
|
|
170
|
+
memory: mockMemory,
|
|
171
|
+
brain: mockBrain,
|
|
172
|
+
});
|
|
173
|
+
const casualContext = {
|
|
174
|
+
companionId: 'comp-casual',
|
|
175
|
+
mode: 'casual',
|
|
176
|
+
actor: {
|
|
177
|
+
actorId: 'alice',
|
|
178
|
+
sessionId: 'sess-casual',
|
|
179
|
+
authenticated: true,
|
|
180
|
+
},
|
|
181
|
+
conversation: {
|
|
182
|
+
channel: 'direct',
|
|
183
|
+
correlationId: 'corr-casual-1',
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
// User says a teaching statement in Casual mode
|
|
187
|
+
const response = await runtime.processPerception({
|
|
188
|
+
source: 'text_chat',
|
|
189
|
+
text: 'Remember that my name is Alice and I love coffee',
|
|
190
|
+
context: casualContext,
|
|
191
|
+
});
|
|
192
|
+
expect(response.status).toBe('APPROVED');
|
|
193
|
+
expect(response.metadata.mode).toBe('casual');
|
|
194
|
+
// ZERO writes to memory: no source events, no proposed claims
|
|
195
|
+
expect(mockMemory.addSourceEvent).not.toHaveBeenCalled();
|
|
196
|
+
expect(mockMemory.proposeClaim).not.toHaveBeenCalled();
|
|
197
|
+
expect(mockMemory.proposeDirective).not.toHaveBeenCalled();
|
|
198
|
+
expect(response.metadata.proposals).toHaveLength(0);
|
|
199
|
+
expect(response.metadata.memory_proposals).toHaveLength(0);
|
|
200
|
+
});
|
|
201
|
+
test('Teach Mode persists proposals and reflects mode in metadata', async () => {
|
|
202
|
+
const mockMemory = {
|
|
203
|
+
proposeClaim: jest.fn().mockImplementation(async (c) => ({
|
|
204
|
+
id: 'claim-prop-1',
|
|
205
|
+
...c,
|
|
206
|
+
status: 'PENDING',
|
|
207
|
+
})),
|
|
208
|
+
proposeDirective: jest.fn().mockResolvedValue(undefined),
|
|
209
|
+
addSourceEvent: jest.fn().mockResolvedValue(undefined),
|
|
210
|
+
};
|
|
211
|
+
const mockBrain = {
|
|
212
|
+
generatePlan: jest.fn().mockResolvedValue({
|
|
213
|
+
speech: 'I have recorded your preferred title as Chief Engineer.',
|
|
214
|
+
language: 'en',
|
|
215
|
+
}),
|
|
216
|
+
};
|
|
217
|
+
const runtime = new runtime_1.SiduriRuntime('comp-teach', { name: 'TeachBot' }, {
|
|
218
|
+
memory: mockMemory,
|
|
219
|
+
brain: mockBrain,
|
|
220
|
+
});
|
|
221
|
+
const teachContext = {
|
|
222
|
+
companionId: 'comp-teach',
|
|
223
|
+
mode: 'teach',
|
|
224
|
+
actor: {
|
|
225
|
+
actorId: 'alice',
|
|
226
|
+
sessionId: 'sess-teach',
|
|
227
|
+
authenticated: true,
|
|
228
|
+
},
|
|
229
|
+
conversation: {
|
|
230
|
+
channel: 'direct',
|
|
231
|
+
correlationId: 'corr-teach-1',
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
const response = await runtime.processPerception({
|
|
235
|
+
source: 'text_chat',
|
|
236
|
+
text: 'From now on, call me Chief Engineer',
|
|
237
|
+
context: teachContext,
|
|
238
|
+
});
|
|
239
|
+
expect(response.status).toBe('APPROVED');
|
|
240
|
+
expect(response.metadata.mode).toBe('teach');
|
|
241
|
+
expect(mockMemory.proposeClaim).toHaveBeenCalled();
|
|
242
|
+
expect(response.metadata.proposals).toHaveLength(1);
|
|
243
|
+
});
|
|
244
|
+
test('Infers Teach Mode semantically when user uses in-dialogue teaching command', async () => {
|
|
245
|
+
const mockMemory = {
|
|
246
|
+
proposeClaim: jest.fn().mockImplementation(async (c) => ({
|
|
247
|
+
id: 'claim-prop-2',
|
|
248
|
+
...c,
|
|
249
|
+
status: 'PENDING',
|
|
250
|
+
})),
|
|
251
|
+
proposeDirective: jest.fn().mockResolvedValue(undefined),
|
|
252
|
+
addSourceEvent: jest.fn().mockResolvedValue(undefined),
|
|
253
|
+
};
|
|
254
|
+
const mockBrain = {
|
|
255
|
+
generatePlan: jest.fn().mockResolvedValue({
|
|
256
|
+
speech: 'Recorded the command.',
|
|
257
|
+
language: 'en',
|
|
258
|
+
}),
|
|
259
|
+
};
|
|
260
|
+
const runtime = new runtime_1.SiduriRuntime('comp-infer', { name: 'InferBot' }, {
|
|
261
|
+
memory: mockMemory,
|
|
262
|
+
brain: mockBrain,
|
|
263
|
+
});
|
|
264
|
+
// No explicit mode override in context
|
|
265
|
+
const defaultContext = {
|
|
266
|
+
companionId: 'comp-infer',
|
|
267
|
+
actor: {
|
|
268
|
+
actorId: 'alice',
|
|
269
|
+
sessionId: 'sess-infer',
|
|
270
|
+
authenticated: true,
|
|
271
|
+
},
|
|
272
|
+
conversation: {
|
|
273
|
+
channel: 'direct',
|
|
274
|
+
correlationId: 'corr-infer-1',
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
const response = await runtime.processPerception({
|
|
278
|
+
source: 'text_chat',
|
|
279
|
+
text: '!teach remember that your name is Atlas',
|
|
280
|
+
context: defaultContext,
|
|
281
|
+
});
|
|
282
|
+
expect(response.status).toBe('APPROVED');
|
|
283
|
+
expect(response.metadata.mode).toBe('teach');
|
|
284
|
+
expect(mockMemory.proposeClaim).toHaveBeenCalled();
|
|
285
|
+
});
|
|
286
|
+
});
|
|
153
287
|
});
|
|
@@ -92,6 +92,7 @@ const promptCompilationStage = async (context) => {
|
|
|
92
92
|
knowledgeData: context.contextRetrieval.knowledgeData,
|
|
93
93
|
memoryData: context.contextRetrieval.memoryData,
|
|
94
94
|
lifeContext: context.contextRetrieval.lifeContext,
|
|
95
|
+
effectiveMode: context.intent?.effectiveMode,
|
|
95
96
|
});
|
|
96
97
|
context.prompts = prompts;
|
|
97
98
|
};
|
|
@@ -146,6 +147,7 @@ const memorySettlementStage = async (context) => {
|
|
|
146
147
|
memory: context.organs.memory,
|
|
147
148
|
explicitTeaching: context.intent.explicitTeaching,
|
|
148
149
|
plan: context.plan,
|
|
150
|
+
effectiveMode: context.intent.effectiveMode,
|
|
149
151
|
});
|
|
150
152
|
context.memorySettlement = memorySettlement;
|
|
151
153
|
};
|
|
@@ -233,6 +235,7 @@ const envelopeAssemblyStage = async (context) => {
|
|
|
233
235
|
subsystemDiagnostics: context.contextRetrieval.subsystemDiagnostics,
|
|
234
236
|
experienceEvents: context.experienceEmission?.experienceEvents || [],
|
|
235
237
|
mouthDelivery: context.mouthDelivery,
|
|
238
|
+
effectiveMode: context.intent?.effectiveMode,
|
|
236
239
|
});
|
|
237
240
|
context.responseEnvelope = envelope;
|
|
238
241
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BehaviorOrgan, BehaviorDirective, KnowledgeItem, Claim, RequestContext } from './index';
|
|
1
|
+
import { BehaviorOrgan, BehaviorDirective, KnowledgeItem, Claim, RequestContext, InteractionMode } from './index';
|
|
2
2
|
export interface PromptCompilationParams {
|
|
3
3
|
companionName: string;
|
|
4
4
|
companionId: string;
|
|
@@ -10,6 +10,7 @@ export interface PromptCompilationParams {
|
|
|
10
10
|
knowledgeData: KnowledgeItem[];
|
|
11
11
|
memoryData: Claim[];
|
|
12
12
|
lifeContext?: string[];
|
|
13
|
+
effectiveMode?: InteractionMode;
|
|
13
14
|
}
|
|
14
15
|
export interface CompiledPrompts {
|
|
15
16
|
systemPrompt: string;
|
package/dist/prompt-compiler.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.compilePrompts = compilePrompts;
|
|
|
5
5
|
* Compiles neutral system prompt and formatted context prompt from structured data.
|
|
6
6
|
*/
|
|
7
7
|
async function compilePrompts(params) {
|
|
8
|
-
const { companionName, companionId, role, requestContext, behavior, activeDirectives, subsystemDiagnostics, knowledgeData, memoryData, lifeContext, } = params;
|
|
8
|
+
const { companionName, companionId, role, requestContext, behavior, activeDirectives, subsystemDiagnostics, knowledgeData, memoryData, lifeContext, effectiveMode, } = params;
|
|
9
9
|
let contextPrompt = '';
|
|
10
10
|
if (Object.keys(subsystemDiagnostics).length > 0) {
|
|
11
11
|
contextPrompt +=
|
|
@@ -43,8 +43,14 @@ async function compilePrompts(params) {
|
|
|
43
43
|
actorId: requestContext.actor.actorId,
|
|
44
44
|
})
|
|
45
45
|
: '';
|
|
46
|
+
const modeInstruction = effectiveMode === 'casual'
|
|
47
|
+
? 'Operating Mode: Casual (Zero memory drift - do not attempt to persist personal claims or directives).'
|
|
48
|
+
: effectiveMode === 'teach'
|
|
49
|
+
? 'Operating Mode: Teach Mode (Active learning session - accurately capture user preferences and proposed boundaries for operator review).'
|
|
50
|
+
: undefined;
|
|
46
51
|
const systemPrompt = [
|
|
47
52
|
`You are ${companionName}.`,
|
|
53
|
+
modeInstruction,
|
|
48
54
|
'This is a neutral conversation context.',
|
|
49
55
|
'Use only approved, permitted memory as factual personal context.',
|
|
50
56
|
'Do not claim prior personal knowledge when no approved memory supports it.',
|
package/dist/proposals.d.ts
CHANGED
|
@@ -20,7 +20,10 @@ export interface MemoryProposal {
|
|
|
20
20
|
}
|
|
21
21
|
export interface BehaviorProposal {
|
|
22
22
|
directive: string;
|
|
23
|
-
priority
|
|
23
|
+
priority?: number;
|
|
24
|
+
category?: 'guardrail' | 'relational' | 'behavioral' | string;
|
|
25
|
+
scopeActor?: string;
|
|
26
|
+
supersedesId?: string;
|
|
24
27
|
subject?: string;
|
|
25
28
|
predicate?: string;
|
|
26
29
|
value?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { StagedResponsePlan, ResponseGateEvaluation, Claim, ResponseCitation, ExperienceEvent, ActionExecutionResult } from './index';
|
|
1
|
+
import { StagedResponsePlan, ResponseGateEvaluation, Claim, ResponseCitation, ExperienceEvent, ActionExecutionResult, InteractionMode } from './index';
|
|
2
2
|
import { MemoryProposalReceipt } from './memory-settler';
|
|
3
3
|
import { FormattedMouthOutput } from './mouth-types';
|
|
4
4
|
export interface AssembleResponseEnvelopeParams {
|
|
@@ -14,6 +14,7 @@ export interface AssembleResponseEnvelopeParams {
|
|
|
14
14
|
subsystemDiagnostics: Record<string, string>;
|
|
15
15
|
experienceEvents: ExperienceEvent[];
|
|
16
16
|
mouthDelivery?: FormattedMouthOutput;
|
|
17
|
+
effectiveMode?: InteractionMode;
|
|
17
18
|
}
|
|
18
19
|
/**
|
|
19
20
|
* Creates the standardized rejection envelope for responses that fail gate evaluation.
|
|
@@ -29,7 +29,7 @@ function createGateRejectionEnvelope(stagedPlan, gateEval) {
|
|
|
29
29
|
* Assembles the standardized response structure for approved companion responses.
|
|
30
30
|
*/
|
|
31
31
|
function assembleResponseEnvelope(params) {
|
|
32
|
-
const { stagedPlan, speech, language, speechId, createdMemoryProposals, memoryProposalReceipts, actionResults, filteredEvidenceIds, filteredCitations, subsystemDiagnostics, experienceEvents, mouthDelivery, } = params;
|
|
32
|
+
const { stagedPlan, speech, language, speechId, createdMemoryProposals, memoryProposalReceipts, actionResults, filteredEvidenceIds, filteredCitations, subsystemDiagnostics, experienceEvents, mouthDelivery, effectiveMode, } = params;
|
|
33
33
|
return {
|
|
34
34
|
status: 'APPROVED',
|
|
35
35
|
response_id: stagedPlan.responseId,
|
|
@@ -42,6 +42,7 @@ function assembleResponseEnvelope(params) {
|
|
|
42
42
|
},
|
|
43
43
|
delivery: mouthDelivery,
|
|
44
44
|
metadata: {
|
|
45
|
+
mode: effectiveMode ?? 'hybrid',
|
|
45
46
|
language,
|
|
46
47
|
proposals: createdMemoryProposals,
|
|
47
48
|
memory_proposals: memoryProposalReceipts,
|
package/dist/siduri-db.d.ts
CHANGED
|
@@ -2,33 +2,46 @@ export interface SelfIdentity {
|
|
|
2
2
|
companionId: string;
|
|
3
3
|
name: string;
|
|
4
4
|
archetype?: string;
|
|
5
|
+
origin?: string;
|
|
6
|
+
ethos?: string;
|
|
5
7
|
version: string;
|
|
6
8
|
updatedAt: string;
|
|
7
9
|
}
|
|
8
10
|
export interface PersonalityTraits {
|
|
9
|
-
warmth
|
|
10
|
-
formality
|
|
11
|
-
sarcasm
|
|
12
|
-
verbosity
|
|
13
|
-
curiosity
|
|
11
|
+
warmth?: number;
|
|
12
|
+
formality?: number;
|
|
13
|
+
sarcasm?: number;
|
|
14
|
+
verbosity?: number;
|
|
15
|
+
curiosity?: number;
|
|
14
16
|
}
|
|
15
17
|
export interface SelfDirective {
|
|
16
18
|
id: string;
|
|
17
19
|
companionId: string;
|
|
18
|
-
priority
|
|
20
|
+
priority?: number;
|
|
19
21
|
directive: string;
|
|
20
22
|
status: 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
|
|
21
23
|
category: 'behavioral' | 'guardrail' | 'relational' | string;
|
|
24
|
+
scopeActor?: string;
|
|
22
25
|
supersedesId?: string;
|
|
23
26
|
createdAt?: string;
|
|
24
27
|
}
|
|
25
28
|
export interface SelfRelationship {
|
|
26
29
|
companionId: string;
|
|
27
30
|
entityId: string;
|
|
28
|
-
entityType
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
entityType?: 'human' | 'companion' | 'system';
|
|
32
|
+
role?: string;
|
|
33
|
+
stance?: string;
|
|
34
|
+
trustScore?: number;
|
|
35
|
+
familiarity?: number;
|
|
31
36
|
interactionConventions: string[];
|
|
37
|
+
updatedAt?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface SelfDialogueExample {
|
|
40
|
+
id?: string;
|
|
41
|
+
companionId?: string;
|
|
42
|
+
user: string;
|
|
43
|
+
assistant: string;
|
|
44
|
+
createdAt?: string;
|
|
32
45
|
}
|
|
33
46
|
export interface LifeInventoryItem {
|
|
34
47
|
id: string;
|
|
@@ -107,7 +120,10 @@ export declare class SiduriDatabase {
|
|
|
107
120
|
expireDirective(id: string, companionId?: string): void;
|
|
108
121
|
disableDirective(id: string, companionId?: string): void;
|
|
109
122
|
getRelationship(companionId: string, entityId: string): SelfRelationship | undefined;
|
|
123
|
+
getRelationships(companionId: string): SelfRelationship[];
|
|
110
124
|
upsertRelationship(rel: SelfRelationship): void;
|
|
125
|
+
getExemplars(companionId: string): SelfDialogueExample[];
|
|
126
|
+
setExemplars(companionId: string, exemplars: SelfDialogueExample[]): void;
|
|
111
127
|
getInventory(companionId: string, domain?: string): LifeInventoryItem[];
|
|
112
128
|
upsertInventoryItem(item: LifeInventoryItem): void;
|
|
113
129
|
getFinanceEntries(companionId: string, limit?: number): LifeFinanceEntry[];
|
package/dist/siduri-db.js
CHANGED
|
@@ -20,6 +20,8 @@ class SiduriDatabase {
|
|
|
20
20
|
companion_id TEXT PRIMARY KEY,
|
|
21
21
|
name TEXT NOT NULL,
|
|
22
22
|
archetype TEXT,
|
|
23
|
+
origin TEXT,
|
|
24
|
+
ethos TEXT,
|
|
23
25
|
version TEXT NOT NULL,
|
|
24
26
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
25
27
|
);
|
|
@@ -41,6 +43,7 @@ class SiduriDatabase {
|
|
|
41
43
|
directive TEXT NOT NULL,
|
|
42
44
|
status TEXT DEFAULT 'ACTIVE',
|
|
43
45
|
category TEXT DEFAULT 'behavioral',
|
|
46
|
+
scope_actor TEXT,
|
|
44
47
|
supersedes_id TEXT,
|
|
45
48
|
created_at TEXT DEFAULT (datetime('now'))
|
|
46
49
|
);
|
|
@@ -48,13 +51,24 @@ class SiduriDatabase {
|
|
|
48
51
|
CREATE TABLE IF NOT EXISTS self_relationships (
|
|
49
52
|
companion_id TEXT NOT NULL,
|
|
50
53
|
entity_id TEXT NOT NULL,
|
|
51
|
-
entity_type TEXT NOT NULL,
|
|
54
|
+
entity_type TEXT NOT NULL DEFAULT 'human',
|
|
55
|
+
role TEXT DEFAULT 'user',
|
|
56
|
+
stance TEXT DEFAULT 'neutral',
|
|
52
57
|
trust_score REAL DEFAULT 0.5,
|
|
53
58
|
familiarity REAL DEFAULT 0.5,
|
|
54
59
|
interaction_conventions TEXT,
|
|
60
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
55
61
|
PRIMARY KEY(companion_id, entity_id)
|
|
56
62
|
);
|
|
57
63
|
|
|
64
|
+
CREATE TABLE IF NOT EXISTS self_exemplars (
|
|
65
|
+
id TEXT PRIMARY KEY,
|
|
66
|
+
companion_id TEXT NOT NULL,
|
|
67
|
+
user_prompt TEXT NOT NULL,
|
|
68
|
+
companion_response TEXT NOT NULL,
|
|
69
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
70
|
+
);
|
|
71
|
+
|
|
58
72
|
-- Knowledge Tables
|
|
59
73
|
CREATE TABLE IF NOT EXISTS life_inventory (
|
|
60
74
|
id TEXT PRIMARY KEY,
|
|
@@ -159,6 +173,42 @@ class SiduriDatabase {
|
|
|
159
173
|
catch {
|
|
160
174
|
// Column already exists
|
|
161
175
|
}
|
|
176
|
+
try {
|
|
177
|
+
this.db.exec("ALTER TABLE self_identity ADD COLUMN origin TEXT");
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Column already exists
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
this.db.exec("ALTER TABLE self_identity ADD COLUMN ethos TEXT");
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Column already exists
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this.db.exec("ALTER TABLE self_directives ADD COLUMN scope_actor TEXT");
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Column already exists
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
this.db.exec("ALTER TABLE self_relationships ADD COLUMN role TEXT DEFAULT 'user'");
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Column already exists
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
this.db.exec("ALTER TABLE self_relationships ADD COLUMN stance TEXT DEFAULT 'neutral'");
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Column already exists
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
this.db.exec("ALTER TABLE self_relationships ADD COLUMN updated_at TEXT");
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Column already exists
|
|
211
|
+
}
|
|
162
212
|
}
|
|
163
213
|
close() {
|
|
164
214
|
this.db.close();
|
|
@@ -175,21 +225,25 @@ class SiduriDatabase {
|
|
|
175
225
|
companionId: row.companion_id,
|
|
176
226
|
name: row.name,
|
|
177
227
|
archetype: row.archetype || undefined,
|
|
228
|
+
origin: row.origin || undefined,
|
|
229
|
+
ethos: row.ethos || undefined,
|
|
178
230
|
version: row.version,
|
|
179
231
|
updatedAt: row.updated_at
|
|
180
232
|
};
|
|
181
233
|
}
|
|
182
234
|
setIdentity(identity) {
|
|
183
235
|
const stmt = this.db.prepare(`
|
|
184
|
-
INSERT INTO self_identity (companion_id, name, archetype, version, updated_at)
|
|
185
|
-
VALUES (?, ?, ?, ?, datetime('now'))
|
|
236
|
+
INSERT INTO self_identity (companion_id, name, archetype, origin, ethos, version, updated_at)
|
|
237
|
+
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
186
238
|
ON CONFLICT(companion_id) DO UPDATE SET
|
|
187
239
|
name = excluded.name,
|
|
188
240
|
archetype = excluded.archetype,
|
|
241
|
+
origin = excluded.origin,
|
|
242
|
+
ethos = excluded.ethos,
|
|
189
243
|
version = excluded.version,
|
|
190
244
|
updated_at = datetime('now')
|
|
191
245
|
`);
|
|
192
|
-
stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.version);
|
|
246
|
+
stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.origin || null, identity.ethos || null, identity.version);
|
|
193
247
|
}
|
|
194
248
|
getPersonality(companionId) {
|
|
195
249
|
const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
|
|
@@ -231,16 +285,17 @@ class SiduriDatabase {
|
|
|
231
285
|
directive: row.directive,
|
|
232
286
|
status: row.status,
|
|
233
287
|
category: row.category,
|
|
288
|
+
scopeActor: row.scope_actor || undefined,
|
|
234
289
|
supersedesId: row.supersedes_id || undefined,
|
|
235
290
|
createdAt: row.created_at
|
|
236
291
|
}));
|
|
237
292
|
}
|
|
238
293
|
commitDirective(directive) {
|
|
239
294
|
const stmt = this.db.prepare(`
|
|
240
|
-
INSERT INTO self_directives (id, companion_id, priority, directive, status, category, supersedes_id, created_at)
|
|
241
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
295
|
+
INSERT INTO self_directives (id, companion_id, priority, directive, status, category, scope_actor, supersedes_id, created_at)
|
|
296
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
242
297
|
`);
|
|
243
|
-
stmt.run(directive.id, directive.companionId, directive.priority, directive.directive, directive.status, directive.category, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
|
|
298
|
+
stmt.run(directive.id, directive.companionId, directive.priority !== undefined ? directive.priority : 50, directive.directive, directive.status, directive.category || 'behavioral', directive.scopeActor || null, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
|
|
244
299
|
}
|
|
245
300
|
getDirective(id, companionId) {
|
|
246
301
|
const stmt = companionId
|
|
@@ -256,6 +311,7 @@ class SiduriDatabase {
|
|
|
256
311
|
directive: row.directive,
|
|
257
312
|
status: row.status,
|
|
258
313
|
category: row.category,
|
|
314
|
+
scopeActor: row.scope_actor || undefined,
|
|
259
315
|
supersedesId: row.supersedes_id || undefined,
|
|
260
316
|
createdAt: row.created_at,
|
|
261
317
|
};
|
|
@@ -351,23 +407,66 @@ class SiduriDatabase {
|
|
|
351
407
|
return {
|
|
352
408
|
companionId: row.companion_id,
|
|
353
409
|
entityId: row.entity_id,
|
|
354
|
-
entityType: row.entity_type,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
410
|
+
entityType: row.entity_type || 'human',
|
|
411
|
+
role: row.role || 'user',
|
|
412
|
+
stance: row.stance || 'neutral',
|
|
413
|
+
trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
|
|
414
|
+
familiarity: row.familiarity !== undefined && row.familiarity !== null ? row.familiarity : 0.5,
|
|
415
|
+
interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : [],
|
|
416
|
+
updatedAt: row.updated_at || undefined,
|
|
358
417
|
};
|
|
359
418
|
}
|
|
419
|
+
getRelationships(companionId) {
|
|
420
|
+
const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? ORDER BY entity_id ASC');
|
|
421
|
+
return stmt.all(companionId).map((row) => ({
|
|
422
|
+
companionId: row.companion_id,
|
|
423
|
+
entityId: row.entity_id,
|
|
424
|
+
entityType: row.entity_type || 'human',
|
|
425
|
+
role: row.role || 'user',
|
|
426
|
+
stance: row.stance || 'neutral',
|
|
427
|
+
trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
|
|
428
|
+
familiarity: row.familiarity !== undefined && row.familiarity !== null ? row.familiarity : 0.5,
|
|
429
|
+
interactionConventions: row.interaction_conventions ? JSON.parse(row.interaction_conventions) : [],
|
|
430
|
+
updatedAt: row.updated_at || undefined,
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
360
433
|
upsertRelationship(rel) {
|
|
361
434
|
const stmt = this.db.prepare(`
|
|
362
|
-
INSERT INTO self_relationships (companion_id, entity_id, entity_type, trust_score, familiarity, interaction_conventions)
|
|
363
|
-
VALUES (?, ?, ?, ?, ?,
|
|
435
|
+
INSERT INTO self_relationships (companion_id, entity_id, entity_type, role, stance, trust_score, familiarity, interaction_conventions, updated_at)
|
|
436
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
|
|
364
437
|
ON CONFLICT(companion_id, entity_id) DO UPDATE SET
|
|
365
438
|
entity_type = excluded.entity_type,
|
|
439
|
+
role = excluded.role,
|
|
440
|
+
stance = excluded.stance,
|
|
366
441
|
trust_score = excluded.trust_score,
|
|
367
442
|
familiarity = excluded.familiarity,
|
|
368
|
-
interaction_conventions = excluded.interaction_conventions
|
|
443
|
+
interaction_conventions = excluded.interaction_conventions,
|
|
444
|
+
updated_at = datetime('now')
|
|
369
445
|
`);
|
|
370
|
-
stmt.run(rel.companionId, rel.entityId, rel.entityType, rel.trustScore, rel.familiarity, JSON.stringify(rel.interactionConventions || []));
|
|
446
|
+
stmt.run(rel.companionId, rel.entityId, rel.entityType || 'human', rel.role || 'user', rel.stance || 'neutral', rel.trustScore !== undefined && rel.trustScore !== null ? rel.trustScore : 0.5, rel.familiarity !== undefined && rel.familiarity !== null ? rel.familiarity : 0.5, JSON.stringify(rel.interactionConventions || []));
|
|
447
|
+
}
|
|
448
|
+
getExemplars(companionId) {
|
|
449
|
+
const stmt = this.db.prepare('SELECT * FROM self_exemplars WHERE companion_id = ? ORDER BY created_at ASC');
|
|
450
|
+
return stmt.all(companionId).map((row) => ({
|
|
451
|
+
id: row.id,
|
|
452
|
+
companionId: row.companion_id,
|
|
453
|
+
user: row.user_prompt,
|
|
454
|
+
assistant: row.companion_response,
|
|
455
|
+
createdAt: row.created_at,
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
setExemplars(companionId, exemplars) {
|
|
459
|
+
const delStmt = this.db.prepare('DELETE FROM self_exemplars WHERE companion_id = ?');
|
|
460
|
+
delStmt.run(companionId);
|
|
461
|
+
const insertStmt = this.db.prepare(`
|
|
462
|
+
INSERT INTO self_exemplars (id, companion_id, user_prompt, companion_response, created_at)
|
|
463
|
+
VALUES (?, ?, ?, ?, datetime('now'))
|
|
464
|
+
`);
|
|
465
|
+
for (let i = 0; i < exemplars.length; i++) {
|
|
466
|
+
const ex = exemplars[i];
|
|
467
|
+
const id = ex.id || `ex-${i + 1}-${Date.now()}`;
|
|
468
|
+
insertStmt.run(id, companionId, ex.user, ex.assistant);
|
|
469
|
+
}
|
|
371
470
|
}
|
|
372
471
|
// ==========================================
|
|
373
472
|
// Knowledge / Life DB Methods
|
package/package.json
CHANGED