Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
1fb267f
Optimizations
Meciah Oct 23, 2025
6cb7a9c
Optimizations
Meciah Oct 23, 2025
77ef465
Optimizations
Meciah Oct 25, 2025
4aeb054
Optimizations
Meciah Oct 25, 2025
10afe85
Before UI update
Meciah Oct 29, 2025
db06f18
UI updates WIP
Meciah Oct 30, 2025
13584cd
UI updates WIP
Meciah Nov 1, 2025
4e679ec
Fix vault ID generation helper
techno-optimist Nov 1, 2025
062a2aa
Vault fix
Meciah Nov 6, 2025
6cd8157
Merge branch 'optimization' into codex/enhance-emma-with-openai-agent…
Meciah Nov 8, 2025
e67ba82
Merge pull request #6 from techno-optimist/codex/enhance-emma-with-op…
Meciah Nov 8, 2025
c5d959b
Vault fix
Meciah Nov 8, 2025
3aaa600
Vault fix
Meciah Nov 8, 2025
78be834
Vault fix
Meciah Nov 8, 2025
cf93568
Vault fix
Meciah Nov 8, 2025
829b9b6
Vault fix
Meciah Nov 9, 2025
33bc4ab
Vault fix
Meciah Nov 9, 2025
cddc125
Vault fix
Meciah Nov 9, 2025
7b7e91f
Vault fix
Meciah Nov 9, 2025
04a82b2
Vault fix
Meciah Nov 9, 2025
93df1fa
Vault fix
Meciah Nov 9, 2025
2888ed5
Update edit mem modal, person modal, edit person modal
Meciah Nov 11, 2025
1ce5808
I instantiated the analyzer in the `EmmaChatExperience` constructor a…
google-labs-jules[bot] Nov 19, 2025
5e9b4d7
I instantiated the analyzer in the `EmmaChatExperience` constructor a…
google-labs-jules[bot] Nov 20, 2025
6943151
I instantiated the analyzer in the `EmmaChatExperience` constructor a…
google-labs-jules[bot] Nov 20, 2025
781fc1b
I instantiated the analyzer in the `EmmaChatExperience` constructor a…
google-labs-jules[bot] Nov 20, 2025
10a5bf1
I instantiated the analyzer in the `EmmaChatExperience` constructor a…
google-labs-jules[bot] Nov 20, 2025
7dc0d50
feat: Complete audit and architectural overhaul of Emma Intelligence
google-labs-jules[bot] Nov 20, 2025
24a2ef4
Merge pull request #7 from techno-optimist/emmaintelligenceupdate
techno-optimist Nov 20, 2025
be25f56
fix: restore missing intelligence files to fix chat UI
claude Nov 20, 2025
912e662
fix: add UTF-8 BOM to intelligence files & initialize vault in chat
claude Nov 20, 2025
76f095e
fix: Sherlock Protocol - resolve critical error cascade
claude Nov 20, 2025
6c3545a
fix: resolve TypeError crashes and add WebSocket debugging
claude Nov 20, 2025
040029d
fix: add forced debug logging to diagnose WebSocket URL issue
claude Nov 20, 2025
60d4774
fix: WebSocket always uses current page origin on Render
claude Nov 20, 2025
8385c07
fix: replace null options with {} in addMessage calls to prevent Type…
claude Nov 20, 2025
5e41541
fix: dynamic CSP and CORS for Render deployment
claude Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor/scratchpad.md
Original file line number Diff line number Diff line change
Expand Up @@ -1843,7 +1843,7 @@ class EmmaQuestionEngine {

#### **EMMA BRANDING PRINCIPLES** 📐
1. **Color Palette**:
- Primary: `--emma-gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)`
- Primary: `--emma-gradient-1: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #deb3e4 100%)`
- Background: `--emma-bg-gradient: linear-gradient(135deg, #1a1033 0%, #2d1b69 50%, #0f0c29 100%)`
- Cards: `rgba(255, 255, 255, 0.05)` with `backdrop-filter: blur(20px)`

Expand Down
13 changes: 0 additions & 13 deletions .force-cache-refresh-v2.txt

This file was deleted.

7 changes: 0 additions & 7 deletions .force-rebuild-2025-08-22T16-00-13Z.txt

This file was deleted.

Binary file removed .render-deploy-trigger
Binary file not shown.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"git.ignoreLimitWarning": true
"git.ignoreLimitWarning": true,
"chatgpt.openOnStartup": true
}
2 changes: 1 addition & 1 deletion _headers
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: DENY
Permissions-Policy: microphone=(self), geolocation=(), camera=()
Content-Security-Policy-Report-Only: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://*.onrender.com; connect-src 'self' https://*.onrender.com
Content-Security-Policy-Report-Only: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://unpkg.com https://*.onrender.com; connect-src 'self' https://*.onrender.com
47 changes: 35 additions & 12 deletions add-person.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add Person - Emma</title>
<link rel="stylesheet" href="css/theme-tokens.css">
<link rel="stylesheet" href="css/main.css">
<script src="themes/theme-catalog.js"></script>
<script src="js/theme-manager.js"></script>
<script nonce="c3RhdGljLWlubGluZQ==" src="js/sanitize.js"></script>
<style>
.add-person-container {
max-width: 600px;
Expand Down Expand Up @@ -149,7 +153,7 @@ <h1>Add New Person</h1>
</div>

<div class="success-message" id="successMessage">
Person added successfully! <a href="people.html">View all people</a>
Person added successfully! <a href="pages/people-emma.html">View all people</a>
</div>

<form id="addPersonForm" onsubmit="addPerson(event)">
Expand Down Expand Up @@ -194,10 +198,23 @@ <h1>Add New Person</h1>
</div>
</div>

<script type="module">
<script nonce="c3RhdGljLWlubGluZQ==" type="module">
// Import identity crypto functions
import { generateIdentity, generateIdentityCard } from './js/vault/identity-crypto.js';

const sanitize = (value) => {
const fallback = (input) => String(input ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
if (typeof window !== 'undefined' && typeof window.escapeHtml === 'function') {
return window.escapeHtml(value ?? '');
}
return fallback(value);
};

// Add person functionality
async function addPerson(event) {
event.preventDefault();
Expand Down Expand Up @@ -253,17 +270,23 @@ <h1>Add New Person</h1>

// Show success message with identity info
const successMessage = document.getElementById('successMessage');
successMessage.innerHTML = `
<p><strong>✅ ${newPerson.name} added successfully!</strong></p>
${newPerson.relationship === 'self' ? `
<div style="margin-top: 10px; padding: 10px; background: rgba(134, 88, 255, 0.1); border-radius: 8px;">
<p style="margin: 0 0 5px 0;"><strong>🔐 Cryptographic Identity Created</strong></p>
<p style="margin: 0; font-size: 12px;">Fingerprint: <code>${identity.fingerprint.substring(0, 20)}...</code></p>
<button onclick="window.open('people.html', '_self')" style="margin-top: 10px; padding: 5px 10px; background: #8658ff; color: white; border: none; border-radius: 4px; cursor: pointer;">
const safeName = sanitize(newPerson.name);
const rawFingerprint = identity?.fingerprint || '';
const truncatedFingerprint = rawFingerprint ? `${sanitize(rawFingerprint.substring(0, 20))}${rawFingerprint.length > 20 ? '...' : ''}` : 'N/A';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Fingerprint truncation logic error with sanitization

The fingerprint truncation logic checks rawFingerprint.length > 20 after calling substring(0, 20), but then sanitizes the substring. This creates a mismatch: the ellipsis condition checks the original length, but the sanitized substring might have a different length due to HTML entity encoding. If the first 20 characters contain special characters like &, <, or >, the sanitized version will be longer (e.g., & becomes &amp;), making the ellipsis logic incorrect. The length check should happen on the sanitized substring, not the raw fingerprint.

Fix in Cursor Fix in Web

const identityDetails = newPerson.relationship === 'self'
? `
<div style="margin-top: 10px; padding: 10px; background: rgba(111, 99, 217, 0.1); border-radius: 8px;">
<p style="margin: 0 0 5px 0;"><strong>Cryptographic Identity Created</strong></p>
<p style="margin: 0; font-size: 12px;">Fingerprint: <code>${truncatedFingerprint}</code></p>
<button onclick="window.open('pages/people-emma.html', '_self')" style="margin-top: 10px; padding: 5px 10px; background: #6f63d9; color: white; border: none; border-radius: 4px; cursor: pointer;">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Inline onclick handler bypasses CSP nonce protection

The dynamically generated button uses an inline onclick attribute with window.open(), which violates the Content Security Policy that requires nonces for inline scripts. The page has a CSP nonce (c3RhdGljLWlubGluZQ==) for its module script, but inline event handlers in dynamically generated HTML bypass this protection. This creates an inconsistency where the page enforces CSP for static scripts but allows unsafe inline handlers in dynamic content, potentially opening a vector for XSS attacks if the identityDetails variable were ever compromised.

Fix in Cursor Fix in Web

View in People List
</button>
</div>
` : ''}
`
: '';
successMessage.innerHTML = `
<p><strong>${safeName} added successfully!</strong></p>
${identityDetails}
`;
successMessage.style.display = 'block';
document.getElementById('addPersonForm').reset();
Expand Down Expand Up @@ -324,7 +347,7 @@ <h1>Add New Person</h1>
if (window.history.length > 1) {
window.history.back();
} else {
window.location.href = 'people.html';
window.location.href = 'pages/people-emma.html';
}
}

Expand All @@ -334,4 +357,4 @@ <h1>Add New Person</h1>
});
</script>
</body>
</html>
</html>
110 changes: 110 additions & 0 deletions apps/emma-openai-app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
'use strict';

const VaultService = require('../lib/vault-service');
const { TOOL_DEFINITIONS } = require('../lib/tool-definitions');

const APP_NAME = 'Emma Memory Companion';
const APP_DESCRIPTION = 'A gentle memory companion that can recall, capture, and enrich family stories with full vault access.';
const APP_DEFAULT_MODEL = 'gpt-4.1-mini';
const APP_INSTRUCTIONS = [
"You are Emma, a gentle memory companion designed to support families navigating dementia.",
"Introduce yourself as Emma and explain that you protect the family's private memory vault.",
"Use validation therapy techniques: acknowledge feelings, reflect emotions, and avoid correcting memories.",
"When helpful, call the available vault tools to look up people, recall memories, or save new stories with photos.",
"Be transparent about any tool calls and summarize the outcome for the user.",
"Keep every interaction private and never fabricate vault data."
].join(' ');

let appsSdk = null;
try {
// Lazy load the Apps SDK if it is installed in the host environment.
appsSdk = require('@openai/applications');
} catch (error) {
appsSdk = null;
}

function cloneParameters(parameters) {
return parameters ? JSON.parse(JSON.stringify(parameters)) : undefined;
}

function buildToolExecutor(vaultService, schema) {
return async (args = {}) => {
return vaultService.execute(schema.name, args);
};
}

function buildConfig(vaultService, options = {}) {
const model = options.model || APP_DEFAULT_MODEL;

return {
name: APP_NAME,
description: APP_DESCRIPTION,
instructions: options.instructions || APP_INSTRUCTIONS,
model,
tools: TOOL_DEFINITIONS.map((schema) => ({
name: schema.name,
description: schema.description,
parameters: cloneParameters(schema.parameters),
execute: buildToolExecutor(vaultService, schema)
}))
};
}

function createEmmaApp(options = {}) {
const vaultService = options.vaultService || new VaultService(options.vaultOptions || {});
const config = buildConfig(vaultService, options);

if (!appsSdk) {
return config;
}

if (typeof appsSdk.createApp === 'function') {
return appsSdk.createApp(config);
}

if (appsSdk.App && typeof appsSdk.tool === 'function') {
const { App, tool } = appsSdk;
return new App({
name: config.name,
description: config.description,
model: config.model,
instructions: config.instructions,
tools: config.tools.map((toolSchema) =>
tool({
name: toolSchema.name,
description: toolSchema.description,
parameters: toolSchema.parameters,
execute: toolSchema.execute
})
)
});
}

return config;
}

function getEmmaAppManifest(options = {}) {
const vaultService = options.vaultService || new VaultService(options.vaultOptions || {});
const config = buildConfig(vaultService, options);
return {
name: config.name,
description: config.description,
model: config.model,
instructions: config.instructions,
tools: config.tools.map(({ name, description, parameters }) => ({
type: 'function',
name,
description,
parameters
}))
};
}

module.exports = {
createEmmaApp,
getEmmaAppManifest,
APP_INSTRUCTIONS,
APP_NAME,
APP_DEFAULT_MODEL,
APP_DESCRIPTION
};
72 changes: 72 additions & 0 deletions chat.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Emma - Chat</title>

<!-- Emma Vault System -->
<script src="js/emma-env.js"></script>
<script src="js/emma-logger.js"></script>
<script src="js/emma-input-modal.js"></script>
<script src="js/clean-password-modal.js"></script>
<script src="js/modal-helpers.js"></script>
<script src="js/emma-feature-flags.js"></script>
<script src="js/emma-vault-primary.js"></script>
<script src="js/emma-web-vault.js"></script>
<script src="js/web-vault-status.js"></script>

<link rel="stylesheet" href="css/emma-chat-clean.css">
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.chat-container {
width: 100%;
max-width: 600px;
height: 80vh;
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-messages" id="chat-messages">
<div id="welcome-message">
<h2>Welcome to Emma Chat</h2>
<p>Start a conversation below.</p>
</div>
</div>
<div id="typing-indicator" class="typing-indicator">
<span></span><span></span><span></span>
</div>
<div class="chat-input-area">
<textarea id="chat-input" placeholder="Talk to Emma..." onkeydown="emmaWsChat.handleKeyPress(event)"></textarea>
<button onclick="emmaWsChat.sendMessage()">Send</button>
</div>
</div>

<!-- Initialize Emma Web Vault -->
<script>
// Ensure Emma Web Vault is initialized before ws-chat.js uses it
(async function initializeVault() {
if (typeof EmmaWebVault === 'undefined') {
console.error('EmmaWebVault class not loaded!');
return;
}

if (!window.emmaWebVault) {
window.emmaWebVault = new EmmaWebVault();
console.log('✓ EmmaWebVault initialized for chat interface');
}
})();
</script>

<script src="js/ws-chat.js"></script>
</body>
</html>
Loading