@affectively/aeon-pages 1.3.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.
- package/CHANGELOG.md +112 -0
- package/README.md +625 -0
- package/examples/basic/aeon.config.ts +39 -0
- package/examples/basic/components/Cursor.tsx +86 -0
- package/examples/basic/components/OfflineIndicator.tsx +103 -0
- package/examples/basic/components/PresenceBar.tsx +77 -0
- package/examples/basic/package.json +20 -0
- package/examples/basic/pages/index.tsx +80 -0
- package/package.json +101 -0
- package/packages/analytics/README.md +309 -0
- package/packages/analytics/build.ts +35 -0
- package/packages/analytics/package.json +50 -0
- package/packages/analytics/src/click-tracker.ts +368 -0
- package/packages/analytics/src/context-bridge.ts +319 -0
- package/packages/analytics/src/data-layer.ts +302 -0
- package/packages/analytics/src/gtm-loader.ts +239 -0
- package/packages/analytics/src/index.ts +230 -0
- package/packages/analytics/src/merkle-tree.ts +489 -0
- package/packages/analytics/src/provider.tsx +300 -0
- package/packages/analytics/src/types.ts +320 -0
- package/packages/analytics/src/use-analytics.ts +296 -0
- package/packages/analytics/tsconfig.json +19 -0
- package/packages/benchmarks/src/benchmark.test.ts +691 -0
- package/packages/cli/dist/index.js +61899 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/commands/build.test.ts +682 -0
- package/packages/cli/src/commands/build.ts +890 -0
- package/packages/cli/src/commands/dev.ts +473 -0
- package/packages/cli/src/commands/init.ts +409 -0
- package/packages/cli/src/commands/start.ts +297 -0
- package/packages/cli/src/index.ts +105 -0
- package/packages/directives/src/use-aeon.ts +272 -0
- package/packages/mcp-server/package.json +51 -0
- package/packages/mcp-server/src/index.ts +178 -0
- package/packages/mcp-server/src/resources.ts +346 -0
- package/packages/mcp-server/src/tools/index.ts +36 -0
- package/packages/mcp-server/src/tools/navigation.ts +545 -0
- package/packages/mcp-server/tsconfig.json +21 -0
- package/packages/react/package.json +40 -0
- package/packages/react/src/Link.tsx +388 -0
- package/packages/react/src/components/InstallPrompt.tsx +286 -0
- package/packages/react/src/components/OfflineDiagnostics.tsx +677 -0
- package/packages/react/src/components/PushNotifications.tsx +453 -0
- package/packages/react/src/hooks/useAeonNavigation.ts +219 -0
- package/packages/react/src/hooks/useConflicts.ts +277 -0
- package/packages/react/src/hooks/useNetworkState.ts +209 -0
- package/packages/react/src/hooks/usePilotNavigation.ts +254 -0
- package/packages/react/src/hooks/useServiceWorker.ts +278 -0
- package/packages/react/src/hooks.ts +195 -0
- package/packages/react/src/index.ts +151 -0
- package/packages/react/src/provider.tsx +467 -0
- package/packages/react/tsconfig.json +19 -0
- package/packages/runtime/README.md +399 -0
- package/packages/runtime/build.ts +48 -0
- package/packages/runtime/package.json +71 -0
- package/packages/runtime/schema.sql +40 -0
- package/packages/runtime/src/api-routes.ts +465 -0
- package/packages/runtime/src/benchmark.ts +171 -0
- package/packages/runtime/src/cache.ts +479 -0
- package/packages/runtime/src/durable-object.ts +1341 -0
- package/packages/runtime/src/index.ts +360 -0
- package/packages/runtime/src/navigation.test.ts +421 -0
- package/packages/runtime/src/navigation.ts +422 -0
- package/packages/runtime/src/nextjs-adapter.ts +272 -0
- package/packages/runtime/src/offline/encrypted-queue.test.ts +607 -0
- package/packages/runtime/src/offline/encrypted-queue.ts +478 -0
- package/packages/runtime/src/offline/encryption.test.ts +412 -0
- package/packages/runtime/src/offline/encryption.ts +397 -0
- package/packages/runtime/src/offline/types.ts +465 -0
- package/packages/runtime/src/predictor.ts +371 -0
- package/packages/runtime/src/registry.ts +351 -0
- package/packages/runtime/src/router/context-extractor.ts +661 -0
- package/packages/runtime/src/router/esi-control-react.tsx +2053 -0
- package/packages/runtime/src/router/esi-control.ts +541 -0
- package/packages/runtime/src/router/esi-cyrano.ts +779 -0
- package/packages/runtime/src/router/esi-format-react.tsx +1744 -0
- package/packages/runtime/src/router/esi-react.tsx +1065 -0
- package/packages/runtime/src/router/esi-translate-observer.ts +476 -0
- package/packages/runtime/src/router/esi-translate-react.tsx +556 -0
- package/packages/runtime/src/router/esi-translate.ts +503 -0
- package/packages/runtime/src/router/esi.ts +666 -0
- package/packages/runtime/src/router/heuristic-adapter.test.ts +295 -0
- package/packages/runtime/src/router/heuristic-adapter.ts +557 -0
- package/packages/runtime/src/router/index.ts +298 -0
- package/packages/runtime/src/router/merkle-capability.ts +473 -0
- package/packages/runtime/src/router/speculation.ts +451 -0
- package/packages/runtime/src/router/types.ts +630 -0
- package/packages/runtime/src/router.test.ts +470 -0
- package/packages/runtime/src/router.ts +302 -0
- package/packages/runtime/src/server.ts +481 -0
- package/packages/runtime/src/service-worker-push.ts +319 -0
- package/packages/runtime/src/service-worker.ts +553 -0
- package/packages/runtime/src/skeleton-hydrate.ts +237 -0
- package/packages/runtime/src/speculation.test.ts +389 -0
- package/packages/runtime/src/speculation.ts +486 -0
- package/packages/runtime/src/storage.test.ts +1297 -0
- package/packages/runtime/src/storage.ts +1048 -0
- package/packages/runtime/src/sync/conflict-resolver.test.ts +528 -0
- package/packages/runtime/src/sync/conflict-resolver.ts +565 -0
- package/packages/runtime/src/sync/coordinator.test.ts +608 -0
- package/packages/runtime/src/sync/coordinator.ts +596 -0
- package/packages/runtime/src/tree-compiler.ts +295 -0
- package/packages/runtime/src/types.ts +728 -0
- package/packages/runtime/src/worker.ts +327 -0
- package/packages/runtime/tsconfig.json +20 -0
- package/packages/runtime/wasm/aeon_pages_runtime.d.ts +504 -0
- package/packages/runtime/wasm/aeon_pages_runtime.js +1657 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime/wasm/aeon_pages_runtime_bg.wasm.d.ts +196 -0
- package/packages/runtime/wasm/package.json +21 -0
- package/packages/runtime/wrangler.toml +41 -0
- package/packages/runtime-wasm/Cargo.lock +436 -0
- package/packages/runtime-wasm/Cargo.toml +29 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.d.ts +480 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime.js +1568 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm +0 -0
- package/packages/runtime-wasm/pkg/aeon_pages_runtime_bg.wasm.d.ts +192 -0
- package/packages/runtime-wasm/pkg/package.json +21 -0
- package/packages/runtime-wasm/src/hydrate.rs +352 -0
- package/packages/runtime-wasm/src/lib.rs +191 -0
- package/packages/runtime-wasm/src/render.rs +629 -0
- package/packages/runtime-wasm/src/router.rs +298 -0
- package/packages/runtime-wasm/src/skeleton.rs +430 -0
- package/rfcs/RFC-001-ZERO-DEPENDENCY-RENDERING.md +1446 -0
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ESI Cyrano Whisper Channel
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional ESI directives for ambient Cyrano intelligence:
|
|
5
|
+
* - esi:context - Site drops context INTO the stream (page → Cyrano)
|
|
6
|
+
* - esi:cyrano - Cyrano whispers back (Cyrano → page)
|
|
7
|
+
* - esi:halo - Halo meta-insight (Halo → page adaptation)
|
|
8
|
+
*
|
|
9
|
+
* These directives create "chat exhaust" - every interaction becomes
|
|
10
|
+
* part of the ongoing conversation between user and Cyrano.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ESIDirective,
|
|
15
|
+
ESIResult,
|
|
16
|
+
UserContext,
|
|
17
|
+
UserTier,
|
|
18
|
+
ESIParams,
|
|
19
|
+
} from './types';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// Whisper Channel Types
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Emotional state for context dropping
|
|
27
|
+
*/
|
|
28
|
+
export interface EmotionContext {
|
|
29
|
+
/** Primary detected emotion */
|
|
30
|
+
primary?: string;
|
|
31
|
+
/** Valence: negative (-1) to positive (1) */
|
|
32
|
+
valence?: number;
|
|
33
|
+
/** Arousal: calm (0) to excited (1) */
|
|
34
|
+
arousal?: number;
|
|
35
|
+
/** Dominance: submissive (0) to dominant (1) */
|
|
36
|
+
dominance?: number;
|
|
37
|
+
/** Detection source (facial, vocal, behavioral, combined) */
|
|
38
|
+
source?: 'facial' | 'vocal' | 'behavioral' | 'combined';
|
|
39
|
+
/** Confidence in detection (0-1) */
|
|
40
|
+
confidence?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Behavioral signals for context dropping
|
|
45
|
+
*/
|
|
46
|
+
export interface BehaviorContext {
|
|
47
|
+
/** Recent pages visited */
|
|
48
|
+
recentPages?: string[];
|
|
49
|
+
/** Scroll depth on current page (0-1) */
|
|
50
|
+
scrollDepth?: number;
|
|
51
|
+
/** Dwell time on current page (ms) */
|
|
52
|
+
dwellTime?: number;
|
|
53
|
+
/** Whether aimless clicking detected */
|
|
54
|
+
isAimlessClicking?: boolean;
|
|
55
|
+
/** Whether hesitation detected */
|
|
56
|
+
hesitationDetected?: boolean;
|
|
57
|
+
/** Inferred search intent */
|
|
58
|
+
searchingFor?: string;
|
|
59
|
+
/** Interaction velocity (clicks per minute) */
|
|
60
|
+
interactionVelocity?: number;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Environmental context
|
|
65
|
+
*/
|
|
66
|
+
export interface EnvironmentContext {
|
|
67
|
+
/** Weather conditions */
|
|
68
|
+
weather?: {
|
|
69
|
+
temp?: number;
|
|
70
|
+
condition?: string;
|
|
71
|
+
humidity?: number;
|
|
72
|
+
};
|
|
73
|
+
/** UV index */
|
|
74
|
+
uv?: number;
|
|
75
|
+
/** Pollen count */
|
|
76
|
+
pollen?: number;
|
|
77
|
+
/** Air quality index */
|
|
78
|
+
aqi?: number;
|
|
79
|
+
/** Location (city/region) */
|
|
80
|
+
location?: string;
|
|
81
|
+
/** Local hour (0-23) */
|
|
82
|
+
localHour?: number;
|
|
83
|
+
/** Is daylight? */
|
|
84
|
+
isDaylight?: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Biometric signals
|
|
89
|
+
*/
|
|
90
|
+
export interface BiometricContext {
|
|
91
|
+
/** Heart rate (BPM) */
|
|
92
|
+
heartRate?: number;
|
|
93
|
+
/** Heart rate variability (ms) */
|
|
94
|
+
hrv?: number;
|
|
95
|
+
/** Stress score (0-100) */
|
|
96
|
+
stressScore?: number;
|
|
97
|
+
/** Energy level (0-100) */
|
|
98
|
+
energyLevel?: number;
|
|
99
|
+
/** Sleep score (0-100) */
|
|
100
|
+
sleepScore?: number;
|
|
101
|
+
/** Readiness score (0-100) */
|
|
102
|
+
readinessScore?: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Full session context dropped via esi:context
|
|
107
|
+
*/
|
|
108
|
+
export interface SessionContext {
|
|
109
|
+
/** User identifier */
|
|
110
|
+
userId?: string;
|
|
111
|
+
/** User tier */
|
|
112
|
+
tier?: UserTier;
|
|
113
|
+
/** Emotional state (multi-source) */
|
|
114
|
+
emotion?: EmotionContext;
|
|
115
|
+
/** Behavioral signals */
|
|
116
|
+
behavior?: BehaviorContext;
|
|
117
|
+
/** Environmental context */
|
|
118
|
+
environment?: EnvironmentContext;
|
|
119
|
+
/** Biometric signals */
|
|
120
|
+
biometric?: BiometricContext;
|
|
121
|
+
/** Current route */
|
|
122
|
+
currentRoute?: string;
|
|
123
|
+
/** Session start timestamp */
|
|
124
|
+
sessionStartedAt?: number;
|
|
125
|
+
/** Custom metadata */
|
|
126
|
+
metadata?: Record<string, unknown>;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cyrano response intent types
|
|
131
|
+
*/
|
|
132
|
+
export type CyranoIntent =
|
|
133
|
+
| 'greeting' // Initial greeting
|
|
134
|
+
| 'proactive-check-in' // Unprompted check-in
|
|
135
|
+
| 'supportive-presence' // Gentle acknowledgment
|
|
136
|
+
| 'gentle-nudge' // Soft suggestion
|
|
137
|
+
| 'tool-suggestion' // Recommend a tool
|
|
138
|
+
| 'navigation-hint' // Suggest a route
|
|
139
|
+
| 'intervention' // Protective intervention
|
|
140
|
+
| 'celebration' // Celebrate progress
|
|
141
|
+
| 'reflection' // Prompt reflection
|
|
142
|
+
| 'guidance' // Offer guidance
|
|
143
|
+
| 'farewell' // Session ending
|
|
144
|
+
| 'custom'; // Custom intent
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Cyrano response tone
|
|
148
|
+
*/
|
|
149
|
+
export type CyranoTone =
|
|
150
|
+
| 'warm'
|
|
151
|
+
| 'calm'
|
|
152
|
+
| 'encouraging'
|
|
153
|
+
| 'playful'
|
|
154
|
+
| 'professional'
|
|
155
|
+
| 'empathetic'
|
|
156
|
+
| 'neutral';
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Trigger conditions for Cyrano response
|
|
160
|
+
*/
|
|
161
|
+
export type CyranoTrigger =
|
|
162
|
+
| `dwell:>${number}s` // Dwell time exceeded
|
|
163
|
+
| `scroll:>${number}` // Scroll depth exceeded
|
|
164
|
+
| `emotion:${string}` // Emotion detected
|
|
165
|
+
| `behavior:aimless` // Aimless clicking
|
|
166
|
+
| `behavior:hesitation` // Hesitation detected
|
|
167
|
+
| `hrv:<${number}` // HRV below threshold
|
|
168
|
+
| `stress:>${number}` // Stress above threshold
|
|
169
|
+
| `session:start` // Session started
|
|
170
|
+
| `session:idle:${number}m` // Idle for N minutes
|
|
171
|
+
| `navigation:to:${string}` // Navigated to route
|
|
172
|
+
| `tool:completed:${string}` // Tool completed
|
|
173
|
+
| `time:${string}` // Time-based trigger
|
|
174
|
+
| 'always' // Always trigger
|
|
175
|
+
| 'never'; // Never trigger (manual only)
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Cyrano whisper configuration
|
|
179
|
+
*/
|
|
180
|
+
export interface CyranoWhisperConfig {
|
|
181
|
+
/** Response intent */
|
|
182
|
+
intent: CyranoIntent;
|
|
183
|
+
/** Response tone */
|
|
184
|
+
tone?: CyranoTone;
|
|
185
|
+
/** Trigger condition */
|
|
186
|
+
trigger?: CyranoTrigger;
|
|
187
|
+
/** Fallback text if inference fails */
|
|
188
|
+
fallback?: string;
|
|
189
|
+
/** Suggested tool to surface */
|
|
190
|
+
suggestTool?: string;
|
|
191
|
+
/** Suggested route to navigate */
|
|
192
|
+
suggestRoute?: string;
|
|
193
|
+
/** Auto-accept navigation (for MCP) */
|
|
194
|
+
autoAcceptNavigation?: boolean;
|
|
195
|
+
/** Priority (higher = more important) */
|
|
196
|
+
priority?: number;
|
|
197
|
+
/** Maximum times to trigger per session */
|
|
198
|
+
maxTriggersPerSession?: number;
|
|
199
|
+
/** Cooldown between triggers (seconds) */
|
|
200
|
+
cooldownSeconds?: number;
|
|
201
|
+
/** Whether to speak via TTS */
|
|
202
|
+
speak?: boolean;
|
|
203
|
+
/** Whether to show as caption */
|
|
204
|
+
showCaption?: boolean;
|
|
205
|
+
/** Required user tier */
|
|
206
|
+
requiredTier?: UserTier;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Halo observation pattern types
|
|
211
|
+
*/
|
|
212
|
+
export type HaloObservation =
|
|
213
|
+
| 'anxiety-pattern'
|
|
214
|
+
| 'stress-accumulation'
|
|
215
|
+
| 'emotional-shift'
|
|
216
|
+
| 'behavioral-loop'
|
|
217
|
+
| 'decision-paralysis'
|
|
218
|
+
| 'growth-opportunity'
|
|
219
|
+
| 'values-misalignment'
|
|
220
|
+
| 'blind-spot'
|
|
221
|
+
| 'crisis-indicators'
|
|
222
|
+
| 'positive-momentum'
|
|
223
|
+
| 'synchronicity'
|
|
224
|
+
| 'temporal-echo'
|
|
225
|
+
| 'custom';
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Halo action types
|
|
229
|
+
*/
|
|
230
|
+
export type HaloAction =
|
|
231
|
+
| 'suggest-breathing'
|
|
232
|
+
| 'suggest-grounding'
|
|
233
|
+
| 'suggest-journaling'
|
|
234
|
+
| 'suggest-break'
|
|
235
|
+
| 'offer-tool'
|
|
236
|
+
| 'adjust-pace'
|
|
237
|
+
| 'reduce-complexity'
|
|
238
|
+
| 'increase-support'
|
|
239
|
+
| 'celebrate-progress'
|
|
240
|
+
| 'shield-intervention'
|
|
241
|
+
| 'crisis-protocol'
|
|
242
|
+
| 'whisper-to-cyrano'
|
|
243
|
+
| 'adapt-content'
|
|
244
|
+
| 'none';
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Halo meta-insight configuration
|
|
248
|
+
*/
|
|
249
|
+
export interface HaloInsightConfig {
|
|
250
|
+
/** Pattern to observe */
|
|
251
|
+
observe: HaloObservation;
|
|
252
|
+
/** Observation window (e.g., '3-pages', '5-minutes', 'session') */
|
|
253
|
+
window?: string;
|
|
254
|
+
/** Action to take when pattern detected */
|
|
255
|
+
action?: HaloAction;
|
|
256
|
+
/** Sensitivity threshold (0-1) */
|
|
257
|
+
sensitivity?: number;
|
|
258
|
+
/** Whether this is a crisis-level observation */
|
|
259
|
+
crisisLevel?: boolean;
|
|
260
|
+
/** Custom insight parameters */
|
|
261
|
+
parameters?: Record<string, unknown>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ============================================================================
|
|
265
|
+
// Chat Exhaust Types
|
|
266
|
+
// ============================================================================
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Chat exhaust types - everything becomes conversation
|
|
270
|
+
*/
|
|
271
|
+
export type ChatExhaustType =
|
|
272
|
+
| 'system' // Session start, env data
|
|
273
|
+
| 'esi:context' // Page drops context
|
|
274
|
+
| 'halo→cyrano' // Halo whispers to Cyrano
|
|
275
|
+
| 'cyrano→page' // Cyrano whispers to page
|
|
276
|
+
| 'behavior' // User action
|
|
277
|
+
| 'emotion' // Emotion shift detected
|
|
278
|
+
| 'user' // Explicit user message
|
|
279
|
+
| 'cyrano' // Cyrano response
|
|
280
|
+
| 'tool:invoke' // Tool invocation
|
|
281
|
+
| 'tool:complete' // Tool completion
|
|
282
|
+
| 'navigation'; // Route change
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Individual chat exhaust entry
|
|
286
|
+
*/
|
|
287
|
+
export interface ChatExhaustEntry {
|
|
288
|
+
/** Entry type */
|
|
289
|
+
type: ChatExhaustType;
|
|
290
|
+
/** Timestamp */
|
|
291
|
+
timestamp: number;
|
|
292
|
+
/** Entry content (varies by type) */
|
|
293
|
+
content: unknown;
|
|
294
|
+
/** Whether this was visible to user */
|
|
295
|
+
visible?: boolean;
|
|
296
|
+
/** Source of the entry */
|
|
297
|
+
source?: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* ESI result extended with whisper channel data
|
|
302
|
+
*/
|
|
303
|
+
export interface ESIWhisperResult extends ESIResult {
|
|
304
|
+
/** Chat exhaust generated by this directive */
|
|
305
|
+
exhaust?: ChatExhaustEntry[];
|
|
306
|
+
/** Suggested tool from Cyrano */
|
|
307
|
+
suggestedTool?: string;
|
|
308
|
+
/** Suggested route from Cyrano */
|
|
309
|
+
suggestedRoute?: string;
|
|
310
|
+
/** Whether to auto-accept navigation */
|
|
311
|
+
autoAcceptNavigation?: boolean;
|
|
312
|
+
/** Halo insights triggered */
|
|
313
|
+
haloInsights?: HaloInsightConfig[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// ESI Context Directive (Page → Cyrano)
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Create an ESI directive to drop session context into the whisper stream.
|
|
322
|
+
* This is how pages communicate their state to Cyrano.
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```tsx
|
|
326
|
+
* <esiContext
|
|
327
|
+
* emotion={{ primary: 'anxious', valence: -0.3, arousal: 0.7 }}
|
|
328
|
+
* behavior={{ scrollDepth: 0.8, dwellTime: 45000 }}
|
|
329
|
+
* environment={{ weather: { temp: 72, condition: 'sunny' }, uv: 6 }}
|
|
330
|
+
* />
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
export function esiContext(
|
|
334
|
+
context: SessionContext,
|
|
335
|
+
options: {
|
|
336
|
+
/** Whether to emit as chat exhaust */
|
|
337
|
+
emitExhaust?: boolean;
|
|
338
|
+
/** Custom directive ID */
|
|
339
|
+
id?: string;
|
|
340
|
+
} = {},
|
|
341
|
+
): ESIDirective {
|
|
342
|
+
const { emitExhaust = true, id } = options;
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
id:
|
|
346
|
+
id ||
|
|
347
|
+
`esi-context-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
348
|
+
params: {
|
|
349
|
+
model: 'custom',
|
|
350
|
+
custom: {
|
|
351
|
+
type: 'context-drop',
|
|
352
|
+
emitExhaust,
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
content: {
|
|
356
|
+
type: 'json',
|
|
357
|
+
value: JSON.stringify(context),
|
|
358
|
+
},
|
|
359
|
+
contextAware: true,
|
|
360
|
+
signals: ['emotion', 'preferences', 'history', 'time', 'device'],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ============================================================================
|
|
365
|
+
// ESI Cyrano Directive (Cyrano → Page)
|
|
366
|
+
// ============================================================================
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Create an ESI directive for Cyrano to whisper to the page.
|
|
370
|
+
* Cyrano responds based on intent, tone, and trigger conditions.
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```tsx
|
|
374
|
+
* <esiCyrano
|
|
375
|
+
* intent="proactive-check-in"
|
|
376
|
+
* trigger="dwell:>60s"
|
|
377
|
+
* tone="warm"
|
|
378
|
+
* fallback="I'm here if you need me"
|
|
379
|
+
* />
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
export function esiCyrano(
|
|
383
|
+
config: CyranoWhisperConfig,
|
|
384
|
+
options: Partial<ESIParams> = {},
|
|
385
|
+
): ESIDirective {
|
|
386
|
+
const {
|
|
387
|
+
intent,
|
|
388
|
+
tone = 'warm',
|
|
389
|
+
trigger = 'always',
|
|
390
|
+
fallback,
|
|
391
|
+
suggestTool,
|
|
392
|
+
suggestRoute,
|
|
393
|
+
autoAcceptNavigation = false,
|
|
394
|
+
priority = 1,
|
|
395
|
+
maxTriggersPerSession,
|
|
396
|
+
cooldownSeconds,
|
|
397
|
+
speak = false,
|
|
398
|
+
showCaption = true,
|
|
399
|
+
requiredTier,
|
|
400
|
+
} = config;
|
|
401
|
+
|
|
402
|
+
// Build system prompt based on intent and tone
|
|
403
|
+
const systemPrompt = buildCyranoSystemPrompt(intent, tone);
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
id: `esi-cyrano-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
407
|
+
params: {
|
|
408
|
+
model: 'llm',
|
|
409
|
+
system: systemPrompt,
|
|
410
|
+
temperature: 0.7,
|
|
411
|
+
maxTokens: 150,
|
|
412
|
+
fallback,
|
|
413
|
+
custom: {
|
|
414
|
+
type: 'cyrano-whisper',
|
|
415
|
+
intent,
|
|
416
|
+
tone,
|
|
417
|
+
trigger,
|
|
418
|
+
suggestTool,
|
|
419
|
+
suggestRoute,
|
|
420
|
+
autoAcceptNavigation,
|
|
421
|
+
priority,
|
|
422
|
+
maxTriggersPerSession,
|
|
423
|
+
cooldownSeconds,
|
|
424
|
+
speak,
|
|
425
|
+
showCaption,
|
|
426
|
+
},
|
|
427
|
+
...options,
|
|
428
|
+
},
|
|
429
|
+
content: {
|
|
430
|
+
type: 'template',
|
|
431
|
+
value: buildCyranoPrompt(intent, trigger),
|
|
432
|
+
variables: {
|
|
433
|
+
intent,
|
|
434
|
+
tone,
|
|
435
|
+
trigger,
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
contextAware: true,
|
|
439
|
+
signals: ['emotion', 'preferences', 'history', 'time'],
|
|
440
|
+
requiredTier,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Build system prompt for Cyrano based on intent and tone
|
|
446
|
+
*/
|
|
447
|
+
function buildCyranoSystemPrompt(
|
|
448
|
+
intent: CyranoIntent,
|
|
449
|
+
tone: CyranoTone,
|
|
450
|
+
): string {
|
|
451
|
+
const toneGuide: Record<CyranoTone, string> = {
|
|
452
|
+
warm: 'Be warm, caring, and approachable. Use gentle language.',
|
|
453
|
+
calm: 'Be calm, measured, and reassuring. Use a steady pace.',
|
|
454
|
+
encouraging: 'Be supportive and uplifting. Celebrate small wins.',
|
|
455
|
+
playful: 'Be light-hearted and fun. Use appropriate humor.',
|
|
456
|
+
professional: 'Be clear and direct. Maintain professionalism.',
|
|
457
|
+
empathetic: 'Show deep understanding. Validate feelings.',
|
|
458
|
+
neutral: 'Be balanced and objective. Provide information.',
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const intentGuide: Record<CyranoIntent, string> = {
|
|
462
|
+
greeting: 'Welcome the user. Make them feel at home.',
|
|
463
|
+
'proactive-check-in': 'Check in gently. Ask how they are doing.',
|
|
464
|
+
'supportive-presence': 'Simply acknowledge. Let them know you are here.',
|
|
465
|
+
'gentle-nudge': 'Suggest an action softly. No pressure.',
|
|
466
|
+
'tool-suggestion': 'Recommend a tool that might help.',
|
|
467
|
+
'navigation-hint': 'Suggest exploring another area.',
|
|
468
|
+
intervention: 'Step in supportively. Offer help.',
|
|
469
|
+
celebration: 'Celebrate their progress. Be genuinely happy for them.',
|
|
470
|
+
reflection: 'Invite them to reflect. Ask thoughtful questions.',
|
|
471
|
+
guidance: 'Offer helpful guidance. Be a trusted advisor.',
|
|
472
|
+
farewell: 'Wish them well. Leave the door open.',
|
|
473
|
+
custom: 'Respond appropriately to the context.',
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
return `You are Cyrano, an ambient AI companion. ${toneGuide[tone]} ${intentGuide[intent]}
|
|
477
|
+
|
|
478
|
+
Keep responses brief (1-2 sentences). Be natural and conversational.
|
|
479
|
+
Never start with "I" - use "You" or the situation as the subject.
|
|
480
|
+
Never say "As an AI" or similar phrases.
|
|
481
|
+
Respond to the emotional context provided.`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Build prompt for Cyrano based on intent and trigger
|
|
486
|
+
*/
|
|
487
|
+
function buildCyranoPrompt(
|
|
488
|
+
intent: CyranoIntent,
|
|
489
|
+
trigger: CyranoTrigger,
|
|
490
|
+
): string {
|
|
491
|
+
const prompts: Record<CyranoIntent, string> = {
|
|
492
|
+
greeting:
|
|
493
|
+
'Generate a warm greeting based on the time of day and user context.',
|
|
494
|
+
'proactive-check-in':
|
|
495
|
+
'Check in with the user based on their emotional state and behavior.',
|
|
496
|
+
'supportive-presence':
|
|
497
|
+
"Acknowledge the user's presence and current activity.",
|
|
498
|
+
'gentle-nudge':
|
|
499
|
+
'Gently suggest the user might benefit from a particular action.',
|
|
500
|
+
'tool-suggestion':
|
|
501
|
+
"Suggest a specific tool that could help with the user's current state.",
|
|
502
|
+
'navigation-hint':
|
|
503
|
+
'Suggest the user might want to explore a different area.',
|
|
504
|
+
intervention:
|
|
505
|
+
'Offer supportive intervention based on detected stress or difficulty.',
|
|
506
|
+
celebration: "Celebrate the user's progress or achievement.",
|
|
507
|
+
reflection: 'Invite the user to reflect on their current experience.',
|
|
508
|
+
guidance: "Offer helpful guidance for the user's current situation.",
|
|
509
|
+
farewell: 'Say goodbye warmly, acknowledging the session.',
|
|
510
|
+
custom: 'Respond appropriately to the context provided.',
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
let prompt = prompts[intent] || prompts.custom;
|
|
514
|
+
|
|
515
|
+
// Add trigger context
|
|
516
|
+
if (trigger !== 'always' && trigger !== 'never') {
|
|
517
|
+
prompt += ` The trigger condition is: ${trigger}.`;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return prompt;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ============================================================================
|
|
524
|
+
// ESI Halo Directive (Halo Meta-Insight)
|
|
525
|
+
// ============================================================================
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Create an ESI directive for Halo meta-insight observation.
|
|
529
|
+
* Halo observes patterns across pages and whispers to Cyrano.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```tsx
|
|
533
|
+
* <esiHalo
|
|
534
|
+
* observe="anxiety-pattern"
|
|
535
|
+
* window="3-pages"
|
|
536
|
+
* action="suggest-breathing"
|
|
537
|
+
* />
|
|
538
|
+
* ```
|
|
539
|
+
*/
|
|
540
|
+
export function esiHalo(
|
|
541
|
+
config: HaloInsightConfig,
|
|
542
|
+
options: Partial<ESIParams> = {},
|
|
543
|
+
): ESIDirective {
|
|
544
|
+
const {
|
|
545
|
+
observe,
|
|
546
|
+
window = 'session',
|
|
547
|
+
action = 'whisper-to-cyrano',
|
|
548
|
+
sensitivity = 0.5,
|
|
549
|
+
crisisLevel = false,
|
|
550
|
+
parameters = {},
|
|
551
|
+
} = config;
|
|
552
|
+
|
|
553
|
+
return {
|
|
554
|
+
id: `esi-halo-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
555
|
+
params: {
|
|
556
|
+
model: 'custom',
|
|
557
|
+
custom: {
|
|
558
|
+
type: 'halo-insight',
|
|
559
|
+
observe,
|
|
560
|
+
window,
|
|
561
|
+
action,
|
|
562
|
+
sensitivity,
|
|
563
|
+
crisisLevel,
|
|
564
|
+
parameters,
|
|
565
|
+
},
|
|
566
|
+
...options,
|
|
567
|
+
},
|
|
568
|
+
content: {
|
|
569
|
+
type: 'json',
|
|
570
|
+
value: JSON.stringify({
|
|
571
|
+
observation: observe,
|
|
572
|
+
window,
|
|
573
|
+
action,
|
|
574
|
+
sensitivity,
|
|
575
|
+
crisisLevel,
|
|
576
|
+
}),
|
|
577
|
+
},
|
|
578
|
+
contextAware: true,
|
|
579
|
+
signals: ['emotion', 'history', 'time'],
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ============================================================================
|
|
584
|
+
// Whisper Channel Processing
|
|
585
|
+
// ============================================================================
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Check if a Cyrano trigger condition is met
|
|
589
|
+
*/
|
|
590
|
+
export function evaluateTrigger(
|
|
591
|
+
trigger: CyranoTrigger,
|
|
592
|
+
context: UserContext,
|
|
593
|
+
sessionContext?: SessionContext,
|
|
594
|
+
): boolean {
|
|
595
|
+
if (trigger === 'always') return true;
|
|
596
|
+
if (trigger === 'never') return false;
|
|
597
|
+
|
|
598
|
+
// Parse trigger string
|
|
599
|
+
const [type, condition] = trigger.split(':');
|
|
600
|
+
|
|
601
|
+
switch (type) {
|
|
602
|
+
case 'dwell': {
|
|
603
|
+
// dwell:>60s
|
|
604
|
+
const match = condition?.match(/>(\d+)s/);
|
|
605
|
+
if (!match) return false;
|
|
606
|
+
const threshold = parseInt(match[1], 10) * 1000;
|
|
607
|
+
const dwellTime = sessionContext?.behavior?.dwellTime || 0;
|
|
608
|
+
return dwellTime > threshold;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
case 'scroll': {
|
|
612
|
+
// scroll:>0.8
|
|
613
|
+
const match = condition?.match(/>(\d+\.?\d*)/);
|
|
614
|
+
if (!match) return false;
|
|
615
|
+
const threshold = parseFloat(match[1]);
|
|
616
|
+
const scrollDepth = sessionContext?.behavior?.scrollDepth || 0;
|
|
617
|
+
return scrollDepth > threshold;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
case 'emotion': {
|
|
621
|
+
// emotion:anxious
|
|
622
|
+
const targetEmotion = condition;
|
|
623
|
+
return (
|
|
624
|
+
sessionContext?.emotion?.primary === targetEmotion ||
|
|
625
|
+
context.emotionState?.primary === targetEmotion
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
case 'behavior': {
|
|
630
|
+
// behavior:aimless, behavior:hesitation
|
|
631
|
+
if (condition === 'aimless') {
|
|
632
|
+
return sessionContext?.behavior?.isAimlessClicking === true;
|
|
633
|
+
}
|
|
634
|
+
if (condition === 'hesitation') {
|
|
635
|
+
return sessionContext?.behavior?.hesitationDetected === true;
|
|
636
|
+
}
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
case 'hrv': {
|
|
641
|
+
// hrv:<40
|
|
642
|
+
const match = condition?.match(/<(\d+)/);
|
|
643
|
+
if (!match) return false;
|
|
644
|
+
const threshold = parseInt(match[1], 10);
|
|
645
|
+
const hrv = sessionContext?.biometric?.hrv || 100;
|
|
646
|
+
return hrv < threshold;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
case 'stress': {
|
|
650
|
+
// stress:>70
|
|
651
|
+
const match = condition?.match(/>(\d+)/);
|
|
652
|
+
if (!match) return false;
|
|
653
|
+
const threshold = parseInt(match[1], 10);
|
|
654
|
+
const stress = sessionContext?.biometric?.stressScore || 0;
|
|
655
|
+
return stress > threshold;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
case 'session': {
|
|
659
|
+
// session:start, session:idle:5m
|
|
660
|
+
if (condition === 'start') {
|
|
661
|
+
return context.isNewSession;
|
|
662
|
+
}
|
|
663
|
+
const idleMatch = condition?.match(/idle:(\d+)m/);
|
|
664
|
+
if (idleMatch) {
|
|
665
|
+
// Would need last activity timestamp to evaluate
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
case 'navigation': {
|
|
672
|
+
// navigation:to:/breathing
|
|
673
|
+
const targetRoute = condition?.replace('to:', '');
|
|
674
|
+
return sessionContext?.currentRoute === targetRoute;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
case 'time': {
|
|
678
|
+
// time:morning (6-11), time:evening (18-21)
|
|
679
|
+
const hour = context.localHour;
|
|
680
|
+
if (condition === 'morning') return hour >= 6 && hour < 12;
|
|
681
|
+
if (condition === 'afternoon') return hour >= 12 && hour < 18;
|
|
682
|
+
if (condition === 'evening') return hour >= 18 && hour < 22;
|
|
683
|
+
if (condition === 'night') return hour >= 22 || hour < 6;
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
default:
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Generate chat exhaust entry from directive result
|
|
694
|
+
*/
|
|
695
|
+
export function createExhaustEntry(
|
|
696
|
+
directive: ESIDirective,
|
|
697
|
+
result: ESIResult,
|
|
698
|
+
type: ChatExhaustType,
|
|
699
|
+
): ChatExhaustEntry {
|
|
700
|
+
return {
|
|
701
|
+
type,
|
|
702
|
+
timestamp: Date.now(),
|
|
703
|
+
content: {
|
|
704
|
+
directiveId: directive.id,
|
|
705
|
+
output: result.output,
|
|
706
|
+
model: result.model,
|
|
707
|
+
success: result.success,
|
|
708
|
+
latencyMs: result.latencyMs,
|
|
709
|
+
},
|
|
710
|
+
visible: type === 'cyrano' || type === 'user',
|
|
711
|
+
source: directive.params.model,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// ============================================================================
|
|
716
|
+
// Tool Suggestion Helpers
|
|
717
|
+
// ============================================================================
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Common tool suggestions based on context
|
|
721
|
+
*/
|
|
722
|
+
export const CYRANO_TOOL_SUGGESTIONS: Record<
|
|
723
|
+
string,
|
|
724
|
+
{
|
|
725
|
+
triggers: CyranoTrigger[];
|
|
726
|
+
tool: string;
|
|
727
|
+
reason: string;
|
|
728
|
+
}
|
|
729
|
+
> = {
|
|
730
|
+
breathing: {
|
|
731
|
+
triggers: ['stress:>70', 'hrv:<40', 'emotion:anxious'],
|
|
732
|
+
tool: 'breathing/4-7-8',
|
|
733
|
+
reason: 'You seem stressed - a breathing exercise might help',
|
|
734
|
+
},
|
|
735
|
+
grounding: {
|
|
736
|
+
triggers: ['emotion:overwhelmed', 'behavior:aimless'],
|
|
737
|
+
tool: 'grounding/5-4-3-2-1',
|
|
738
|
+
reason: 'A grounding exercise can help center you',
|
|
739
|
+
},
|
|
740
|
+
journaling: {
|
|
741
|
+
triggers: ['dwell:>120s', 'emotion:reflective'],
|
|
742
|
+
tool: 'journaling/freeform',
|
|
743
|
+
reason: "Would you like to write about what's on your mind?",
|
|
744
|
+
},
|
|
745
|
+
insights: {
|
|
746
|
+
triggers: ['navigation:to:/insights', 'dwell:>60s'],
|
|
747
|
+
tool: 'insights/dashboard',
|
|
748
|
+
reason: 'Your recent patterns are ready to explore',
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Get tool suggestions based on session context
|
|
754
|
+
*/
|
|
755
|
+
export function getToolSuggestions(
|
|
756
|
+
context: UserContext,
|
|
757
|
+
sessionContext?: SessionContext,
|
|
758
|
+
): Array<{ tool: string; reason: string; priority: number }> {
|
|
759
|
+
const suggestions: Array<{ tool: string; reason: string; priority: number }> =
|
|
760
|
+
[];
|
|
761
|
+
|
|
762
|
+
for (const [, config] of Object.entries(CYRANO_TOOL_SUGGESTIONS)) {
|
|
763
|
+
for (const trigger of config.triggers) {
|
|
764
|
+
if (evaluateTrigger(trigger, context, sessionContext)) {
|
|
765
|
+
suggestions.push({
|
|
766
|
+
tool: config.tool,
|
|
767
|
+
reason: config.reason,
|
|
768
|
+
priority:
|
|
769
|
+
trigger.startsWith('stress') || trigger.startsWith('hrv') ? 2 : 1,
|
|
770
|
+
});
|
|
771
|
+
break; // One suggestion per tool
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return suggestions.sort((a, b) => b.priority - a.priority);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// All functions are exported at their definition site
|