get-claudia 1.51.4 → 1.51.6
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/cli/commands/google-auth.js +222 -0
- package/cli/core/database.js +2 -2
- package/cli/index.js +18 -1
- package/memory-daemon/claudia_memory/schema.sql +477 -0
- package/package.json +3 -2
- package/template-v2/CLAUDE.md +1 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* claudia gmail status - Check Gmail connection status
|
|
10
10
|
* claudia gmail search - Search emails
|
|
11
11
|
* claudia gmail read - Read a specific email
|
|
12
|
+
* claudia gmail send - Send an email with optional attachments
|
|
12
13
|
* claudia gmail logout - Sign out of Gmail
|
|
13
14
|
* claudia calendar login - Sign in with Google (Calendar only)
|
|
14
15
|
* claudia calendar status - Check Calendar connection status
|
|
@@ -18,9 +19,146 @@
|
|
|
18
19
|
* claudia calendar logout - Sign out of Calendar
|
|
19
20
|
*/
|
|
20
21
|
|
|
22
|
+
import { readFileSync, existsSync, statSync } from 'node:fs';
|
|
23
|
+
import { basename, extname } from 'node:path';
|
|
24
|
+
import { randomBytes } from 'node:crypto';
|
|
21
25
|
import { authenticate, getAccessToken, isAuthenticated, revokeTokens, authStatus } from '../core/google-oauth.js';
|
|
22
26
|
import { outputJson as output } from '../core/output.js';
|
|
23
27
|
|
|
28
|
+
// ── MIME Helpers (for gmail send) ──
|
|
29
|
+
|
|
30
|
+
const MIME_TYPES = {
|
|
31
|
+
png: 'image/png',
|
|
32
|
+
jpg: 'image/jpeg',
|
|
33
|
+
jpeg: 'image/jpeg',
|
|
34
|
+
gif: 'image/gif',
|
|
35
|
+
webp: 'image/webp',
|
|
36
|
+
svg: 'image/svg+xml',
|
|
37
|
+
pdf: 'application/pdf',
|
|
38
|
+
txt: 'text/plain',
|
|
39
|
+
csv: 'text/csv',
|
|
40
|
+
json: 'application/json',
|
|
41
|
+
xml: 'application/xml',
|
|
42
|
+
html: 'text/html',
|
|
43
|
+
zip: 'application/zip',
|
|
44
|
+
gz: 'application/gzip',
|
|
45
|
+
doc: 'application/msword',
|
|
46
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
47
|
+
xls: 'application/vnd.ms-excel',
|
|
48
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
49
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
50
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
51
|
+
mp3: 'audio/mpeg',
|
|
52
|
+
mp4: 'video/mp4',
|
|
53
|
+
wav: 'audio/wav',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function getMimeType(filePath) {
|
|
57
|
+
const ext = extname(filePath).toLowerCase().replace('.', '');
|
|
58
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Validate and read attachment files from disk.
|
|
63
|
+
* @param {string[]} filePaths
|
|
64
|
+
* @returns {Array<{path: string, data: Buffer, mimeType: string, filename: string}>}
|
|
65
|
+
*/
|
|
66
|
+
function prepareAttachments(filePaths) {
|
|
67
|
+
const MAX_SIZE = 25 * 1024 * 1024; // 25 MB Gmail limit
|
|
68
|
+
const attachments = [];
|
|
69
|
+
|
|
70
|
+
for (const filePath of filePaths) {
|
|
71
|
+
if (!existsSync(filePath)) {
|
|
72
|
+
throw new Error(`Attachment not found: ${filePath}`);
|
|
73
|
+
}
|
|
74
|
+
const stat = statSync(filePath);
|
|
75
|
+
if (!stat.isFile()) {
|
|
76
|
+
throw new Error(`Not a file: ${filePath}`);
|
|
77
|
+
}
|
|
78
|
+
if (stat.size > MAX_SIZE) {
|
|
79
|
+
throw new Error(`File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 25 MB): ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
attachments.push({
|
|
82
|
+
path: filePath,
|
|
83
|
+
data: readFileSync(filePath),
|
|
84
|
+
mimeType: getMimeType(filePath),
|
|
85
|
+
filename: basename(filePath),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return attachments;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Build an RFC 2822 MIME message string.
|
|
93
|
+
* @param {Object} opts
|
|
94
|
+
* @param {string[]} opts.to
|
|
95
|
+
* @param {string} opts.subject
|
|
96
|
+
* @param {string} opts.body
|
|
97
|
+
* @param {string[]} [opts.cc]
|
|
98
|
+
* @param {string[]} [opts.bcc]
|
|
99
|
+
* @param {boolean} [opts.html]
|
|
100
|
+
* @param {string} [opts.replyTo] - Message-ID for In-Reply-To/References
|
|
101
|
+
* @param {Array} [opts.attachments] - From prepareAttachments()
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
function buildMimeMessage(opts) {
|
|
105
|
+
const lines = [];
|
|
106
|
+
|
|
107
|
+
// Headers
|
|
108
|
+
lines.push(`To: ${opts.to.join(', ')}`);
|
|
109
|
+
lines.push(`Subject: ${opts.subject}`);
|
|
110
|
+
lines.push('MIME-Version: 1.0');
|
|
111
|
+
if (opts.cc && opts.cc.length) lines.push(`Cc: ${opts.cc.join(', ')}`);
|
|
112
|
+
if (opts.bcc && opts.bcc.length) lines.push(`Bcc: ${opts.bcc.join(', ')}`);
|
|
113
|
+
|
|
114
|
+
if (opts.replyTo) {
|
|
115
|
+
const msgId = opts.replyTo.startsWith('<') ? opts.replyTo : `<${opts.replyTo}>`;
|
|
116
|
+
lines.push(`In-Reply-To: ${msgId}`);
|
|
117
|
+
lines.push(`References: ${msgId}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const hasAttachments = opts.attachments && opts.attachments.length > 0;
|
|
121
|
+
const contentType = opts.html ? 'text/html; charset=UTF-8' : 'text/plain; charset=UTF-8';
|
|
122
|
+
|
|
123
|
+
if (!hasAttachments) {
|
|
124
|
+
// Simple single-part message
|
|
125
|
+
lines.push(`Content-Type: ${contentType}`);
|
|
126
|
+
lines.push('Content-Transfer-Encoding: base64');
|
|
127
|
+
lines.push(''); // blank line separating headers from body
|
|
128
|
+
lines.push(Buffer.from(opts.body, 'utf-8').toString('base64'));
|
|
129
|
+
} else {
|
|
130
|
+
// Multipart/mixed
|
|
131
|
+
const boundary = `claudia_${randomBytes(16).toString('hex')}`;
|
|
132
|
+
lines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
133
|
+
lines.push('');
|
|
134
|
+
|
|
135
|
+
// Text part
|
|
136
|
+
lines.push(`--${boundary}`);
|
|
137
|
+
lines.push(`Content-Type: ${contentType}`);
|
|
138
|
+
lines.push('Content-Transfer-Encoding: base64');
|
|
139
|
+
lines.push('');
|
|
140
|
+
lines.push(Buffer.from(opts.body, 'utf-8').toString('base64'));
|
|
141
|
+
lines.push('');
|
|
142
|
+
|
|
143
|
+
// Attachment parts
|
|
144
|
+
for (const att of opts.attachments) {
|
|
145
|
+
lines.push(`--${boundary}`);
|
|
146
|
+
lines.push(`Content-Type: ${att.mimeType}; name="${att.filename}"`);
|
|
147
|
+
lines.push(`Content-Disposition: attachment; filename="${att.filename}"`);
|
|
148
|
+
lines.push('Content-Transfer-Encoding: base64');
|
|
149
|
+
lines.push('');
|
|
150
|
+
// Split base64 into 76-char lines per RFC 2045
|
|
151
|
+
const b64 = att.data.toString('base64');
|
|
152
|
+
lines.push((b64.match(/.{1,76}/g) || []).join('\r\n'));
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
lines.push(`--${boundary}--`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return lines.join('\r\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
24
162
|
// ── Gmail Commands ──
|
|
25
163
|
|
|
26
164
|
export async function gmailLoginCommand() {
|
|
@@ -146,6 +284,90 @@ export async function gmailReadCommand(messageId) {
|
|
|
146
284
|
});
|
|
147
285
|
}
|
|
148
286
|
|
|
287
|
+
export async function gmailSendCommand(opts) {
|
|
288
|
+
const token = await getAccessToken('gmail');
|
|
289
|
+
if (!token) {
|
|
290
|
+
console.error('Not authenticated. Run: claudia gmail login');
|
|
291
|
+
process.exitCode = 1;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Validate required fields (belt-and-suspenders; Commander's requiredOption catches most)
|
|
296
|
+
if (!opts.to || opts.to.length === 0) {
|
|
297
|
+
console.error('At least one --to recipient is required.');
|
|
298
|
+
process.exitCode = 1;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (!opts.subject) {
|
|
302
|
+
console.error('--subject is required.');
|
|
303
|
+
process.exitCode = 1;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!opts.body) {
|
|
307
|
+
console.error('--body is required.');
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Prepare attachments
|
|
313
|
+
let attachments = [];
|
|
314
|
+
if (opts.attach && opts.attach.length > 0) {
|
|
315
|
+
try {
|
|
316
|
+
attachments = prepareAttachments(opts.attach);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.error(err.message);
|
|
319
|
+
process.exitCode = 1;
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Build MIME message and base64url-encode it
|
|
325
|
+
const rawMessage = buildMimeMessage({
|
|
326
|
+
to: opts.to,
|
|
327
|
+
subject: opts.subject,
|
|
328
|
+
body: opts.body,
|
|
329
|
+
cc: opts.cc,
|
|
330
|
+
bcc: opts.bcc,
|
|
331
|
+
html: opts.html,
|
|
332
|
+
replyTo: opts.replyTo,
|
|
333
|
+
attachments,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const encodedMessage = Buffer.from(rawMessage, 'utf-8')
|
|
337
|
+
.toString('base64')
|
|
338
|
+
.replace(/\+/g, '-')
|
|
339
|
+
.replace(/\//g, '_')
|
|
340
|
+
.replace(/=+$/, '');
|
|
341
|
+
|
|
342
|
+
const requestBody = { raw: encodedMessage };
|
|
343
|
+
if (opts.thread) {
|
|
344
|
+
requestBody.threadId = opts.thread;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const resp = await fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers: {
|
|
350
|
+
Authorization: `Bearer ${token}`,
|
|
351
|
+
'Content-Type': 'application/json',
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify(requestBody),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
if (!resp.ok) {
|
|
357
|
+
const err = await resp.text();
|
|
358
|
+
console.error(`Gmail API error (${resp.status}): ${err}`);
|
|
359
|
+
process.exitCode = 1;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const data = await resp.json();
|
|
364
|
+
output({
|
|
365
|
+
id: data.id,
|
|
366
|
+
threadId: data.threadId,
|
|
367
|
+
labelIds: data.labelIds || [],
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
149
371
|
export async function gmailLogoutCommand() {
|
|
150
372
|
const removed = revokeTokens('gmail');
|
|
151
373
|
if (removed) {
|
package/cli/core/database.js
CHANGED
|
@@ -901,7 +901,7 @@ class ClaudiaDatabase {
|
|
|
901
901
|
"ALTER TABLE memories ADD COLUMN lifecycle_tier TEXT DEFAULT 'active'",
|
|
902
902
|
'ALTER TABLE memories ADD COLUMN sacred_reason TEXT',
|
|
903
903
|
'ALTER TABLE memories ADD COLUMN archived_at TEXT',
|
|
904
|
-
'ALTER TABLE memories ADD COLUMN fact_id TEXT
|
|
904
|
+
'ALTER TABLE memories ADD COLUMN fact_id TEXT',
|
|
905
905
|
'ALTER TABLE memories ADD COLUMN hash TEXT',
|
|
906
906
|
'ALTER TABLE memories ADD COLUMN prev_hash TEXT',
|
|
907
907
|
'ALTER TABLE entities ADD COLUMN close_circle BOOLEAN DEFAULT FALSE',
|
|
@@ -920,7 +920,7 @@ class ClaudiaDatabase {
|
|
|
920
920
|
'CREATE INDEX IF NOT EXISTS idx_memories_lifecycle ON memories(lifecycle_tier)'
|
|
921
921
|
);
|
|
922
922
|
this.db.exec(
|
|
923
|
-
'CREATE INDEX IF NOT EXISTS idx_memories_fact_id ON memories(fact_id)'
|
|
923
|
+
'CREATE UNIQUE INDEX IF NOT EXISTS idx_memories_fact_id ON memories(fact_id)'
|
|
924
924
|
);
|
|
925
925
|
this.db.exec(
|
|
926
926
|
'CREATE INDEX IF NOT EXISTS idx_entities_close_circle ON entities(close_circle) WHERE close_circle = 1'
|
package/cli/index.js
CHANGED
|
@@ -522,7 +522,7 @@ program
|
|
|
522
522
|
// ── Gmail subcommand group ──
|
|
523
523
|
const gmail = program
|
|
524
524
|
.command('gmail')
|
|
525
|
-
.description('Gmail integration (login, search, read)');
|
|
525
|
+
.description('Gmail integration (login, search, read, send)');
|
|
526
526
|
|
|
527
527
|
gmail
|
|
528
528
|
.command('login')
|
|
@@ -559,6 +559,23 @@ gmail
|
|
|
559
559
|
await gmailReadCommand(messageId);
|
|
560
560
|
});
|
|
561
561
|
|
|
562
|
+
gmail
|
|
563
|
+
.command('send')
|
|
564
|
+
.description('Send an email (with optional attachments)')
|
|
565
|
+
.requiredOption('--to <email...>', 'Recipient email address(es)')
|
|
566
|
+
.requiredOption('--subject <text>', 'Email subject')
|
|
567
|
+
.requiredOption('--body <text>', 'Email body text')
|
|
568
|
+
.option('--cc <email...>', 'CC recipient(s)')
|
|
569
|
+
.option('--bcc <email...>', 'BCC recipient(s)')
|
|
570
|
+
.option('--attach <filepath...>', 'File(s) to attach')
|
|
571
|
+
.option('--html', 'Treat body as HTML', false)
|
|
572
|
+
.option('--thread <threadId>', 'Thread ID (for replies)')
|
|
573
|
+
.option('--reply-to <messageId>', 'Message-ID for In-Reply-To header')
|
|
574
|
+
.action(async (opts) => {
|
|
575
|
+
const { gmailSendCommand } = await import('./commands/google-auth.js');
|
|
576
|
+
await gmailSendCommand(opts);
|
|
577
|
+
});
|
|
578
|
+
|
|
562
579
|
gmail
|
|
563
580
|
.command('logout')
|
|
564
581
|
.description('Sign out of Gmail (remove stored tokens)')
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
-- Claudia Memory System Schema
|
|
2
|
+
-- SQLite with sqlite-vec for vector similarity search
|
|
3
|
+
-- WAL mode enabled for crash safety
|
|
4
|
+
|
|
5
|
+
-- Enable WAL mode for crash safety
|
|
6
|
+
PRAGMA journal_mode = WAL;
|
|
7
|
+
PRAGMA synchronous = NORMAL;
|
|
8
|
+
PRAGMA foreign_keys = ON;
|
|
9
|
+
|
|
10
|
+
-- ============================================================================
|
|
11
|
+
-- ENTITIES: People, organizations, projects, concepts
|
|
12
|
+
-- ============================================================================
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
name TEXT NOT NULL,
|
|
17
|
+
type TEXT NOT NULL CHECK (type IN ('person', 'organization', 'project', 'concept', 'location')),
|
|
18
|
+
canonical_name TEXT, -- Normalized name for matching (lowercase, no titles)
|
|
19
|
+
description TEXT,
|
|
20
|
+
importance REAL DEFAULT 1.0, -- Decays over time but never deleted
|
|
21
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
22
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
23
|
+
metadata TEXT, -- JSON blob for flexible attributes
|
|
24
|
+
last_contact_at TEXT, -- Last time user interacted with this entity
|
|
25
|
+
contact_frequency_days REAL, -- Average days between contacts (rolling)
|
|
26
|
+
contact_trend TEXT, -- accelerating, stable, decelerating, dormant
|
|
27
|
+
attention_tier TEXT DEFAULT 'standard', -- active, watchlist, standard, archive
|
|
28
|
+
close_circle BOOLEAN DEFAULT FALSE, -- Inner circle: never decay, auto-sacred
|
|
29
|
+
close_circle_reason TEXT, -- Why this entity is close-circle
|
|
30
|
+
UNIQUE(canonical_name, type)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_entities_type ON entities(type);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_entities_canonical ON entities(canonical_name);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_entities_importance ON entities(importance DESC);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_entities_last_contact ON entities(last_contact_at);
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_entities_trend ON entities(contact_trend);
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_entities_attention_tier ON entities(attention_tier);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_entities_close_circle ON entities(close_circle) WHERE close_circle = 1;
|
|
40
|
+
|
|
41
|
+
-- Entity aliases for matching variations (e.g., "Sarah", "Sarah Chen", "S. Chen")
|
|
42
|
+
CREATE TABLE IF NOT EXISTS entity_aliases (
|
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
+
entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
45
|
+
alias TEXT NOT NULL,
|
|
46
|
+
canonical_alias TEXT NOT NULL, -- Normalized for matching
|
|
47
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
48
|
+
UNIQUE(entity_id, canonical_alias)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX IF NOT EXISTS idx_entity_aliases_canonical ON entity_aliases(canonical_alias);
|
|
52
|
+
|
|
53
|
+
-- ============================================================================
|
|
54
|
+
-- MEMORIES: Facts, preferences, observations, learnings
|
|
55
|
+
-- ============================================================================
|
|
56
|
+
|
|
57
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
58
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
59
|
+
content TEXT NOT NULL,
|
|
60
|
+
content_hash TEXT UNIQUE, -- SHA256 for deduplication
|
|
61
|
+
type TEXT NOT NULL CHECK (type IN ('fact', 'preference', 'observation', 'learning', 'commitment', 'pattern')),
|
|
62
|
+
importance REAL DEFAULT 1.0, -- Decays over time
|
|
63
|
+
confidence REAL DEFAULT 1.0, -- How sure we are about this
|
|
64
|
+
source TEXT, -- Where this came from (conversation, document, etc.)
|
|
65
|
+
source_id TEXT, -- Reference to source (episode_id, etc.)
|
|
66
|
+
source_context TEXT, -- One-line breadcrumb (e.g., "Email from Jim re: Forum V+, 2025-01-28")
|
|
67
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
68
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
69
|
+
last_accessed_at TEXT, -- For rehearsal-based importance boost
|
|
70
|
+
access_count INTEGER DEFAULT 0,
|
|
71
|
+
verified_at TEXT, -- When this memory was verified
|
|
72
|
+
verification_status TEXT DEFAULT 'pending', -- pending, verified, flagged, contradicts
|
|
73
|
+
metadata TEXT, -- JSON blob for flexible attributes
|
|
74
|
+
source_channel TEXT DEFAULT 'claude_code', -- Origin channel: claude_code, telegram, slack
|
|
75
|
+
deadline_at TEXT, -- ISO datetime for commitment deadlines (Phase 2: temporal intelligence)
|
|
76
|
+
temporal_markers TEXT, -- JSON: extracted temporal references from content
|
|
77
|
+
lifecycle_tier TEXT DEFAULT 'active', -- sacred/active/cooling/archived
|
|
78
|
+
sacred_reason TEXT, -- Why this memory is sacred (user-protected, auto-detected)
|
|
79
|
+
archived_at TEXT, -- When this memory was archived
|
|
80
|
+
fact_id TEXT UNIQUE, -- UUID for human-friendly reference
|
|
81
|
+
hash TEXT, -- SHA-256 chain hash
|
|
82
|
+
prev_hash TEXT -- Previous hash in chain (NULL for genesis)
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type);
|
|
86
|
+
CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance DESC);
|
|
87
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created ON memories(created_at DESC);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_memories_hash ON memories(content_hash);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_memories_deadline ON memories(deadline_at);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_memories_verification ON memories(verification_status);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_memories_lifecycle ON memories(lifecycle_tier);
|
|
92
|
+
CREATE INDEX IF NOT EXISTS idx_memories_fact_id ON memories(fact_id);
|
|
93
|
+
|
|
94
|
+
-- Junction table linking memories to entities
|
|
95
|
+
CREATE TABLE IF NOT EXISTS memory_entities (
|
|
96
|
+
memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
97
|
+
entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
98
|
+
relationship TEXT DEFAULT 'about', -- about, by, to, from, etc.
|
|
99
|
+
PRIMARY KEY (memory_id, entity_id, relationship)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_memory_entities_entity ON memory_entities(entity_id);
|
|
103
|
+
|
|
104
|
+
-- ============================================================================
|
|
105
|
+
-- RELATIONSHIPS: Graph connections between entities
|
|
106
|
+
-- ============================================================================
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS relationships (
|
|
109
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
110
|
+
source_entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
111
|
+
target_entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
112
|
+
relationship_type TEXT NOT NULL, -- works_with, manages, client_of, etc.
|
|
113
|
+
strength REAL DEFAULT 1.0, -- Relationship strength (decays/grows)
|
|
114
|
+
origin_type TEXT DEFAULT 'extracted', -- user_stated, extracted, inferred, corrected
|
|
115
|
+
direction TEXT DEFAULT 'bidirectional' CHECK (direction IN ('forward', 'backward', 'bidirectional')),
|
|
116
|
+
valid_at TEXT, -- When this relationship became true in the real world
|
|
117
|
+
invalid_at TEXT, -- When this relationship was superseded (NULL = current)
|
|
118
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
119
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
120
|
+
metadata TEXT,
|
|
121
|
+
lifecycle_tier TEXT DEFAULT 'active', -- Mirrors memory lifecycle for consistency
|
|
122
|
+
UNIQUE(source_entity_id, target_entity_id, relationship_type)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_source ON relationships(source_entity_id);
|
|
126
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_target ON relationships(target_entity_id);
|
|
127
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_type ON relationships(relationship_type);
|
|
128
|
+
CREATE INDEX IF NOT EXISTS idx_relationships_temporal ON relationships(invalid_at, valid_at);
|
|
129
|
+
|
|
130
|
+
-- ============================================================================
|
|
131
|
+
-- EPISODES: Conversation session summaries
|
|
132
|
+
-- ============================================================================
|
|
133
|
+
|
|
134
|
+
CREATE TABLE IF NOT EXISTS episodes (
|
|
135
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
+
session_id TEXT UNIQUE, -- External session identifier
|
|
137
|
+
summary TEXT,
|
|
138
|
+
narrative TEXT, -- Free-form session narrative (tone, context, unresolved threads)
|
|
139
|
+
started_at TEXT DEFAULT (datetime('now')),
|
|
140
|
+
ended_at TEXT,
|
|
141
|
+
message_count INTEGER DEFAULT 0,
|
|
142
|
+
turn_count INTEGER DEFAULT 0, -- Buffered turns count
|
|
143
|
+
is_summarized INTEGER DEFAULT 0, -- Whether session has been summarized by Claude
|
|
144
|
+
source TEXT, -- Origin channel: 'claude_code', 'telegram', 'slack', etc.
|
|
145
|
+
ingested_at TEXT, -- When Claude Code read this (NULL = unread)
|
|
146
|
+
key_topics TEXT, -- JSON array of main topics
|
|
147
|
+
metadata TEXT
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_session ON episodes(session_id);
|
|
151
|
+
CREATE INDEX IF NOT EXISTS idx_episodes_started ON episodes(started_at DESC);
|
|
152
|
+
|
|
153
|
+
-- ============================================================================
|
|
154
|
+
-- MESSAGES: Individual conversation turns
|
|
155
|
+
-- ============================================================================
|
|
156
|
+
|
|
157
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
158
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
159
|
+
episode_id INTEGER REFERENCES episodes(id) ON DELETE CASCADE,
|
|
160
|
+
role TEXT NOT NULL CHECK (role IN ('user', 'assistant', 'system')),
|
|
161
|
+
content TEXT NOT NULL,
|
|
162
|
+
content_hash TEXT, -- For deduplication
|
|
163
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
164
|
+
metadata TEXT
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
CREATE INDEX IF NOT EXISTS idx_messages_episode ON messages(episode_id);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at DESC);
|
|
169
|
+
|
|
170
|
+
-- ============================================================================
|
|
171
|
+
-- PATTERNS: Detected behavioral patterns
|
|
172
|
+
-- ============================================================================
|
|
173
|
+
|
|
174
|
+
CREATE TABLE IF NOT EXISTS patterns (
|
|
175
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
176
|
+
name TEXT NOT NULL,
|
|
177
|
+
description TEXT NOT NULL,
|
|
178
|
+
pattern_type TEXT NOT NULL, -- behavioral, communication, scheduling, relationship
|
|
179
|
+
occurrences INTEGER DEFAULT 1,
|
|
180
|
+
first_observed_at TEXT DEFAULT (datetime('now')),
|
|
181
|
+
last_observed_at TEXT DEFAULT (datetime('now')),
|
|
182
|
+
confidence REAL DEFAULT 0.5, -- Grows with observations
|
|
183
|
+
is_active INTEGER DEFAULT 1,
|
|
184
|
+
evidence TEXT, -- JSON array of supporting observations
|
|
185
|
+
metadata TEXT
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_type ON patterns(pattern_type);
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_active ON patterns(is_active);
|
|
190
|
+
CREATE INDEX IF NOT EXISTS idx_patterns_confidence ON patterns(confidence DESC);
|
|
191
|
+
|
|
192
|
+
-- ============================================================================
|
|
193
|
+
-- PREDICTIONS: Proactive suggestions
|
|
194
|
+
-- ============================================================================
|
|
195
|
+
|
|
196
|
+
CREATE TABLE IF NOT EXISTS predictions (
|
|
197
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
198
|
+
content TEXT NOT NULL,
|
|
199
|
+
prediction_type TEXT NOT NULL, -- reminder, suggestion, warning, insight
|
|
200
|
+
priority REAL DEFAULT 0.5,
|
|
201
|
+
expires_at TEXT, -- When this prediction is no longer relevant
|
|
202
|
+
is_shown INTEGER DEFAULT 0, -- Whether user has seen this
|
|
203
|
+
is_acted_on INTEGER DEFAULT 0, -- Whether user acted on this
|
|
204
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
205
|
+
shown_at TEXT,
|
|
206
|
+
prediction_pattern_name TEXT, -- Links to pattern for feedback loop
|
|
207
|
+
metadata TEXT
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_predictions_type ON predictions(prediction_type);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS idx_predictions_expires ON predictions(expires_at);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS idx_predictions_shown ON predictions(is_shown);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_predictions_priority ON predictions(priority DESC);
|
|
214
|
+
|
|
215
|
+
-- ============================================================================
|
|
216
|
+
-- CONFIG: Runtime configuration stored in database
|
|
217
|
+
-- ============================================================================
|
|
218
|
+
|
|
219
|
+
CREATE TABLE IF NOT EXISTS config (
|
|
220
|
+
key TEXT PRIMARY KEY,
|
|
221
|
+
value TEXT NOT NULL,
|
|
222
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
-- ============================================================================
|
|
226
|
+
-- VECTOR TABLES: sqlite-vec virtual tables for semantic search
|
|
227
|
+
-- ============================================================================
|
|
228
|
+
-- NOTE: vec0 virtual tables are created by database.py initialize() with
|
|
229
|
+
-- configurable dimensions from config.embedding_dimensions.
|
|
230
|
+
-- This allows changing embedding models without schema changes.
|
|
231
|
+
|
|
232
|
+
-- ============================================================================
|
|
233
|
+
-- TURN BUFFER: Raw conversation turns awaiting session summary
|
|
234
|
+
-- ============================================================================
|
|
235
|
+
|
|
236
|
+
CREATE TABLE IF NOT EXISTS turn_buffer (
|
|
237
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
238
|
+
episode_id INTEGER NOT NULL REFERENCES episodes(id) ON DELETE CASCADE,
|
|
239
|
+
turn_number INTEGER NOT NULL,
|
|
240
|
+
user_content TEXT,
|
|
241
|
+
assistant_content TEXT,
|
|
242
|
+
is_archived INTEGER DEFAULT 0,
|
|
243
|
+
source TEXT, -- Origin channel: 'claude_code', 'telegram', 'slack', etc.
|
|
244
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
CREATE INDEX IF NOT EXISTS idx_turn_buffer_episode ON turn_buffer(episode_id);
|
|
248
|
+
|
|
249
|
+
-- Episode narrative embeddings are created by database.py with configurable dimensions.
|
|
250
|
+
|
|
251
|
+
-- ============================================================================
|
|
252
|
+
-- DOCUMENTS: File registry for provenance tracking
|
|
253
|
+
-- ============================================================================
|
|
254
|
+
|
|
255
|
+
CREATE TABLE IF NOT EXISTS documents (
|
|
256
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
257
|
+
file_hash TEXT, -- SHA-256 of file contents for deduplication
|
|
258
|
+
filename TEXT NOT NULL,
|
|
259
|
+
mime_type TEXT,
|
|
260
|
+
file_size INTEGER,
|
|
261
|
+
storage_provider TEXT DEFAULT 'local' CHECK (storage_provider IN ('local', 'google_drive')),
|
|
262
|
+
storage_path TEXT, -- Resolved file path on disk or cloud URI
|
|
263
|
+
source_type TEXT CHECK (source_type IN ('gmail', 'transcript', 'upload', 'capture', 'session')),
|
|
264
|
+
source_ref TEXT, -- External reference (email ID, URL, etc.)
|
|
265
|
+
summary TEXT,
|
|
266
|
+
lifecycle TEXT DEFAULT 'active' CHECK (lifecycle IN ('active', 'dormant', 'archived', 'purged')),
|
|
267
|
+
last_accessed_at TEXT,
|
|
268
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
269
|
+
updated_at TEXT DEFAULT (datetime('now')),
|
|
270
|
+
workspace_id TEXT, -- Project hash for isolation
|
|
271
|
+
metadata TEXT -- JSON blob
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
CREATE INDEX IF NOT EXISTS idx_documents_hash ON documents(file_hash);
|
|
275
|
+
CREATE INDEX IF NOT EXISTS idx_documents_lifecycle ON documents(lifecycle);
|
|
276
|
+
CREATE INDEX IF NOT EXISTS idx_documents_source_type ON documents(source_type);
|
|
277
|
+
CREATE INDEX IF NOT EXISTS idx_documents_workspace ON documents(workspace_id);
|
|
278
|
+
CREATE INDEX IF NOT EXISTS idx_documents_source_lookup ON documents(source_type, source_ref);
|
|
279
|
+
|
|
280
|
+
-- Links documents to entities (people, projects, etc.)
|
|
281
|
+
CREATE TABLE IF NOT EXISTS entity_documents (
|
|
282
|
+
entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
283
|
+
document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
284
|
+
relationship TEXT DEFAULT 'about' CHECK (relationship IN ('sent_by', 'about', 'mentioned_in', 'authored', 'received_by')),
|
|
285
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
286
|
+
PRIMARY KEY (entity_id, document_id, relationship)
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
CREATE INDEX IF NOT EXISTS idx_entity_documents_doc ON entity_documents(document_id);
|
|
290
|
+
|
|
291
|
+
-- Links memories to source documents (provenance)
|
|
292
|
+
CREATE TABLE IF NOT EXISTS memory_sources (
|
|
293
|
+
memory_id INTEGER NOT NULL REFERENCES memories(id) ON DELETE CASCADE,
|
|
294
|
+
document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
|
295
|
+
excerpt TEXT, -- Relevant excerpt from the document
|
|
296
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
297
|
+
PRIMARY KEY (memory_id, document_id)
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
CREATE INDEX IF NOT EXISTS idx_memory_sources_doc ON memory_sources(document_id);
|
|
301
|
+
|
|
302
|
+
-- ============================================================================
|
|
303
|
+
-- DATABASE METADATA
|
|
304
|
+
-- ============================================================================
|
|
305
|
+
|
|
306
|
+
-- Stores database-level metadata like workspace path for identification
|
|
307
|
+
CREATE TABLE IF NOT EXISTS _meta (
|
|
308
|
+
key TEXT PRIMARY KEY,
|
|
309
|
+
value TEXT,
|
|
310
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
-- ============================================================================
|
|
314
|
+
-- MIGRATION TRACKING
|
|
315
|
+
-- ============================================================================
|
|
316
|
+
|
|
317
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
318
|
+
version INTEGER PRIMARY KEY,
|
|
319
|
+
applied_at TEXT DEFAULT (datetime('now')),
|
|
320
|
+
description TEXT
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
-- Record schema versions
|
|
324
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
325
|
+
VALUES (1, 'Initial schema with entities, memories, relationships, episodes, patterns, predictions');
|
|
326
|
+
|
|
327
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
328
|
+
VALUES (2, 'Add turn_buffer table, episode narrative/summary columns, episode_embeddings');
|
|
329
|
+
|
|
330
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
331
|
+
VALUES (3, 'Add source_context to memories, is_archived to turn_buffer for episodic provenance');
|
|
332
|
+
|
|
333
|
+
-- NOTE: FTS5 full-text search (migration v4) is created by database.py migration code
|
|
334
|
+
-- rather than here, because CREATE TRIGGER statements contain internal semicolons
|
|
335
|
+
-- that the schema.sql line-based parser cannot handle.
|
|
336
|
+
|
|
337
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
338
|
+
VALUES (5, 'Add verification columns to memories, prediction_pattern_name to predictions');
|
|
339
|
+
|
|
340
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
341
|
+
VALUES (6, 'Add source and ingested_at to episodes, source to turn_buffer for gateway integration');
|
|
342
|
+
|
|
343
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
344
|
+
VALUES (7, 'Add documents, entity_documents, memory_sources tables for provenance tracking');
|
|
345
|
+
|
|
346
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
347
|
+
VALUES (8, 'Add valid_at, invalid_at to relationships for bi-temporal tracking');
|
|
348
|
+
|
|
349
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
350
|
+
VALUES (9, 'Add _meta table for database identification and workspace path tracking');
|
|
351
|
+
|
|
352
|
+
-- ============================================================================
|
|
353
|
+
-- REFLECTIONS: Persistent learnings and observations (from /meditate)
|
|
354
|
+
-- ============================================================================
|
|
355
|
+
|
|
356
|
+
CREATE TABLE IF NOT EXISTS reflections (
|
|
357
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
358
|
+
episode_id INTEGER REFERENCES episodes(id),
|
|
359
|
+
|
|
360
|
+
-- Type and content
|
|
361
|
+
reflection_type TEXT NOT NULL CHECK (reflection_type IN ('observation', 'pattern', 'learning', 'question')),
|
|
362
|
+
content TEXT NOT NULL,
|
|
363
|
+
content_hash TEXT, -- For deduplication
|
|
364
|
+
|
|
365
|
+
-- Optional entity association
|
|
366
|
+
about_entity_id INTEGER REFERENCES entities(id),
|
|
367
|
+
|
|
368
|
+
-- Scoring (reflections are user-approved, so start high)
|
|
369
|
+
importance REAL DEFAULT 0.7,
|
|
370
|
+
confidence REAL DEFAULT 0.8,
|
|
371
|
+
|
|
372
|
+
-- Very slow decay (reflections are long-term learnings)
|
|
373
|
+
decay_rate REAL DEFAULT 0.999,
|
|
374
|
+
|
|
375
|
+
-- Aggregation tracking
|
|
376
|
+
aggregated_from TEXT, -- JSON array of reflection IDs this merged from
|
|
377
|
+
aggregation_count INTEGER DEFAULT 1,
|
|
378
|
+
|
|
379
|
+
-- Timeline tracking (pattern evolution)
|
|
380
|
+
first_observed_at TEXT DEFAULT (datetime('now')),
|
|
381
|
+
last_confirmed_at TEXT DEFAULT (datetime('now')),
|
|
382
|
+
|
|
383
|
+
-- Embedding for semantic search
|
|
384
|
+
embedding BLOB,
|
|
385
|
+
|
|
386
|
+
-- Timestamps
|
|
387
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
388
|
+
updated_at TEXT,
|
|
389
|
+
surfaced_count INTEGER DEFAULT 0,
|
|
390
|
+
last_surfaced_at TEXT
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
CREATE INDEX IF NOT EXISTS idx_reflections_type ON reflections(reflection_type);
|
|
394
|
+
CREATE INDEX IF NOT EXISTS idx_reflections_importance ON reflections(importance DESC);
|
|
395
|
+
CREATE INDEX IF NOT EXISTS idx_reflections_entity ON reflections(about_entity_id);
|
|
396
|
+
CREATE INDEX IF NOT EXISTS idx_reflections_episode ON reflections(episode_id);
|
|
397
|
+
|
|
398
|
+
-- Reflection embeddings are created by database.py with configurable dimensions.
|
|
399
|
+
|
|
400
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
401
|
+
VALUES (10, 'Add reflections table and reflection_embeddings for /meditate skill');
|
|
402
|
+
|
|
403
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
404
|
+
VALUES (11, 'Add compound index for fast source lookup on documents');
|
|
405
|
+
|
|
406
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
407
|
+
VALUES (13, 'Add origin_type to memories, agent_dispatches table for Trust North Star');
|
|
408
|
+
|
|
409
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
410
|
+
VALUES (14, 'Add dispatch_tier to agent_dispatches for native agent team support');
|
|
411
|
+
|
|
412
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
413
|
+
VALUES (15, 'Add origin_type to relationships for organic trust model');
|
|
414
|
+
|
|
415
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
416
|
+
VALUES (16, 'Add source_channel to memories for channel-aware memory');
|
|
417
|
+
|
|
418
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
419
|
+
VALUES (17, 'Add deadline_at and temporal_markers to memories for temporal intelligence');
|
|
420
|
+
|
|
421
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
422
|
+
VALUES (18, 'Add contact velocity and attention tier to entities for proactive relationship intelligence');
|
|
423
|
+
|
|
424
|
+
-- ============================================================================
|
|
425
|
+
-- ENTITY SUMMARIES: Hierarchical summaries for graph-aware retrieval
|
|
426
|
+
-- ============================================================================
|
|
427
|
+
|
|
428
|
+
CREATE TABLE IF NOT EXISTS entity_summaries (
|
|
429
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
430
|
+
entity_id INTEGER NOT NULL REFERENCES entities(id) ON DELETE CASCADE,
|
|
431
|
+
summary TEXT NOT NULL, -- Generated summary of all memories about this entity
|
|
432
|
+
summary_type TEXT DEFAULT 'overview', -- overview, relationship_map, timeline
|
|
433
|
+
memory_count INTEGER DEFAULT 0, -- Number of memories summarized
|
|
434
|
+
relationship_count INTEGER DEFAULT 0, -- Number of relationships included
|
|
435
|
+
generated_at TEXT DEFAULT (datetime('now')),
|
|
436
|
+
expires_at TEXT, -- When to regenerate (NULL = never expires)
|
|
437
|
+
metadata TEXT, -- JSON: entity_ids_included, key_themes, etc.
|
|
438
|
+
UNIQUE(entity_id, summary_type)
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
CREATE INDEX IF NOT EXISTS idx_entity_summaries_entity ON entity_summaries(entity_id);
|
|
442
|
+
CREATE INDEX IF NOT EXISTS idx_entity_summaries_expires ON entity_summaries(expires_at);
|
|
443
|
+
|
|
444
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
445
|
+
VALUES (19, 'Add entity_summaries table for hierarchical graph-aware retrieval');
|
|
446
|
+
|
|
447
|
+
-- ============================================================================
|
|
448
|
+
-- AGENT DISPATCHES: Track delegated tasks to sub-agents
|
|
449
|
+
-- ============================================================================
|
|
450
|
+
|
|
451
|
+
CREATE TABLE IF NOT EXISTS agent_dispatches (
|
|
452
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
453
|
+
agent_name TEXT NOT NULL,
|
|
454
|
+
dispatch_category TEXT NOT NULL,
|
|
455
|
+
task_summary TEXT,
|
|
456
|
+
started_at TEXT DEFAULT (datetime('now')),
|
|
457
|
+
completed_at TEXT,
|
|
458
|
+
duration_ms INTEGER,
|
|
459
|
+
success INTEGER DEFAULT 1,
|
|
460
|
+
required_claudia_judgment INTEGER DEFAULT 0,
|
|
461
|
+
judgment_reason TEXT,
|
|
462
|
+
episode_id INTEGER REFERENCES episodes(id),
|
|
463
|
+
user_approved INTEGER DEFAULT 1,
|
|
464
|
+
dispatch_tier TEXT DEFAULT 'task',
|
|
465
|
+
metadata TEXT
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
CREATE INDEX IF NOT EXISTS idx_agent_dispatches_agent ON agent_dispatches(agent_name);
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_agent_dispatches_category ON agent_dispatches(dispatch_category);
|
|
470
|
+
CREATE INDEX IF NOT EXISTS idx_agent_dispatches_started ON agent_dispatches(started_at DESC);
|
|
471
|
+
|
|
472
|
+
-- NOTE: dispatch_tier validation trigger is created by database.py migration code
|
|
473
|
+
-- rather than here, because CREATE TRIGGER statements contain internal semicolons
|
|
474
|
+
-- that the schema.sql line-based parser cannot handle.
|
|
475
|
+
|
|
476
|
+
INSERT OR IGNORE INTO schema_migrations (version, description)
|
|
477
|
+
VALUES (20, 'Add lifecycle tiers, sacred memories, close-circle entities, fact_id, SHA-256 chain');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get-claudia",
|
|
3
|
-
"version": "1.51.
|
|
3
|
+
"version": "1.51.6",
|
|
4
4
|
"description": "An AI assistant who learns how you work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claudia",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"cli",
|
|
35
35
|
"template-v2",
|
|
36
36
|
"assets",
|
|
37
|
-
"visualizer"
|
|
37
|
+
"visualizer",
|
|
38
|
+
"memory-daemon/claudia_memory/schema.sql"
|
|
38
39
|
],
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"better-sqlite3": "^11.8.1",
|
package/template-v2/CLAUDE.md
CHANGED
|
@@ -328,6 +328,7 @@ I adapt to whatever tools are available. When you ask me to do something that ne
|
|
|
328
328
|
| `claudia gmail status` | Check if Gmail is connected |
|
|
329
329
|
| `claudia gmail search "<query>"` | Search emails (Gmail search syntax) |
|
|
330
330
|
| `claudia gmail read <messageId>` | Read a specific email |
|
|
331
|
+
| `claudia gmail send --to <email> --subject <text> --body <text>` | Send email (supports --attach, --cc, --bcc, --html, --thread, --reply-to) |
|
|
331
332
|
| `claudia gmail logout` | Disconnect Gmail, remove tokens |
|
|
332
333
|
| `claudia calendar login` | Opens browser for Calendar-only OAuth |
|
|
333
334
|
| `claudia calendar status` | Check if Calendar is connected |
|