@siduri-x/core 1.0.5 → 1.0.7

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 (85) hide show
  1. package/dist/action-executor.d.ts +12 -0
  2. package/dist/action-executor.js +50 -0
  3. package/dist/action-policy.d.ts +45 -0
  4. package/dist/action-policy.js +219 -0
  5. package/dist/action-policy.test.d.ts +1 -0
  6. package/dist/action-policy.test.js +194 -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 +493 -0
  11. package/dist/architecture-boundary.test.d.ts +1 -0
  12. package/dist/architecture-boundary.test.js +117 -0
  13. package/dist/capability.d.ts +65 -0
  14. package/dist/capability.js +157 -0
  15. package/dist/capability.test.d.ts +1 -0
  16. package/dist/capability.test.js +269 -0
  17. package/dist/chat-contract.d.ts +81 -0
  18. package/dist/chat-contract.js +68 -0
  19. package/dist/cognition-planner.d.ts +15 -0
  20. package/dist/cognition-planner.js +23 -0
  21. package/dist/context-retriever.d.ts +24 -0
  22. package/dist/context-retriever.js +92 -0
  23. package/dist/context.d.ts +45 -0
  24. package/dist/context.js +72 -0
  25. package/dist/context.test.d.ts +1 -0
  26. package/dist/context.test.js +76 -0
  27. package/dist/dispatcher.d.ts +14 -0
  28. package/dist/dispatcher.js +40 -0
  29. package/dist/dispatcher.test.d.ts +1 -0
  30. package/dist/dispatcher.test.js +60 -0
  31. package/dist/ear-types.d.ts +33 -0
  32. package/dist/ear-types.js +2 -0
  33. package/dist/evidence.d.ts +76 -0
  34. package/dist/evidence.js +47 -0
  35. package/dist/evidence.test.d.ts +1 -0
  36. package/dist/evidence.test.js +101 -0
  37. package/dist/experience-emitter.d.ts +21 -0
  38. package/dist/experience-emitter.js +48 -0
  39. package/dist/experience.d.ts +57 -0
  40. package/dist/experience.js +79 -0
  41. package/dist/experience.test.d.ts +1 -0
  42. package/dist/experience.test.js +59 -0
  43. package/dist/gating.d.ts +46 -0
  44. package/dist/gating.js +185 -0
  45. package/dist/gating.test.d.ts +1 -0
  46. package/dist/gating.test.js +190 -0
  47. package/dist/index.d.ts +250 -0
  48. package/dist/index.js +43 -0
  49. package/dist/input-normalizer.d.ts +14 -0
  50. package/dist/input-normalizer.js +58 -0
  51. package/dist/input-normalizer.test.d.ts +1 -0
  52. package/dist/input-normalizer.test.js +39 -0
  53. package/dist/intent-classifier.d.ts +24 -0
  54. package/dist/intent-classifier.js +53 -0
  55. package/dist/intent-classifier.test.d.ts +1 -0
  56. package/dist/intent-classifier.test.js +68 -0
  57. package/dist/memory-settler.d.ts +27 -0
  58. package/dist/memory-settler.js +95 -0
  59. package/dist/mouth-types.d.ts +85 -0
  60. package/dist/mouth-types.js +2 -0
  61. package/dist/perception-cycle.test.d.ts +1 -0
  62. package/dist/perception-cycle.test.js +155 -0
  63. package/dist/prompt-compiler.d.ts +20 -0
  64. package/dist/prompt-compiler.js +57 -0
  65. package/dist/prompt-compiler.test.d.ts +1 -0
  66. package/dist/prompt-compiler.test.js +76 -0
  67. package/dist/proposals.d.ts +30 -0
  68. package/dist/proposals.js +2 -0
  69. package/dist/response-envelope.d.ts +25 -0
  70. package/dist/response-envelope.js +64 -0
  71. package/dist/runtime-facades.test.d.ts +1 -0
  72. package/dist/runtime-facades.test.js +69 -0
  73. package/dist/runtime.d.ts +114 -0
  74. package/dist/runtime.js +412 -0
  75. package/dist/session-history.d.ts +20 -0
  76. package/dist/session-history.js +55 -0
  77. package/dist/session-history.test.d.ts +1 -0
  78. package/dist/session-history.test.js +38 -0
  79. package/dist/sqlite-action-store.d.ts +20 -0
  80. package/dist/sqlite-action-store.js +225 -0
  81. package/dist/sqlite-action-store.test.d.ts +1 -0
  82. package/dist/sqlite-action-store.test.js +252 -0
  83. package/dist/teaching.d.ts +15 -0
  84. package/dist/teaching.js +159 -0
  85. package/package.json +1 -1
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const gating_1 = require("./gating");
4
+ describe('T4 Response Gating & Staged Approval Suite', () => {
5
+ let engine;
6
+ const validPublicContext = {
7
+ companionId: 'companion-a',
8
+ actor: {
9
+ actorId: 'actor-a',
10
+ sessionId: 'session-a',
11
+ authorizationRole: 'viewer',
12
+ capabilities: ['chat:public'],
13
+ authenticated: false,
14
+ },
15
+ conversation: {
16
+ channel: 'public',
17
+ audienceId: 'audience-public',
18
+ correlationId: 'corr-gate-1',
19
+ },
20
+ };
21
+ beforeEach(() => {
22
+ engine = new gating_1.ResponseGatingEngine();
23
+ });
24
+ test('valid grounded response without special approval requirement is approved/admissible directly', () => {
25
+ const evidence = {
26
+ evidenceId: 'ev-pub-1',
27
+ sourceId: 'src-wiki',
28
+ origin: 'knowledge',
29
+ trust: 'configured',
30
+ sensitivity: 'public',
31
+ allowedAudiences: ['audience-public'],
32
+ companionId: 'companion-a',
33
+ correlationId: 'corr-gate-1',
34
+ createdAt: new Date().toISOString(),
35
+ };
36
+ const staged = engine.stageResponse({
37
+ requestContext: validPublicContext,
38
+ candidateSpeech: 'Here is grounded public knowledge.',
39
+ candidateLanguage: 'en',
40
+ evidenceRecords: [evidence],
41
+ citations: [{ sourceId: 'src-wiki', revision: 'r1' }],
42
+ });
43
+ expect(staged.requiresApproval).toBe(false);
44
+ expect(staged.status).toBe('STAGED');
45
+ const evaluation = engine.evaluateGate(staged, [evidence]);
46
+ expect(evaluation.admissible).toBe(true);
47
+ expect(evaluation.disposition).toBe('APPROVED');
48
+ expect(evaluation.reasonCode).toBe('APPROVED_DIRECT');
49
+ expect(evaluation.filteredEvidenceIds).toEqual(['ev-pub-1']);
50
+ expect(evaluation.filteredCitations.length).toBe(1);
51
+ });
52
+ test('response containing untrusted OCR evidence requires approval and remains staged until approved', () => {
53
+ const ocrEvidence = {
54
+ evidenceId: 'ev-ocr-1',
55
+ sourceId: 'vision-ocr',
56
+ origin: 'ocr',
57
+ trust: 'untrusted',
58
+ sensitivity: 'public',
59
+ allowedAudiences: ['audience-public'],
60
+ companionId: 'companion-a',
61
+ correlationId: 'corr-gate-2',
62
+ createdAt: new Date().toISOString(),
63
+ uncertainty: 'OCR detected prompt injection text',
64
+ };
65
+ const staged = engine.stageResponse({
66
+ requestContext: {
67
+ ...validPublicContext,
68
+ conversation: { ...validPublicContext.conversation, correlationId: 'corr-gate-2' },
69
+ },
70
+ candidateSpeech: 'Text extracted from image.',
71
+ candidateLanguage: 'en',
72
+ evidenceRecords: [ocrEvidence],
73
+ });
74
+ expect(staged.requiresApproval).toBe(true);
75
+ expect(staged.status).toBe('STAGED');
76
+ // Before approval -> gating holds it
77
+ const beforeApproval = engine.evaluateGate(staged, [ocrEvidence]);
78
+ expect(beforeApproval.admissible).toBe(false);
79
+ expect(beforeApproval.disposition).toBe('STAGED');
80
+ expect(beforeApproval.reasonCode).toBe('APPROVAL_REQUIRED');
81
+ // Operator approves the response
82
+ const approveResult = engine.approveResponse({
83
+ responseId: staged.responseId,
84
+ companionId: 'companion-a',
85
+ correlationId: 'corr-gate-2',
86
+ });
87
+ expect(approveResult.success).toBe(true);
88
+ // After approval -> gating allows it
89
+ const afterApproval = engine.evaluateGate(staged, [ocrEvidence]);
90
+ expect(afterApproval.admissible).toBe(true);
91
+ expect(afterApproval.disposition).toBe('APPROVED');
92
+ expect(afterApproval.reasonCode).toBe('APPROVED_DIRECT');
93
+ });
94
+ test('unknown or mismatched approval ID is rejected', () => {
95
+ const staged = engine.stageResponse({
96
+ requestContext: validPublicContext,
97
+ candidateSpeech: 'Requires approval',
98
+ candidateLanguage: 'en',
99
+ requiresApproval: true,
100
+ });
101
+ // Unknown response ID
102
+ const unknownRes = engine.approveResponse({
103
+ responseId: 'non-existent-resp',
104
+ companionId: 'companion-a',
105
+ correlationId: 'corr-gate-1',
106
+ });
107
+ expect(unknownRes.success).toBe(false);
108
+ expect(unknownRes.reason).toBe('UNKNOWN_APPROVAL_ID');
109
+ // Companion mismatch
110
+ const companionMismatch = engine.approveResponse({
111
+ responseId: staged.responseId,
112
+ companionId: 'companion-other',
113
+ correlationId: 'corr-gate-1',
114
+ });
115
+ expect(companionMismatch.success).toBe(false);
116
+ expect(companionMismatch.reason).toBe('COMPANION_MISMATCH');
117
+ // Correlation ID mismatch
118
+ const correlationMismatch = engine.approveResponse({
119
+ responseId: staged.responseId,
120
+ companionId: 'companion-a',
121
+ correlationId: 'corr-mismatch',
122
+ });
123
+ expect(correlationMismatch.success).toBe(false);
124
+ expect(correlationMismatch.reason).toBe('APPROVAL_ID_MISMATCH');
125
+ });
126
+ test('rejection prevents response from ever reaching emission', () => {
127
+ const staged = engine.stageResponse({
128
+ requestContext: validPublicContext,
129
+ candidateSpeech: 'Potentially unsafe response',
130
+ candidateLanguage: 'en',
131
+ requiresApproval: true,
132
+ });
133
+ const rejectRes = engine.rejectResponse({
134
+ responseId: staged.responseId,
135
+ companionId: 'companion-a',
136
+ correlationId: 'corr-gate-1',
137
+ });
138
+ expect(rejectRes.success).toBe(true);
139
+ const evalRes = engine.evaluateGate(staged);
140
+ expect(evalRes.admissible).toBe(false);
141
+ expect(evalRes.disposition).toBe('REJECTED');
142
+ expect(evalRes.reasonCode).toBe('EXPLICITLY_REJECTED');
143
+ });
144
+ test('expired staged response plan cannot be approved or emitted', () => {
145
+ const staged = engine.stageResponse({
146
+ requestContext: validPublicContext,
147
+ candidateSpeech: 'Expired response',
148
+ candidateLanguage: 'en',
149
+ requiresApproval: true,
150
+ ttlMs: 100,
151
+ now: new Date(Date.now() - 500),
152
+ });
153
+ const evalRes = engine.evaluateGate(staged, [], new Date());
154
+ expect(evalRes.admissible).toBe(false);
155
+ expect(evalRes.disposition).toBe('EXPIRED');
156
+ expect(evalRes.reasonCode).toBe('EVIDENCE_EXPIRED');
157
+ const approveRes = engine.approveResponse({
158
+ responseId: staged.responseId,
159
+ companionId: 'companion-a',
160
+ correlationId: 'corr-gate-1',
161
+ });
162
+ expect(approveRes.success).toBe(false);
163
+ expect(approveRes.reason).toBe('EVIDENCE_EXPIRED');
164
+ });
165
+ test('private evidence is filtered out and absent from public citations/evidence list', () => {
166
+ const privateEvidence = {
167
+ evidenceId: 'ev-priv-1',
168
+ sourceId: 'src-secret',
169
+ origin: 'knowledge',
170
+ trust: 'configured',
171
+ sensitivity: 'private',
172
+ allowedAudiences: ['audience-direct-a'],
173
+ companionId: 'companion-a',
174
+ correlationId: 'corr-gate-1',
175
+ createdAt: new Date().toISOString(),
176
+ };
177
+ const staged = engine.stageResponse({
178
+ requestContext: validPublicContext, // public channel
179
+ candidateSpeech: 'Public response text',
180
+ candidateLanguage: 'en',
181
+ evidenceRecords: [privateEvidence],
182
+ citations: [{ sourceId: 'src-secret' }],
183
+ });
184
+ const evalRes = engine.evaluateGate(staged, [privateEvidence]);
185
+ expect(evalRes.admissible).toBe(true);
186
+ // Private evidence and its citation must be stripped from public output metadata
187
+ expect(evalRes.filteredEvidenceIds).toEqual([]);
188
+ expect(evalRes.filteredCitations).toEqual([]);
189
+ });
190
+ });
@@ -0,0 +1,250 @@
1
+ export * from './context';
2
+ export * from './proposals';
3
+ export * from './evidence';
4
+ export * from './gating';
5
+ export * from './experience';
6
+ export * from './dispatcher';
7
+ export * from './action';
8
+ export * from './action-policy';
9
+ export * from './ear-types';
10
+ export * from './capability';
11
+ export * from './sqlite-action-store';
12
+ export * from './teaching';
13
+ export * from './runtime';
14
+ export * from './chat-contract';
15
+ export * from './input-normalizer';
16
+ export * from './intent-classifier';
17
+ export * from './context-retriever';
18
+ export * from './prompt-compiler';
19
+ export * from './cognition-planner';
20
+ export * from './memory-settler';
21
+ export * from './action-executor';
22
+ export * from './experience-emitter';
23
+ export * from './response-envelope';
24
+ export * from './session-history';
25
+ import { EvidenceRecord } from './evidence';
26
+ import { ActionIntent } from './action';
27
+ import { RequestContext } from './context';
28
+ import { EarIngestOptions } from './ear-types';
29
+ import { ClaimType, ClaimAuthority, ClaimStatus, SourceEvent, MemoryProposal, BehaviorProposal } from './proposals';
30
+ export interface OrganConfig {
31
+ provider: string;
32
+ [key: string]: unknown;
33
+ }
34
+ export interface CompanionConfig {
35
+ id: string;
36
+ name: string;
37
+ brain: OrganConfig;
38
+ voice: OrganConfig;
39
+ memory: OrganConfig;
40
+ knowledge: OrganConfig;
41
+ behavior: OrganConfig;
42
+ body: OrganConfig;
43
+ vision: OrganConfig;
44
+ }
45
+ export interface Message {
46
+ role: 'user' | 'assistant' | 'system';
47
+ content: string;
48
+ name?: string;
49
+ }
50
+ export interface BrainContext {
51
+ systemPrompt: string;
52
+ contextPrompt: string;
53
+ recentMessages: Message[];
54
+ recipient?: MemoryScope;
55
+ }
56
+ export interface ResponsePlan {
57
+ speech: string;
58
+ language: string;
59
+ memoryProposals?: MemoryProposal[];
60
+ behaviorProposals?: BehaviorProposal[];
61
+ actionIntents?: ActionIntent[];
62
+ internalMonologue?: string;
63
+ }
64
+ export interface BrainOrgan {
65
+ generatePlan(context: BrainContext): Promise<ResponsePlan>;
66
+ }
67
+ export type MemoryScope = 'companion' | 'user' | string;
68
+ export interface Claim {
69
+ id: string;
70
+ subject: string;
71
+ predicate: string;
72
+ value: string;
73
+ status: ClaimStatus;
74
+ evidence?: string[];
75
+ scope?: MemoryScope;
76
+ companionId: string;
77
+ provenance?: string;
78
+ sourceEventId?: string;
79
+ claimType?: ClaimType;
80
+ authority?: ClaimAuthority;
81
+ userConfirmation?: 'explicit' | 'implied' | 'none';
82
+ sensitivity?: string;
83
+ confidence?: number;
84
+ assertedAt?: string;
85
+ validFrom?: string;
86
+ validUntil?: string;
87
+ supersedes?: string;
88
+ replaces?: string;
89
+ [key: string]: unknown;
90
+ }
91
+ export interface BehaviorDirective {
92
+ id: string;
93
+ companionId: string;
94
+ directive: string;
95
+ priority: number;
96
+ status: 'PENDING' | 'ACTIVE' | 'DISABLED' | 'SUPERSEDED' | 'REJECTED' | 'REVOKED' | 'EXPIRED';
97
+ supersedesId?: string;
98
+ memoryClass?: 'identity' | 'relationship' | 'behavioral';
99
+ subject?: string;
100
+ predicate?: string;
101
+ value?: string;
102
+ validFrom?: string;
103
+ validUntil?: string;
104
+ [key: string]: unknown;
105
+ }
106
+ export interface MemoryQueryOptions {
107
+ sensitivity?: string;
108
+ limit?: number;
109
+ minConfidence?: number;
110
+ now?: string | Date;
111
+ [key: string]: unknown;
112
+ }
113
+ export interface MemoryOrgan {
114
+ initialize(companionId: string): Promise<void>;
115
+ proposeClaim(claim: Omit<Claim, 'id' | 'status' | 'companionId'>): Promise<Claim>;
116
+ searchClaims(query: string, scopeOrOptions?: MemoryScope | MemoryQueryOptions, limit?: number): Promise<Claim[]>;
117
+ getClaims(limit?: number): Promise<Claim[]>;
118
+ getPendingClaims(limit?: number): Promise<Claim[]>;
119
+ approveClaim(id: string): Promise<void>;
120
+ rejectClaim(id: string): Promise<void>;
121
+ markClaimSessionOnly?(id: string): Promise<void>;
122
+ expireClaim?(id: string): Promise<void>;
123
+ revokeClaim?(id: string, reason?: string): Promise<void>;
124
+ getDirectives(): Promise<BehaviorDirective[]>;
125
+ proposeDirective(directiveData: Omit<BehaviorDirective, 'id' | 'status' | 'companionId'>): Promise<BehaviorDirective>;
126
+ approveDirective(id: string): Promise<void>;
127
+ rejectDirective(id: string): Promise<void>;
128
+ revokeDirective(id: string): Promise<void>;
129
+ disableDirective(id: string): Promise<void>;
130
+ expireDirective?(id: string): Promise<void>;
131
+ supersedeClaim?(id: string, replacement: Omit<Claim, 'id' | 'status' | 'companionId'>): Promise<Claim>;
132
+ updateClaim?(id: string, updates: Partial<Pick<Claim, 'subject' | 'predicate' | 'value' | 'scope' | 'sensitivity' | 'confidence' | 'validFrom' | 'validUntil'>>): Promise<Claim>;
133
+ resetMemory?(): Promise<void>;
134
+ addSourceEvent?(event: SourceEvent): Promise<SourceEvent>;
135
+ getSourceEvent?(id: string): Promise<SourceEvent | undefined>;
136
+ }
137
+ export interface AudioEvent {
138
+ type: 'STARTED' | 'COMPLETED' | 'FAILED';
139
+ speechId: string;
140
+ text?: string;
141
+ language?: string;
142
+ audioBuffer?: Uint8Array;
143
+ }
144
+ export interface VoiceOrgan {
145
+ enqueueSpeech(text: string, language: string, priority?: number): string;
146
+ onLifecycleEvent(callback: (event: AudioEvent) => void): void;
147
+ getQueueStatus(): {
148
+ pending: number;
149
+ current?: string;
150
+ };
151
+ }
152
+ export interface VisionOrgan {
153
+ analyze(imageUrl: string, prompt: string): Promise<string>;
154
+ }
155
+ export interface ActiveSelfProjection {
156
+ identityFacts: string[];
157
+ relationshipFacts: string[];
158
+ behavioralRules: string[];
159
+ activeIds: string[];
160
+ excludedIds: string[];
161
+ diagnostics: Record<string, string>;
162
+ render(): string;
163
+ }
164
+ export interface BehaviorContext {
165
+ directives: BehaviorDirective[];
166
+ companionId?: string;
167
+ actorId?: string;
168
+ sessionId?: string;
169
+ now?: string;
170
+ [key: string]: unknown;
171
+ }
172
+ export interface BehaviorOrgan {
173
+ compile(context: BehaviorContext): Promise<string>;
174
+ compileProjection?(context: BehaviorContext): Promise<ActiveSelfProjection>;
175
+ }
176
+ export interface KnowledgeItem {
177
+ content: string;
178
+ provenance: string;
179
+ revision: string;
180
+ citations: KnowledgeCitation[];
181
+ evidenceRecord?: EvidenceRecord;
182
+ }
183
+ export interface KnowledgeCitation {
184
+ sourceId: string;
185
+ documentId?: string;
186
+ chunkId?: string;
187
+ locator?: string;
188
+ }
189
+ export interface KnowledgeOrgan {
190
+ search(query: string): Promise<KnowledgeItem[]>;
191
+ }
192
+ export interface EarPerception {
193
+ id: string;
194
+ source: string;
195
+ text?: string;
196
+ audioBuffer?: Uint8Array;
197
+ metadata?: Record<string, unknown>;
198
+ timestamp: string;
199
+ }
200
+ export interface EarOrgan {
201
+ listen(source: string, payload: unknown, options?: EarIngestOptions): Promise<EarPerception>;
202
+ transcribeAudio?(audio: Uint8Array): Promise<string>;
203
+ classifyIntent?(text: string, context?: RequestContext): Promise<Record<string, unknown>> | Record<string, unknown>;
204
+ }
205
+ export interface BodyOrgan {
206
+ setExpression(expression: string): void;
207
+ speak(speechId: string, text?: string, language?: string): void;
208
+ act(action: string): void;
209
+ completeAction?(): void;
210
+ }
211
+ export interface ObservationReading {
212
+ entity: string;
213
+ value: string;
214
+ confidence: number;
215
+ sourceCrop?: string;
216
+ ocrText?: string;
217
+ competingInterpretations?: string[];
218
+ }
219
+ export interface Observation {
220
+ observationId: string;
221
+ evidenceId: string;
222
+ sourceName: string;
223
+ providerId: string;
224
+ readings: ObservationReading[];
225
+ confidence: number;
226
+ createdAt: string;
227
+ expiresAt: string;
228
+ frameDigest: string;
229
+ }
230
+ export interface ObservationResult {
231
+ observation?: Observation;
232
+ duplicate: boolean;
233
+ reason?: 'empty_frame' | 'duplicate_frame' | 'invalid_reading' | 'provider_failure';
234
+ }
235
+ export interface ObservationOrgan {
236
+ ingest(frame: Uint8Array, sourceName: string, providerId?: string): Promise<ObservationResult>;
237
+ current(now?: Date): Observation[];
238
+ clearExpired(now?: Date): number;
239
+ }
240
+ export interface HealthProbeContext {
241
+ config: unknown;
242
+ env: NodeJS.ProcessEnv | Record<string, string | undefined>;
243
+ }
244
+ export interface HealthProbeResult {
245
+ ok: boolean;
246
+ message?: string;
247
+ details?: Record<string, unknown>;
248
+ }
249
+ export type HealthProbeFn = (context: HealthProbeContext) => Promise<HealthProbeResult> | HealthProbeResult;
250
+ export * from './mouth-types';
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // Export neutral context types and validator
18
+ __exportStar(require("./context"), exports);
19
+ __exportStar(require("./proposals"), exports);
20
+ __exportStar(require("./evidence"), exports);
21
+ __exportStar(require("./gating"), exports);
22
+ __exportStar(require("./experience"), exports);
23
+ __exportStar(require("./dispatcher"), exports);
24
+ __exportStar(require("./action"), exports);
25
+ __exportStar(require("./action-policy"), exports);
26
+ __exportStar(require("./ear-types"), exports);
27
+ __exportStar(require("./capability"), exports);
28
+ __exportStar(require("./sqlite-action-store"), exports);
29
+ __exportStar(require("./teaching"), exports);
30
+ __exportStar(require("./runtime"), exports);
31
+ __exportStar(require("./chat-contract"), exports);
32
+ __exportStar(require("./input-normalizer"), exports);
33
+ __exportStar(require("./intent-classifier"), exports);
34
+ __exportStar(require("./context-retriever"), exports);
35
+ __exportStar(require("./prompt-compiler"), exports);
36
+ __exportStar(require("./cognition-planner"), exports);
37
+ __exportStar(require("./memory-settler"), exports);
38
+ __exportStar(require("./action-executor"), exports);
39
+ __exportStar(require("./experience-emitter"), exports);
40
+ __exportStar(require("./response-envelope"), exports);
41
+ __exportStar(require("./session-history"), exports);
42
+ // Mouth (Communication & Output Delivery)
43
+ __exportStar(require("./mouth-types"), exports);
@@ -0,0 +1,14 @@
1
+ import { Message, RequestContext, EarOrgan } from './index';
2
+ export interface NormalizedInput {
3
+ perceivedText: string;
4
+ sanitizedText: string;
5
+ requestContext: RequestContext;
6
+ role: 'OWNER' | 'VIEWER' | 'OPERATOR';
7
+ isContextObject: boolean;
8
+ boundedHistory: Message[];
9
+ }
10
+ /**
11
+ * Validates raw user input, synthesizes RequestContext, normalizes history,
12
+ * and routes input through EarOrgan if available.
13
+ */
14
+ export declare function normalizeUserInput(message: string, roleOrContext: ("OWNER" | "VIEWER" | "OPERATOR" | RequestContext | string) | undefined, history: Message[] | undefined, companionId: string, ear?: EarOrgan): Promise<NormalizedInput>;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeUserInput = normalizeUserInput;
4
+ /**
5
+ * Validates raw user input, synthesizes RequestContext, normalizes history,
6
+ * and routes input through EarOrgan if available.
7
+ */
8
+ async function normalizeUserInput(message, roleOrContext = 'OWNER', history = [], companionId, ear) {
9
+ if (typeof message !== 'string' || !message.trim() || message.length > 4000) {
10
+ throw new Error('message must be a non-empty string of at most 4000 characters');
11
+ }
12
+ if (!Array.isArray(history) ||
13
+ history.length > 20 ||
14
+ history.some((item) => !item || !['user', 'assistant'].includes(item.role) || typeof item.content !== 'string')) {
15
+ throw new Error('history must contain at most 20 user/assistant messages');
16
+ }
17
+ const isContextObject = typeof roleOrContext === 'object' && roleOrContext !== null;
18
+ const role = isContextObject
19
+ ? (roleOrContext.actor?.authorizationRole === 'viewer' ? 'VIEWER' : 'OWNER')
20
+ : roleOrContext;
21
+ const requestContext = isContextObject
22
+ ? roleOrContext
23
+ : {
24
+ companionId,
25
+ actor: {
26
+ actorId: role === 'VIEWER' ? 'anonymous-session' : 'owner-user',
27
+ sessionId: `sess-${companionId}`,
28
+ authorizationRole: role === 'VIEWER' ? 'viewer' : 'administrator',
29
+ capabilities: role === 'VIEWER' ? ['chat'] : ['chat', 'memory:approve', 'action:execute'],
30
+ authenticated: role !== 'VIEWER',
31
+ },
32
+ conversation: {
33
+ channel: 'direct',
34
+ correlationId: `corr-${Date.now()}`,
35
+ },
36
+ source: 'local',
37
+ };
38
+ // Universal Perception: Route user input through EarOrgan if available
39
+ let perceivedText = message;
40
+ if (ear && typeof ear.listen === 'function') {
41
+ const perception = await ear.listen('text_chat', message, {
42
+ context: requestContext,
43
+ });
44
+ perceivedText = perception.text || message;
45
+ }
46
+ const boundedHistory = history.map((item) => ({
47
+ role: item.role,
48
+ content: item.content.slice(0, 2000).replace(/\0/g, ''),
49
+ }));
50
+ return {
51
+ perceivedText,
52
+ sanitizedText: message,
53
+ requestContext,
54
+ role,
55
+ isContextObject,
56
+ boundedHistory,
57
+ };
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const input_normalizer_1 = require("./input-normalizer");
4
+ describe('InputNormalizer', () => {
5
+ test('rejects empty or oversized messages', async () => {
6
+ await expect((0, input_normalizer_1.normalizeUserInput)('', 'OWNER', [], 'comp-1')).rejects.toThrow('message must be a non-empty string of at most 4000 characters');
7
+ await expect((0, input_normalizer_1.normalizeUserInput)(' ', 'OWNER', [], 'comp-1')).rejects.toThrow('message must be a non-empty string of at most 4000 characters');
8
+ const oversized = 'a'.repeat(4001);
9
+ await expect((0, input_normalizer_1.normalizeUserInput)(oversized, 'OWNER', [], 'comp-1')).rejects.toThrow('message must be a non-empty string of at most 4000 characters');
10
+ });
11
+ test('rejects oversized history or invalid message roles', async () => {
12
+ const invalidHistory = Array(21).fill({ role: 'user', content: 'hi' });
13
+ await expect((0, input_normalizer_1.normalizeUserInput)('hi', 'OWNER', invalidHistory, 'comp-1')).rejects.toThrow('history must contain at most 20 user/assistant messages');
14
+ const malformedRole = [{ role: 'bad_role', content: 'test' }];
15
+ await expect((0, input_normalizer_1.normalizeUserInput)('hi', 'OWNER', malformedRole, 'comp-1')).rejects.toThrow('history must contain at most 20 user/assistant messages');
16
+ });
17
+ test('normalizes role string into full RequestContext and strips null bytes from history', async () => {
18
+ const historyWithNulls = [{ role: 'user', content: 'hello\0world' }];
19
+ const result = await (0, input_normalizer_1.normalizeUserInput)('hello', 'OWNER', historyWithNulls, 'comp-1');
20
+ expect(result.role).toBe('OWNER');
21
+ expect(result.requestContext.companionId).toBe('comp-1');
22
+ expect(result.requestContext.actor.authorizationRole).toBe('administrator');
23
+ expect(result.requestContext.actor.authenticated).toBe(true);
24
+ expect(result.boundedHistory[0].content).toBe('helloworld');
25
+ });
26
+ test('routes input through EarOrgan when present', async () => {
27
+ const mockEar = {
28
+ listen: jest.fn().mockResolvedValue({
29
+ id: 'perc-1',
30
+ source: 'text_chat',
31
+ text: 'transcribed text',
32
+ timestamp: new Date().toISOString(),
33
+ }),
34
+ };
35
+ const result = await (0, input_normalizer_1.normalizeUserInput)('raw text', 'VIEWER', [], 'comp-1', mockEar);
36
+ expect(mockEar.listen).toHaveBeenCalledWith('text_chat', 'raw text', expect.anything());
37
+ expect(result.perceivedText).toBe('transcribed text');
38
+ });
39
+ });
@@ -0,0 +1,24 @@
1
+ import { RequestContext } from './index';
2
+ import { extractDeterministicTeaching } from './teaching';
3
+ export interface IntentClassification {
4
+ normalizedMessage: string;
5
+ explicitTeaching: ReturnType<typeof extractDeterministicTeaching>;
6
+ isTeachingLike: boolean;
7
+ isSelfIdentityRequest: boolean;
8
+ isGreeting: boolean;
9
+ shouldQueryKnowledge: boolean;
10
+ confidence?: number;
11
+ classifierOrigin?: 'heuristic' | 'cognitive' | 'ear';
12
+ }
13
+ export type CognitiveIntentClassifier = (text: string, context: RequestContext) => Promise<Partial<IntentClassification>> | Partial<IntentClassification>;
14
+ /**
15
+ * Evaluates domain heuristics and intent classification for user messages.
16
+ * Determines if deterministic teaching is present and whether external knowledge
17
+ * retrieval is appropriate.
18
+ */
19
+ export declare function classifyInputIntent(text: string, context: RequestContext, overrides?: Partial<IntentClassification>): IntentClassification;
20
+ /**
21
+ * Asynchronously classifies intent, allowing an EarOrgan or cognitive model
22
+ * to provide semantic classification before falling back to heuristics.
23
+ */
24
+ export declare function classifyInputIntentAsync(text: string, context: RequestContext, classifier?: CognitiveIntentClassifier): Promise<IntentClassification>;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyInputIntent = classifyInputIntent;
4
+ exports.classifyInputIntentAsync = classifyInputIntentAsync;
5
+ const teaching_1 = require("./teaching");
6
+ /**
7
+ * Evaluates domain heuristics and intent classification for user messages.
8
+ * Determines if deterministic teaching is present and whether external knowledge
9
+ * retrieval is appropriate.
10
+ */
11
+ function classifyInputIntent(text, context, overrides) {
12
+ const normalizedMessage = text.replace(/\s+/g, ' ').trim().toLowerCase();
13
+ const explicitTeaching = (0, teaching_1.extractDeterministicTeaching)(text, context);
14
+ const isTeachingLike = overrides?.isTeachingLike ??
15
+ (explicitTeaching.claims.length > 0 ||
16
+ explicitTeaching.behaviorProposals.length > 0 ||
17
+ /\bremember that\b/.test(normalizedMessage));
18
+ const isSelfIdentityRequest = overrides?.isSelfIdentityRequest ??
19
+ /\b(?:who|what) are you\b|\bwho is siduri\b|\b(?:your|my) name\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);
20
+ const isGreeting = overrides?.isGreeting ??
21
+ /^(?:hello|hi|hey|greetings|good morning|good afternoon|good evening|howdy|yo)[.!]?$/.test(normalizedMessage);
22
+ const shouldQueryKnowledge = overrides?.shouldQueryKnowledge ??
23
+ (!isTeachingLike && !isSelfIdentityRequest && !isGreeting);
24
+ return {
25
+ normalizedMessage,
26
+ explicitTeaching,
27
+ isTeachingLike,
28
+ isSelfIdentityRequest,
29
+ isGreeting,
30
+ shouldQueryKnowledge,
31
+ confidence: overrides?.confidence ?? 0.95,
32
+ classifierOrigin: overrides?.classifierOrigin ?? 'heuristic',
33
+ };
34
+ }
35
+ /**
36
+ * Asynchronously classifies intent, allowing an EarOrgan or cognitive model
37
+ * to provide semantic classification before falling back to heuristics.
38
+ */
39
+ async function classifyInputIntentAsync(text, context, classifier) {
40
+ if (!classifier) {
41
+ return classifyInputIntent(text, context);
42
+ }
43
+ try {
44
+ const cognitiveResult = await classifier(text, context);
45
+ return classifyInputIntent(text, context, {
46
+ ...cognitiveResult,
47
+ classifierOrigin: cognitiveResult.classifierOrigin || 'cognitive',
48
+ });
49
+ }
50
+ catch (err) {
51
+ return classifyInputIntent(text, context);
52
+ }
53
+ }