@sidurijs/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +54 -0
  3. package/dist/action-policy.d.ts +79 -0
  4. package/dist/action-policy.js +439 -0
  5. package/dist/action-policy.test.d.ts +1 -0
  6. package/dist/action-policy.test.js +363 -0
  7. package/dist/action.d.ts +72 -0
  8. package/dist/action.js +2 -0
  9. package/dist/adversarial.test.d.ts +1 -0
  10. package/dist/adversarial.test.js +491 -0
  11. package/dist/architecture-boundary.test.d.ts +1 -0
  12. package/dist/architecture-boundary.test.js +108 -0
  13. package/dist/capability.d.ts +78 -0
  14. package/dist/capability.js +168 -0
  15. package/dist/capability.test.d.ts +1 -0
  16. package/dist/capability.test.js +311 -0
  17. package/dist/chat-contract.d.ts +94 -0
  18. package/dist/chat-contract.js +104 -0
  19. package/dist/cognition-planner.d.ts +15 -0
  20. package/dist/cognition-planner.js +23 -0
  21. package/dist/container.d.ts +74 -0
  22. package/dist/container.js +81 -0
  23. package/dist/context-retriever.d.ts +33 -0
  24. package/dist/context-retriever.js +260 -0
  25. package/dist/context.d.ts +46 -0
  26. package/dist/context.js +85 -0
  27. package/dist/context.test.d.ts +1 -0
  28. package/dist/context.test.js +76 -0
  29. package/dist/conversational-teach.test.d.ts +1 -0
  30. package/dist/conversational-teach.test.js +941 -0
  31. package/dist/database.d.ts +1 -0
  32. package/dist/database.js +17 -0
  33. package/dist/dispatcher.d.ts +14 -0
  34. package/dist/dispatcher.js +40 -0
  35. package/dist/dispatcher.test.d.ts +1 -0
  36. package/dist/dispatcher.test.js +58 -0
  37. package/dist/ear-types.d.ts +33 -0
  38. package/dist/ear-types.js +2 -0
  39. package/dist/evidence.d.ts +76 -0
  40. package/dist/evidence.js +27 -0
  41. package/dist/evidence.test.d.ts +1 -0
  42. package/dist/evidence.test.js +75 -0
  43. package/dist/experience-emitter.d.ts +21 -0
  44. package/dist/experience-emitter.js +47 -0
  45. package/dist/experience.d.ts +55 -0
  46. package/dist/experience.js +74 -0
  47. package/dist/experience.test.d.ts +1 -0
  48. package/dist/experience.test.js +55 -0
  49. package/dist/gating.d.ts +45 -0
  50. package/dist/gating.js +185 -0
  51. package/dist/gating.test.d.ts +1 -0
  52. package/dist/gating.test.js +186 -0
  53. package/dist/index.d.ts +367 -0
  54. package/dist/index.js +56 -0
  55. package/dist/input-normalizer.d.ts +14 -0
  56. package/dist/input-normalizer.js +76 -0
  57. package/dist/input-normalizer.test.d.ts +1 -0
  58. package/dist/input-normalizer.test.js +48 -0
  59. package/dist/intent-classifier.d.ts +37 -0
  60. package/dist/intent-classifier.js +150 -0
  61. package/dist/intent-classifier.test.d.ts +1 -0
  62. package/dist/intent-classifier.test.js +118 -0
  63. package/dist/memory-settler.d.ts +49 -0
  64. package/dist/memory-settler.js +161 -0
  65. package/dist/mouth-types.d.ts +90 -0
  66. package/dist/mouth-types.js +2 -0
  67. package/dist/perception-cycle.test.d.ts +1 -0
  68. package/dist/perception-cycle.test.js +301 -0
  69. package/dist/perception-pipeline.d.ts +77 -0
  70. package/dist/perception-pipeline.js +304 -0
  71. package/dist/perception-pipeline.test.d.ts +1 -0
  72. package/dist/perception-pipeline.test.js +65 -0
  73. package/dist/prompt-compiler.d.ts +26 -0
  74. package/dist/prompt-compiler.js +81 -0
  75. package/dist/prompt-compiler.test.d.ts +1 -0
  76. package/dist/prompt-compiler.test.js +90 -0
  77. package/dist/proposals.d.ts +33 -0
  78. package/dist/proposals.js +2 -0
  79. package/dist/response-envelope.d.ts +30 -0
  80. package/dist/response-envelope.js +90 -0
  81. package/dist/runtime-facades.test.d.ts +1 -0
  82. package/dist/runtime-facades.test.js +95 -0
  83. package/dist/runtime.d.ts +116 -0
  84. package/dist/runtime.js +778 -0
  85. package/dist/schema-validator.d.ts +9 -0
  86. package/dist/schema-validator.js +107 -0
  87. package/dist/schema-validator.test.d.ts +1 -0
  88. package/dist/schema-validator.test.js +124 -0
  89. package/dist/session-history.d.ts +20 -0
  90. package/dist/session-history.js +55 -0
  91. package/dist/session-history.test.d.ts +1 -0
  92. package/dist/session-history.test.js +38 -0
  93. package/dist/siduri-db.d.ts +235 -0
  94. package/dist/siduri-db.js +1405 -0
  95. package/dist/siduri-db.test.d.ts +1 -0
  96. package/dist/siduri-db.test.js +1021 -0
  97. package/dist/sqlite-action-store.d.ts +21 -0
  98. package/dist/sqlite-action-store.js +281 -0
  99. package/dist/sqlite-action-store.test.d.ts +1 -0
  100. package/dist/sqlite-action-store.test.js +261 -0
  101. package/dist/teaching.d.ts +14 -0
  102. package/dist/teaching.js +14 -0
  103. package/package.json +43 -0
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractSearchKeywords = extractSearchKeywords;
4
+ exports.classifyInputIntent = classifyInputIntent;
5
+ exports.classifyInputIntentAsync = classifyInputIntentAsync;
6
+ const teaching_1 = require("./teaching");
7
+ /**
8
+ * Strips conversational syntax, companion invocations, and question carrier phrases
9
+ * to extract targeted keyword search terms (e.g., "Siduri, who is Sandrone" -> ["Sandrone"]).
10
+ */
11
+ function extractSearchKeywords(text, companionName, recentHistory) {
12
+ if (!text || !text.trim())
13
+ return [];
14
+ let query = text.trim();
15
+ // 1. Strip companion names (e.g. "Companion", "Siduri", "Hey companion,")
16
+ const companionTokens = ['companion', 'siduri'];
17
+ if (typeof companionName === 'string' && companionName.trim()) {
18
+ companionTokens.push(companionName.trim().toLowerCase());
19
+ }
20
+ for (const comp of companionTokens) {
21
+ const compRegex = new RegExp(`^(?:hey|hi|hello)?\\s*${comp}\\b[,:]?\\s*`, 'i');
22
+ query = query.replace(compRegex, '');
23
+ const compEndRegex = new RegExp(`[,:]?\\s*${comp}\\s*[.!?]?$`, 'i');
24
+ query = query.replace(compEndRegex, '');
25
+ }
26
+ // 2. Strip leading conversational intent carriers / question frames
27
+ const prefixPatterns = [
28
+ /^(?:can\s+you\s+)?(?:please\s+)?(?:tell\s+me\s+(?:all\s+)?about|explain|describe)\s+(?:the\s+)?/i,
29
+ /^(?:do\s+you\s+know\s+(?:about|who|what)\s+(?:is\s+)?(?:the\s+)?)/i,
30
+ /^(?:what\s+do\s+you\s+know\s+about\s+(?:the\s+)?)/i,
31
+ /^(?:who\s+(?:is|was|are|were)\s+(?:the\s+)?)/i,
32
+ /^(?:what\s+(?:is|was|are|were)\s+(?:the\s+)?)/i,
33
+ /^(?:where\s+(?:is|was|are|were)\s+(?:the\s+)?)/i,
34
+ /^(?:when\s+(?:is|was|are|were|will|does)\s+(?:the\s+)?)/i,
35
+ /^(?:search\s+(?:for\s+)?(?:the\s+)?)/i,
36
+ /^(?:look\s+up\s+(?:the\s+)?)/i,
37
+ /^(?:give\s+me\s+(?:info|information|details)\s+(?:on|about)\s+(?:the\s+)?)/i,
38
+ ];
39
+ for (const pattern of prefixPatterns) {
40
+ query = query.replace(pattern, '');
41
+ }
42
+ // 3. Strip trailing question marks, quotes, and punctuation
43
+ query = query.replace(/[?.,!;:'"]+$/g, '').replace(/^[?.,!;:'"]+/g, '').trim();
44
+ // 4. Resolve pronouns from conversation history if present
45
+ const hasPronoun = /\b(?:she|he|they|her|him|it)\b/i.test(query);
46
+ if (hasPronoun && Array.isArray(recentHistory) && recentHistory.length > 0) {
47
+ for (let i = recentHistory.length - 1; i >= 0; i--) {
48
+ const prevContent = recentHistory[i].content || '';
49
+ const prevClean = prevContent.replace(/^(?:who\s+is|what\s+is|where\s+is)\s+/i, '').replace(/[?.,!;:'"]+$/g, '').trim();
50
+ if (prevClean && !/\b(?:she|he|they|it)\b/i.test(prevClean)) {
51
+ const firstToken = prevClean.split(/\s+/)[0].replace(/[^a-zA-Z0-9]/g, '');
52
+ if (firstToken && firstToken.length >= 3) {
53
+ // Extract remaining qualifier keywords from current question (e.g. "banner", "build", "lore")
54
+ const qualifiers = query.split(/\s+/).filter((w) => !/\b(?:she|he|they|her|him|it)\b/i.test(w) && w.length >= 3);
55
+ if (qualifiers.length > 0) {
56
+ return [`${firstToken} ${qualifiers.join(' ')}`, firstToken];
57
+ }
58
+ return [firstToken];
59
+ }
60
+ }
61
+ }
62
+ }
63
+ // 5. Return clean keyword if valid
64
+ if (query.length > 0) {
65
+ return [query];
66
+ }
67
+ return [];
68
+ }
69
+ /**
70
+ * Evaluates domain heuristics and intent classification for user messages.
71
+ * Determines if deterministic teaching is present, resolves the effective
72
+ * interaction mode (casual, teach, hybrid), and determines if external knowledge
73
+ * retrieval is appropriate.
74
+ */
75
+ function classifyInputIntent(text, context, overrides) {
76
+ const normalizedMessage = text.replace(/\s+/g, ' ').trim().toLowerCase();
77
+ const explicitTeaching = (0, teaching_1.extractDeterministicTeaching)(text, context);
78
+ const isTeachingLike = overrides?.isTeachingLike ??
79
+ (context.mode === 'teach' ||
80
+ explicitTeaching.claims.length > 0 ||
81
+ explicitTeaching.behaviorProposals.length > 0 ||
82
+ /\b(?:remember that|remember:|my name is|call me)\b/i.test(normalizedMessage));
83
+ const isSelfIdentityRequest = overrides?.isSelfIdentityRequest ??
84
+ /\b(?:who|what) are you\b|\bwho (?:is|are) (?:siduri|companion|the companion|she|he|her|him)\b|\b(?:your|my) name\b|\bdo you (?:know|remember|recognize) me\b|\bwho am i\b|\btell me about yourself\b|\bdescribe yourself\b|\bwhat is your origin\b|\bwho created you\b|\bwho made you\b|\bintroduce yourself\b/.test(normalizedMessage);
85
+ const isGreeting = overrides?.isGreeting ??
86
+ /^(?:hello|hi|hey|greetings|good morning|good afternoon|good evening|howdy|yo)[.!]?$/.test(normalizedMessage);
87
+ const shouldQueryKnowledge = typeof overrides?.shouldQueryKnowledge === 'boolean'
88
+ ? overrides.shouldQueryKnowledge
89
+ : (!isTeachingLike && !isSelfIdentityRequest && !isGreeting);
90
+ const rawKeywords = extractSearchKeywords(text, context.companionId, context.history);
91
+ const aiKnowledgeQueries = (overrides?.knowledgeQueries || []).filter(Boolean);
92
+ const knowledgeQueries = aiKnowledgeQueries.length > 0
93
+ ? Array.from(new Set([...aiKnowledgeQueries, ...rawKeywords]))
94
+ : (shouldQueryKnowledge ? rawKeywords : []);
95
+ const primaryQuery = knowledgeQueries[0] || (shouldQueryKnowledge ? text.trim() : undefined);
96
+ const memoryQueries = (overrides?.memoryQueries && overrides.memoryQueries.length > 0)
97
+ ? overrides.memoryQueries
98
+ : rawKeywords;
99
+ // Multi-tier Interaction Mode Resolution:
100
+ // 1. Overrides / Cognitive Classifier
101
+ // 2. Security Boundary: public channel or external source forces 'casual' (Zero Memory Drift)
102
+ // 3. Explicit Request Mode (context.mode: 'casual' | 'teach' | 'hybrid')
103
+ // 4. Default companion baseline: 'hybrid' (salience filtering)
104
+ let effectiveMode;
105
+ if (overrides?.effectiveMode) {
106
+ effectiveMode = overrides.effectiveMode;
107
+ }
108
+ else if (context.conversation?.channel === 'public' || context.source === 'external') {
109
+ effectiveMode = 'casual';
110
+ }
111
+ else if (context.mode) {
112
+ effectiveMode = context.mode;
113
+ }
114
+ else {
115
+ effectiveMode = 'hybrid';
116
+ }
117
+ return {
118
+ normalizedMessage,
119
+ explicitTeaching,
120
+ isTeachingLike,
121
+ isSelfIdentityRequest,
122
+ isGreeting,
123
+ shouldQueryKnowledge,
124
+ knowledgeQueries,
125
+ primaryQuery,
126
+ memoryQueries,
127
+ effectiveMode,
128
+ confidence: overrides?.confidence ?? 0.95,
129
+ classifierOrigin: overrides?.classifierOrigin ?? 'heuristic',
130
+ };
131
+ }
132
+ /**
133
+ * Asynchronously classifies intent, allowing an EarOrgan or cognitive model
134
+ * to provide semantic classification before falling back to heuristics.
135
+ */
136
+ async function classifyInputIntentAsync(text, context, classifier) {
137
+ if (!classifier) {
138
+ return classifyInputIntent(text, context);
139
+ }
140
+ try {
141
+ const cognitiveResult = await classifier(text, context);
142
+ return classifyInputIntent(text, context, {
143
+ ...cognitiveResult,
144
+ classifierOrigin: cognitiveResult.classifierOrigin || 'cognitive',
145
+ });
146
+ }
147
+ catch (err) {
148
+ return classifyInputIntent(text, context);
149
+ }
150
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const intent_classifier_1 = require("./intent-classifier");
4
+ describe('IntentClassifier', () => {
5
+ const dummyContext = {
6
+ companionId: 'test-comp',
7
+ actor: {
8
+ actorId: 'user-1',
9
+ sessionId: 'sess-1',
10
+ authorizationRole: 'administrator',
11
+ capabilities: ['chat:public'],
12
+ authenticated: true,
13
+ },
14
+ conversation: {
15
+ channel: 'direct',
16
+ correlationId: 'corr-1',
17
+ },
18
+ };
19
+ test('classifies greetings correctly and suppresses knowledge queries', () => {
20
+ const greetings = ['Hello', 'hi', 'Good morning', 'greetings!'];
21
+ for (const g of greetings) {
22
+ const result = (0, intent_classifier_1.classifyInputIntent)(g, dummyContext);
23
+ expect(result.isGreeting).toBe(true);
24
+ expect(result.shouldQueryKnowledge).toBe(false);
25
+ }
26
+ });
27
+ test('classifies self-identity inquiries and suppresses knowledge queries', () => {
28
+ const identityQuestions = [
29
+ 'Who are you?',
30
+ 'What are you',
31
+ 'who is siduri',
32
+ 'Tell me about yourself',
33
+ 'what is your name',
34
+ ];
35
+ for (const q of identityQuestions) {
36
+ const result = (0, intent_classifier_1.classifyInputIntent)(q, dummyContext);
37
+ expect(result.isSelfIdentityRequest).toBe(true);
38
+ expect(result.shouldQueryKnowledge).toBe(false);
39
+ }
40
+ });
41
+ test('classifies teaching statements and suppresses knowledge queries', () => {
42
+ const teachingInputs = [
43
+ 'Remember that the sky is purple',
44
+ 'My name is Alice',
45
+ 'Call me Bob',
46
+ ];
47
+ for (const t of teachingInputs) {
48
+ const result = (0, intent_classifier_1.classifyInputIntent)(t, dummyContext);
49
+ expect(result.isTeachingLike).toBe(true);
50
+ expect(result.shouldQueryKnowledge).toBe(false);
51
+ }
52
+ });
53
+ test('enables knowledge queries for standard informational questions', () => {
54
+ const queries = [
55
+ 'What happened during the Cataclysm?',
56
+ 'Tell me about the archons',
57
+ 'How does alchemy work in Khaenriah?',
58
+ ];
59
+ for (const q of queries) {
60
+ const result = (0, intent_classifier_1.classifyInputIntent)(q, dummyContext);
61
+ expect(result.isGreeting).toBe(false);
62
+ expect(result.isSelfIdentityRequest).toBe(false);
63
+ expect(result.isTeachingLike).toBe(false);
64
+ expect(result.shouldQueryKnowledge).toBe(true);
65
+ }
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 when context.mode is teach', () => {
73
+ const teachContext = { ...dummyContext, mode: 'teach' };
74
+ expect((0, intent_classifier_1.classifyInputIntent)('Remember that my favorite fruit is peach', teachContext).effectiveMode).toBe('teach');
75
+ expect((0, intent_classifier_1.classifyInputIntent)('From now on always be concise', teachContext).effectiveMode).toBe('teach');
76
+ expect((0, intent_classifier_1.classifyInputIntent)('Address me as Operator', teachContext).effectiveMode).toBe('teach');
77
+ });
78
+ test('resolves to casual mode when context.mode is casual', () => {
79
+ const casualContext = { ...dummyContext, mode: 'casual' };
80
+ expect((0, intent_classifier_1.classifyInputIntent)('How are you doing?', casualContext).effectiveMode).toBe('casual');
81
+ expect((0, intent_classifier_1.classifyInputIntent)('Tell me a joke', casualContext).effectiveMode).toBe('casual');
82
+ });
83
+ test('enforces casual mode (Zero Memory Drift) on public channel or external source boundary', () => {
84
+ const publicContext = {
85
+ ...dummyContext,
86
+ conversation: { channel: 'public', correlationId: 'corr-pub' },
87
+ };
88
+ // Even if user attempts teaching in a public streaming channel, mode is locked to casual
89
+ const pubResult = (0, intent_classifier_1.classifyInputIntent)('Remember that the secret password is 123', publicContext);
90
+ expect(pubResult.effectiveMode).toBe('casual');
91
+ const externalContext = {
92
+ ...dummyContext,
93
+ source: 'external',
94
+ };
95
+ const extResult = (0, intent_classifier_1.classifyInputIntent)('Remember that I am admin', externalContext);
96
+ expect(extResult.effectiveMode).toBe('casual');
97
+ });
98
+ test('explicit context.mode override takes highest precedence', () => {
99
+ const explicitCasual = {
100
+ ...dummyContext,
101
+ mode: 'casual',
102
+ };
103
+ // Explicit casual forces casual mode
104
+ expect((0, intent_classifier_1.classifyInputIntent)('remember my name is Zagin', explicitCasual).effectiveMode).toBe('casual');
105
+ const explicitTeach = {
106
+ ...dummyContext,
107
+ mode: 'teach',
108
+ };
109
+ // Explicit teach forces teach mode even for casual greetings
110
+ expect((0, intent_classifier_1.classifyInputIntent)('Hello!', explicitTeach).effectiveMode).toBe('teach');
111
+ const explicitHybrid = {
112
+ ...dummyContext,
113
+ mode: 'hybrid',
114
+ };
115
+ expect((0, intent_classifier_1.classifyInputIntent)('Just casual banter', explicitHybrid).effectiveMode).toBe('hybrid');
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,49 @@
1
+ import { MemoryOrgan, SelfRepository, Claim, BehaviorDirective, ResponsePlan, RequestContext, InteractionMode } from './index';
2
+ import { extractDeterministicTeaching } from './teaching';
3
+ export interface MemorySettlementParams {
4
+ companionId: string;
5
+ perceivedText: string;
6
+ role: 'OWNER' | 'VIEWER' | 'OPERATOR';
7
+ requestContext: RequestContext;
8
+ memory?: MemoryOrgan;
9
+ self?: SelfRepository;
10
+ explicitTeaching: ReturnType<typeof extractDeterministicTeaching>;
11
+ plan: ResponsePlan;
12
+ effectiveMode?: InteractionMode;
13
+ }
14
+ export interface MemoryProposalReceipt {
15
+ proposal_id: string;
16
+ subject: string;
17
+ predicate: string;
18
+ value: string;
19
+ status: string;
20
+ claim_type?: string;
21
+ content?: string;
22
+ }
23
+ export interface BehavioralProposalReceipt {
24
+ directive_id: string;
25
+ domain?: string;
26
+ knowledge_domain?: string;
27
+ memory_class?: string;
28
+ runtime_effect?: string;
29
+ subject?: string;
30
+ predicate?: string;
31
+ value?: string;
32
+ status: string;
33
+ behavior?: {
34
+ instruction: string;
35
+ frequency?: string;
36
+ preferred_positions?: string[];
37
+ };
38
+ }
39
+ export interface MemorySettlementResult {
40
+ createdMemoryProposals: Claim[];
41
+ memoryProposalReceipts: MemoryProposalReceipt[];
42
+ createdBehavioralProposals?: BehaviorDirective[];
43
+ behavioralProposalReceipts?: BehavioralProposalReceipt[];
44
+ }
45
+ /**
46
+ * Persists source events, deterministic teaching claims, LLM memory proposals,
47
+ * and LLM behavior proposals to the MemoryOrgan.
48
+ */
49
+ export declare function settleMemoryProposals(params: MemorySettlementParams): Promise<MemorySettlementResult>;
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.settleMemoryProposals = settleMemoryProposals;
4
+ /**
5
+ * Persists source events, deterministic teaching claims, LLM memory proposals,
6
+ * and LLM behavior proposals to the MemoryOrgan.
7
+ */
8
+ async function settleMemoryProposals(params) {
9
+ const { companionId, perceivedText, role, requestContext, memory, self, 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
+ }
18
+ const createdMemoryProposals = [];
19
+ let sourceEventId;
20
+ const hasTeaching = explicitTeaching.claims.length > 0 ||
21
+ explicitTeaching.behaviorProposals.length > 0;
22
+ const hasPlanProposals = Boolean(plan.memoryProposals?.length) ||
23
+ Boolean(plan.behaviorProposals?.length);
24
+ if (memory &&
25
+ (hasTeaching || hasPlanProposals) &&
26
+ typeof memory.addSourceEvent === 'function') {
27
+ const sourceEvent = {
28
+ id: `evt-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
29
+ sourceType: 'user_chat_explicit',
30
+ occurredAt: new Date().toISOString(),
31
+ payload: {
32
+ message: perceivedText,
33
+ role,
34
+ companionId,
35
+ actorId: requestContext.actor?.actorId || 'owner-user',
36
+ channel: requestContext.conversation?.channel || 'direct',
37
+ },
38
+ };
39
+ await memory.addSourceEvent(sourceEvent);
40
+ sourceEventId = sourceEvent.id;
41
+ }
42
+ // Persist deterministic memory proposals as PENDING if memory is available
43
+ if (memory && typeof memory.proposeClaim === 'function') {
44
+ for (const claim of explicitTeaching.claims) {
45
+ const proposal = await memory.proposeClaim({
46
+ companionId,
47
+ subject: claim.subject,
48
+ predicate: claim.predicate,
49
+ value: claim.value,
50
+ scope: claim.subject?.startsWith('companion:') ? 'companion' : 'user',
51
+ provenance: claim.provenance || 'deterministic_teaching',
52
+ sourceEventId,
53
+ claimType: claim.claimType || 'preference',
54
+ authority: 'user_explicit',
55
+ userConfirmation: 'none',
56
+ sensitivity: claim.sensitivity ||
57
+ (requestContext.conversation?.channel === 'public' ? 'public' : 'private'),
58
+ });
59
+ createdMemoryProposals.push(proposal);
60
+ }
61
+ // Also persist plan memory proposals if model returned structured proposals
62
+ if (plan.memoryProposals && plan.memoryProposals.length > 0) {
63
+ for (const p of plan.memoryProposals) {
64
+ const proposal = await memory.proposeClaim({
65
+ companionId,
66
+ subject: p.subject || `actor:${requestContext.actor.actorId}`,
67
+ predicate: p.predicate,
68
+ value: p.value,
69
+ scope: p.subject?.startsWith('companion:') ? 'companion' : 'user',
70
+ provenance: p.provenance || 'llm_proposal',
71
+ sourceEventId: sourceEventId || p.sourceEventId,
72
+ claimType: p.claimType || 'semantic',
73
+ sensitivity: p.sensitivity || 'private',
74
+ });
75
+ createdMemoryProposals.push(proposal);
76
+ }
77
+ }
78
+ }
79
+ const createdBehavioralProposals = [];
80
+ const behavioralProposalReceipts = [];
81
+ const allBehaviorProposals = [
82
+ ...explicitTeaching.behaviorProposals,
83
+ ...(plan.behaviorProposals || []),
84
+ ];
85
+ for (const bp of allBehaviorProposals) {
86
+ let directive;
87
+ if (memory && typeof memory.proposeDirective === 'function') {
88
+ directive = await memory.proposeDirective({
89
+ companionId,
90
+ directive: bp.directive,
91
+ priority: bp.priority || 50,
92
+ category: bp.category || 'behavioral',
93
+ supersedesId: bp.supersedesId,
94
+ scopeActor: bp.scopeActor || (bp.subject?.startsWith('actor:') ? bp.subject.slice(6) : undefined),
95
+ memoryClass: bp.memoryClass,
96
+ subject: bp.subject,
97
+ predicate: bp.predicate,
98
+ value: bp.value,
99
+ sourceEventId: sourceEventId || bp.sourceEventId,
100
+ });
101
+ }
102
+ if (!directive || !directive.id) {
103
+ directive = {
104
+ id: directive?.id || `dir-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
105
+ companionId,
106
+ directive: bp.directive,
107
+ priority: bp.priority || 50,
108
+ status: 'pending',
109
+ category: (bp.category || 'behavioral'),
110
+ scopeActor: bp.scopeActor,
111
+ supersedesId: bp.supersedesId,
112
+ createdAt: new Date().toISOString(),
113
+ };
114
+ }
115
+ createdBehavioralProposals.push(directive);
116
+ if (self && typeof self.commitDirectives === 'function') {
117
+ await self.commitDirectives(companionId, [{
118
+ id: directive.id,
119
+ companionId,
120
+ directive: bp.directive,
121
+ priority: bp.priority || 50,
122
+ status: 'pending',
123
+ category: (bp.category || 'behavioral'),
124
+ scopeActor: bp.scopeActor,
125
+ supersedesId: bp.supersedesId,
126
+ createdAt: new Date().toISOString(),
127
+ }]);
128
+ }
129
+ behavioralProposalReceipts.push({
130
+ directive_id: directive.id,
131
+ domain: 'behavioral',
132
+ knowledge_domain: 'behavioral',
133
+ memory_class: bp.memoryClass || 'behavioral',
134
+ runtime_effect: bp.memoryClass || 'behavioral',
135
+ subject: bp.subject || `companion:${companionId}`,
136
+ predicate: bp.predicate || 'rule',
137
+ value: bp.value || bp.directive,
138
+ status: 'pending',
139
+ behavior: {
140
+ instruction: bp.directive,
141
+ frequency: 'continuous',
142
+ preferred_positions: [],
143
+ },
144
+ });
145
+ }
146
+ const memoryProposalReceipts = createdMemoryProposals.map((p) => ({
147
+ proposal_id: p.id,
148
+ subject: p.subject,
149
+ predicate: p.predicate,
150
+ value: p.value,
151
+ status: (p.status || 'pending').toLowerCase().replace(/_/g, '-'),
152
+ claim_type: p.claimType,
153
+ content: p.content,
154
+ }));
155
+ return {
156
+ createdMemoryProposals,
157
+ memoryProposalReceipts,
158
+ createdBehavioralProposals,
159
+ behavioralProposalReceipts,
160
+ };
161
+ }
@@ -0,0 +1,90 @@
1
+ import { ResponseCitation } from './evidence';
2
+ export type MouthMedium = 'web';
3
+ export interface MouthVisemeCue {
4
+ timeMs: number;
5
+ durationMs: number;
6
+ mouthOpenY: number;
7
+ mouthForm?: number;
8
+ phoneme?: string;
9
+ }
10
+ export interface MouthUtterance {
11
+ utteranceId: string;
12
+ companionId: string;
13
+ responseId?: string;
14
+ correlationId?: string;
15
+ text: string;
16
+ language?: string;
17
+ subtitleJa?: string;
18
+ subtitleEn?: string;
19
+ spokenJa?: string;
20
+ subtitle?: string;
21
+ subtitleLanguage?: string;
22
+ subtitles?: Record<string, string>;
23
+ expression?: string;
24
+ action?: string;
25
+ medium?: MouthMedium;
26
+ metadata?: Record<string, unknown>;
27
+ audioUrl?: string;
28
+ audioBuffer?: Uint8Array;
29
+ citations?: ResponseCitation[];
30
+ evidenceIds?: string[];
31
+ signal?: AbortSignal;
32
+ }
33
+ export interface FormattedMouthOutput {
34
+ medium: MouthMedium;
35
+ text: string;
36
+ displayText: string;
37
+ richContent?: Record<string, unknown>;
38
+ ssml?: string;
39
+ visemes?: MouthVisemeCue[];
40
+ subtitles?: {
41
+ ja?: string;
42
+ en?: string;
43
+ spoken?: string;
44
+ [lang: string]: string | undefined;
45
+ };
46
+ subtitle?: string;
47
+ audioUrl?: string;
48
+ audioBuffer?: Uint8Array;
49
+ expression?: string;
50
+ action?: string;
51
+ metadata?: Record<string, unknown>;
52
+ timestamp: string;
53
+ interrupted?: boolean;
54
+ }
55
+ export interface MouthStreamChunk {
56
+ utteranceId: string;
57
+ index: number;
58
+ deltaText: string;
59
+ isComplete: boolean;
60
+ audioChunk?: Uint8Array;
61
+ medium?: MouthMedium;
62
+ visemes?: MouthVisemeCue[];
63
+ expression?: string;
64
+ action?: string;
65
+ interrupted?: boolean;
66
+ }
67
+ export interface MouthChannel {
68
+ id: string;
69
+ name: string;
70
+ medium: MouthMedium;
71
+ deliver(output: FormattedMouthOutput): Promise<void>;
72
+ deliverStream?(stream: AsyncIterable<MouthStreamChunk>): Promise<void>;
73
+ interrupt?(utteranceId?: string, reason?: string): Promise<void> | void;
74
+ }
75
+ export interface MouthOrganConfig {
76
+ defaultMedium?: MouthMedium;
77
+ supportedMedia?: MouthMedium[];
78
+ [key: string]: unknown;
79
+ }
80
+ export interface MouthOrgan {
81
+ readonly kind?: string;
82
+ speak(utterance: MouthUtterance): Promise<FormattedMouthOutput>;
83
+ format(utterance: MouthUtterance, medium?: MouthMedium): FormattedMouthOutput;
84
+ stream?(utterance: MouthUtterance): AsyncIterable<MouthStreamChunk>;
85
+ interrupt?(reason?: string): void;
86
+ registerChannel?(channel: MouthChannel): void;
87
+ unregisterChannel?(channelId: string): void;
88
+ broadcast?(utterance: MouthUtterance): Promise<FormattedMouthOutput[]>;
89
+ getRegisteredChannels?(): MouthChannel[];
90
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ export {};