@siduri-x/core 2.0.2 → 2.0.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/dist/chat-contract.d.ts +4 -0
- package/dist/chat-contract.js +17 -2
- package/dist/context.d.ts +2 -0
- package/dist/context.js +13 -0
- package/dist/index.d.ts +13 -5
- 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 +10 -2
- package/dist/mouth-types.d.ts +7 -2
- package/dist/perception-cycle.test.js +134 -0
- package/dist/perception-pipeline.d.ts +1 -0
- package/dist/perception-pipeline.js +10 -0
- package/dist/prompt-compiler.d.ts +3 -1
- package/dist/prompt-compiler.js +11 -1
- package/dist/proposals.d.ts +6 -2
- package/dist/response-envelope.d.ts +5 -1
- package/dist/response-envelope.js +14 -3
- package/dist/runtime.d.ts +1 -1
- package/dist/runtime.js +2 -1
- package/dist/siduri-db.d.ts +34 -11
- package/dist/siduri-db.js +208 -134
- package/dist/siduri-db.test.js +18 -18
- package/package.json +1 -1
package/dist/chat-contract.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export interface ChatRequest {
|
|
|
13
13
|
history?: Message[];
|
|
14
14
|
medium?: MouthMedium;
|
|
15
15
|
signal?: AbortSignal;
|
|
16
|
+
subtitleLanguage?: string;
|
|
16
17
|
[key: string]: any;
|
|
17
18
|
}
|
|
18
19
|
export interface ChatResponseMetadataEvent {
|
|
@@ -30,6 +31,9 @@ export interface ChatResponsePlan {
|
|
|
30
31
|
subtitle_ja: string;
|
|
31
32
|
subtitle_en: string;
|
|
32
33
|
spoken_ja?: string;
|
|
34
|
+
subtitle?: string;
|
|
35
|
+
subtitle_language?: string;
|
|
36
|
+
subtitles?: Record<string, string>;
|
|
33
37
|
evidence_ids?: string[];
|
|
34
38
|
}
|
|
35
39
|
export interface ChatResponseMetadata {
|
package/dist/chat-contract.js
CHANGED
|
@@ -21,8 +21,9 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
21
21
|
else {
|
|
22
22
|
roleOrContext = 'OWNER';
|
|
23
23
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
const requestedSubtitleLang = payload.subtitleLanguage || payload.subtitle_language;
|
|
25
|
+
const runtimeResult = (payload.medium || payload.signal || requestedSubtitleLang)
|
|
26
|
+
? await runner.handleUserMessage(userMessage, roleOrContext, history, payload.medium, payload.signal, requestedSubtitleLang)
|
|
26
27
|
: await runner.handleUserMessage(userMessage, roleOrContext, history);
|
|
27
28
|
const delivery = runtimeResult?.delivery;
|
|
28
29
|
// Normalize response plan
|
|
@@ -41,6 +42,17 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
41
42
|
expression = avatarEvent.expression;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
const resolvedSubtitles = {
|
|
46
|
+
...(runtimeResult?.response?.subtitles || {}),
|
|
47
|
+
...(delivery?.subtitles || {}),
|
|
48
|
+
};
|
|
49
|
+
const subtitle = (requestedSubtitleLang && resolvedSubtitles[requestedSubtitleLang]) ||
|
|
50
|
+
runtimeResult?.response?.subtitle ||
|
|
51
|
+
(requestedSubtitleLang === 'ja' ? (delivery?.subtitles?.ja ?? runtimeResult?.response?.subtitle_ja) : undefined) ||
|
|
52
|
+
(requestedSubtitleLang === 'en' ? (delivery?.subtitles?.en ?? runtimeResult?.response?.subtitle_en) : undefined);
|
|
53
|
+
if (subtitle && requestedSubtitleLang) {
|
|
54
|
+
resolvedSubtitles[requestedSubtitleLang] = subtitle;
|
|
55
|
+
}
|
|
44
56
|
// Ensure both spoken_ja and subtitle_en are accessible alongside speech_id and evidence_ids
|
|
45
57
|
const responsePlan = {
|
|
46
58
|
speech_id: runtimeResult?.response?.speech_id,
|
|
@@ -48,6 +60,9 @@ async function dispatchCompanionChat(runtime, payload) {
|
|
|
48
60
|
subtitle_ja: delivery?.subtitles?.ja ?? runtimeResult?.response?.subtitle_ja ?? speech,
|
|
49
61
|
subtitle_en: delivery?.subtitles?.en ?? runtimeResult?.response?.subtitle_en ?? speech,
|
|
50
62
|
spoken_ja: delivery?.subtitles?.spoken ?? runtimeResult?.response?.spoken_ja ?? runtimeResult?.response?.subtitle_ja ?? speech,
|
|
63
|
+
subtitle,
|
|
64
|
+
subtitle_language: requestedSubtitleLang,
|
|
65
|
+
subtitles: resolvedSubtitles,
|
|
51
66
|
evidence_ids: runtimeResult?.metadata?.evidence_ids ?? runtimeResult?.response?.evidence_ids ?? [],
|
|
52
67
|
};
|
|
53
68
|
const metadata = {
|
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
|
@@ -31,7 +31,7 @@ import { EvidenceRecord } from './evidence';
|
|
|
31
31
|
import { ActionIntent } from './action';
|
|
32
32
|
import { RequestContext } from './context';
|
|
33
33
|
import { EarIngestOptions } from './ear-types';
|
|
34
|
-
import { ClaimType, ClaimAuthority, ClaimStatus, SourceEvent, MemoryProposal, BehaviorProposal } from './proposals';
|
|
34
|
+
import { ClaimType, ClaimAuthority, ClaimStatus, DirectiveStatus, SourceEvent, MemoryProposal, BehaviorProposal } from './proposals';
|
|
35
35
|
export interface OrganConfig {
|
|
36
36
|
provider: string;
|
|
37
37
|
[key: string]: unknown;
|
|
@@ -61,6 +61,8 @@ export interface BrainContext {
|
|
|
61
61
|
export interface ResponsePlan {
|
|
62
62
|
speech: string;
|
|
63
63
|
language: string;
|
|
64
|
+
subtitle?: string;
|
|
65
|
+
subtitles?: Record<string, string>;
|
|
64
66
|
memoryProposals?: MemoryProposal[];
|
|
65
67
|
behaviorProposals?: BehaviorProposal[];
|
|
66
68
|
actionIntents?: ActionIntent[];
|
|
@@ -98,7 +100,7 @@ export interface BehaviorDirective {
|
|
|
98
100
|
companionId: string;
|
|
99
101
|
directive: string;
|
|
100
102
|
priority: number;
|
|
101
|
-
status:
|
|
103
|
+
status: DirectiveStatus;
|
|
102
104
|
supersedesId?: string;
|
|
103
105
|
memoryClass?: 'identity' | 'relationship' | 'behavioral';
|
|
104
106
|
subject?: string;
|
|
@@ -255,19 +257,25 @@ export interface HealthProbeResult {
|
|
|
255
257
|
}
|
|
256
258
|
export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
|
|
257
259
|
export * from './mouth-types';
|
|
258
|
-
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
260
|
+
import type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent } from './siduri-db';
|
|
261
|
+
export type { SelfIdentity, PersonalityTraits, SelfDirective, SelfRelationship, SelfDialogueExample, LifeInventoryItem, LifeScheduleItem, LifePreference, MemoryClaim, EpisodicEvent, };
|
|
259
262
|
export interface SelfRepository {
|
|
260
263
|
getIdentity(companionId: string): Promise<SelfIdentity | undefined>;
|
|
261
|
-
getPersonality(companionId: string): Promise<PersonalityTraits>;
|
|
264
|
+
getPersonality?(companionId: string): Promise<PersonalityTraits>;
|
|
262
265
|
getActiveDirectives(companionId: string): Promise<SelfDirective[]>;
|
|
263
266
|
getRelationship(companionId: string, entityId: string): Promise<SelfRelationship | null>;
|
|
267
|
+
getRelationships?(companionId: string): Promise<SelfRelationship[]>;
|
|
268
|
+
getExemplars?(companionId: string): Promise<SelfDialogueExample[]>;
|
|
269
|
+
setExemplars?(companionId: string, exemplars: SelfDialogueExample[]): Promise<void>;
|
|
264
270
|
commitDirectives(companionId: string, directives: SelfDirective[]): Promise<void>;
|
|
265
271
|
updateRelationship(companionId: string, rel: SelfRelationship): Promise<void>;
|
|
266
272
|
disableDirective?(id: string): Promise<void>;
|
|
267
273
|
getActiveSelf?(companionId: string): Promise<{
|
|
268
274
|
identity?: SelfIdentity;
|
|
269
|
-
personality
|
|
275
|
+
personality?: PersonalityTraits;
|
|
270
276
|
directives: SelfDirective[];
|
|
277
|
+
relationships?: SelfRelationship[];
|
|
278
|
+
exemplars?: SelfDialogueExample[];
|
|
271
279
|
}>;
|
|
272
280
|
}
|
|
273
281
|
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 ||
|
|
@@ -83,7 +91,7 @@ async function settleMemoryProposals(params) {
|
|
|
83
91
|
subject: p.subject,
|
|
84
92
|
predicate: p.predicate,
|
|
85
93
|
value: p.value,
|
|
86
|
-
status: p.status,
|
|
94
|
+
status: (p.status || 'pending').toLowerCase().replace(/_/g, '-'),
|
|
87
95
|
}));
|
|
88
96
|
return {
|
|
89
97
|
createdMemoryProposals,
|
package/dist/mouth-types.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export interface MouthUtterance {
|
|
|
17
17
|
subtitleJa?: string;
|
|
18
18
|
subtitleEn?: string;
|
|
19
19
|
spokenJa?: string;
|
|
20
|
+
subtitle?: string;
|
|
21
|
+
subtitleLanguage?: string;
|
|
22
|
+
subtitles?: Record<string, string>;
|
|
20
23
|
expression?: string;
|
|
21
24
|
action?: string;
|
|
22
25
|
medium?: MouthMedium;
|
|
@@ -35,10 +38,12 @@ export interface FormattedMouthOutput {
|
|
|
35
38
|
ssml?: string;
|
|
36
39
|
visemes?: MouthVisemeCue[];
|
|
37
40
|
subtitles?: {
|
|
38
|
-
ja
|
|
39
|
-
en
|
|
41
|
+
ja?: string;
|
|
42
|
+
en?: string;
|
|
40
43
|
spoken?: string;
|
|
44
|
+
[lang: string]: string | undefined;
|
|
41
45
|
};
|
|
46
|
+
subtitle?: string;
|
|
42
47
|
audioUrl?: string;
|
|
43
48
|
audioBuffer?: Uint8Array;
|
|
44
49
|
expression?: string;
|
|
@@ -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,8 @@ 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,
|
|
96
|
+
subtitleLanguage: context.perception.subtitleLanguage,
|
|
95
97
|
});
|
|
96
98
|
context.prompts = prompts;
|
|
97
99
|
};
|
|
@@ -146,6 +148,7 @@ const memorySettlementStage = async (context) => {
|
|
|
146
148
|
memory: context.organs.memory,
|
|
147
149
|
explicitTeaching: context.intent.explicitTeaching,
|
|
148
150
|
plan: context.plan,
|
|
151
|
+
effectiveMode: context.intent.effectiveMode,
|
|
149
152
|
});
|
|
150
153
|
context.memorySettlement = memorySettlement;
|
|
151
154
|
};
|
|
@@ -196,6 +199,9 @@ const mouthDeliveryStage = async (context) => {
|
|
|
196
199
|
subtitleJa: context.plan.speech,
|
|
197
200
|
subtitleEn: context.plan.speech,
|
|
198
201
|
spokenJa: context.plan.speech,
|
|
202
|
+
subtitle: context.plan.subtitle,
|
|
203
|
+
subtitles: context.plan.subtitles,
|
|
204
|
+
subtitleLanguage: context.perception.subtitleLanguage,
|
|
199
205
|
expression: avatarEvent?.expression,
|
|
200
206
|
medium: context.perception.medium,
|
|
201
207
|
signal: context.perception.signal,
|
|
@@ -224,6 +230,9 @@ const envelopeAssemblyStage = async (context) => {
|
|
|
224
230
|
stagedPlan: context.stagedPlan,
|
|
225
231
|
speech: context.plan.speech,
|
|
226
232
|
language: context.plan.language,
|
|
233
|
+
subtitle: context.plan.subtitle,
|
|
234
|
+
subtitles: context.plan.subtitles,
|
|
235
|
+
subtitleLanguage: context.perception.subtitleLanguage,
|
|
227
236
|
speechId: context.experienceEmission?.speechId,
|
|
228
237
|
createdMemoryProposals: context.memorySettlement.createdMemoryProposals,
|
|
229
238
|
memoryProposalReceipts: context.memorySettlement.memoryProposalReceipts,
|
|
@@ -233,6 +242,7 @@ const envelopeAssemblyStage = async (context) => {
|
|
|
233
242
|
subsystemDiagnostics: context.contextRetrieval.subsystemDiagnostics,
|
|
234
243
|
experienceEvents: context.experienceEmission?.experienceEvents || [],
|
|
235
244
|
mouthDelivery: context.mouthDelivery,
|
|
245
|
+
effectiveMode: context.intent?.effectiveMode,
|
|
236
246
|
});
|
|
237
247
|
context.responseEnvelope = envelope;
|
|
238
248
|
};
|
|
@@ -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,8 @@ export interface PromptCompilationParams {
|
|
|
10
10
|
knowledgeData: KnowledgeItem[];
|
|
11
11
|
memoryData: Claim[];
|
|
12
12
|
lifeContext?: string[];
|
|
13
|
+
effectiveMode?: InteractionMode;
|
|
14
|
+
subtitleLanguage?: string;
|
|
13
15
|
}
|
|
14
16
|
export interface CompiledPrompts {
|
|
15
17
|
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, subtitleLanguage, } = params;
|
|
9
9
|
let contextPrompt = '';
|
|
10
10
|
if (Object.keys(subsystemDiagnostics).length > 0) {
|
|
11
11
|
contextPrompt +=
|
|
@@ -43,8 +43,18 @@ 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;
|
|
51
|
+
const subtitleInstruction = subtitleLanguage && subtitleLanguage !== 'off'
|
|
52
|
+
? `Requested Subtitle Language: "${subtitleLanguage}". Along with your primary speech, provide a natural subtitle translation in "${subtitleLanguage}" in the subtitle field.`
|
|
53
|
+
: undefined;
|
|
46
54
|
const systemPrompt = [
|
|
47
55
|
`You are ${companionName}.`,
|
|
56
|
+
modeInstruction,
|
|
57
|
+
subtitleInstruction,
|
|
48
58
|
'This is a neutral conversation context.',
|
|
49
59
|
'Use only approved, permitted memory as factual personal context.',
|
|
50
60
|
'Do not claim prior personal knowledge when no approved memory supports it.',
|
package/dist/proposals.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type ClaimType = 'semantic' | 'preference' | 'episodic' | 'relationship';
|
|
2
2
|
export type ClaimAuthority = 'user_explicit' | 'user_correction' | 'import' | 'repeated_dialogue' | 'inference' | 'observation';
|
|
3
|
-
export type ClaimStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'SESSION_ONLY' | 'EXPIRED' | 'SUPERSEDED' | 'REVOKED';
|
|
3
|
+
export type ClaimStatus = 'pending' | 'approved' | 'rejected' | 'session-only' | 'expired' | 'superseded' | 'revoked' | 'PENDING' | 'APPROVED' | 'REJECTED' | 'SESSION_ONLY' | 'EXPIRED' | 'SUPERSEDED' | 'REVOKED';
|
|
4
|
+
export type DirectiveStatus = 'pending' | 'active' | 'disabled' | 'superseded' | 'rejected' | 'revoked' | 'expired' | 'confirmed' | 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
|
|
4
5
|
export interface SourceEvent {
|
|
5
6
|
id: string;
|
|
6
7
|
sourceType: string;
|
|
@@ -20,7 +21,10 @@ export interface MemoryProposal {
|
|
|
20
21
|
}
|
|
21
22
|
export interface BehaviorProposal {
|
|
22
23
|
directive: string;
|
|
23
|
-
priority
|
|
24
|
+
priority?: number;
|
|
25
|
+
category?: 'guardrail' | 'relational' | 'behavioral' | string;
|
|
26
|
+
scopeActor?: string;
|
|
27
|
+
supersedesId?: string;
|
|
24
28
|
subject?: string;
|
|
25
29
|
predicate?: string;
|
|
26
30
|
value?: string;
|
|
@@ -1,10 +1,13 @@
|
|
|
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 {
|
|
5
5
|
stagedPlan: StagedResponsePlan;
|
|
6
6
|
speech: string;
|
|
7
7
|
language?: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
subtitles?: Record<string, string>;
|
|
10
|
+
subtitleLanguage?: string;
|
|
8
11
|
speechId?: string;
|
|
9
12
|
createdMemoryProposals: Claim[];
|
|
10
13
|
memoryProposalReceipts: MemoryProposalReceipt[];
|
|
@@ -14,6 +17,7 @@ export interface AssembleResponseEnvelopeParams {
|
|
|
14
17
|
subsystemDiagnostics: Record<string, string>;
|
|
15
18
|
experienceEvents: ExperienceEvent[];
|
|
16
19
|
mouthDelivery?: FormattedMouthOutput;
|
|
20
|
+
effectiveMode?: InteractionMode;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
23
|
* Creates the standardized rejection envelope for responses that fail gate evaluation.
|