@usejarvis/brain 0.1.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/LICENSE +153 -0
- package/README.md +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +252 -0
- package/src/actions/browser/session.ts +437 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +321 -0
- package/src/actions/tools/builtin.ts +846 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +297 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +348 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +119 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +194 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +190 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vault — Awareness CRUD
|
|
3
|
+
*
|
|
4
|
+
* Database operations for screen_captures, awareness_sessions, and awareness_suggestions tables.
|
|
5
|
+
* Follows the same patterns as observations.ts and commitments.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getDb, generateId } from './schema.ts';
|
|
9
|
+
import type {
|
|
10
|
+
ScreenCaptureRow,
|
|
11
|
+
SessionRow,
|
|
12
|
+
SuggestionRow,
|
|
13
|
+
SuggestionType,
|
|
14
|
+
AppUsageStat,
|
|
15
|
+
} from '../awareness/types.ts';
|
|
16
|
+
|
|
17
|
+
// ── Screen Captures ──
|
|
18
|
+
|
|
19
|
+
export function createCapture(data: {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
sessionId?: string;
|
|
22
|
+
imagePath?: string;
|
|
23
|
+
thumbnailPath?: string;
|
|
24
|
+
pixelChangePct: number;
|
|
25
|
+
ocrText?: string;
|
|
26
|
+
appName?: string;
|
|
27
|
+
windowTitle?: string;
|
|
28
|
+
url?: string;
|
|
29
|
+
filePath?: string;
|
|
30
|
+
retentionTier?: 'full' | 'key_moment' | 'metadata_only';
|
|
31
|
+
}): ScreenCaptureRow {
|
|
32
|
+
const db = getDb();
|
|
33
|
+
const id = generateId();
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
|
|
36
|
+
db.prepare(`
|
|
37
|
+
INSERT INTO screen_captures
|
|
38
|
+
(id, timestamp, session_id, image_path, thumbnail_path, pixel_change_pct,
|
|
39
|
+
ocr_text, app_name, window_title, url, file_path, retention_tier, created_at)
|
|
40
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
41
|
+
`).run(
|
|
42
|
+
id,
|
|
43
|
+
data.timestamp,
|
|
44
|
+
data.sessionId ?? null,
|
|
45
|
+
data.imagePath ?? null,
|
|
46
|
+
data.thumbnailPath ?? null,
|
|
47
|
+
data.pixelChangePct,
|
|
48
|
+
data.ocrText ?? null,
|
|
49
|
+
data.appName ?? null,
|
|
50
|
+
data.windowTitle ?? null,
|
|
51
|
+
data.url ?? null,
|
|
52
|
+
data.filePath ?? null,
|
|
53
|
+
data.retentionTier ?? 'full',
|
|
54
|
+
now,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
id,
|
|
59
|
+
timestamp: data.timestamp,
|
|
60
|
+
session_id: data.sessionId ?? null,
|
|
61
|
+
image_path: data.imagePath ?? null,
|
|
62
|
+
thumbnail_path: data.thumbnailPath ?? null,
|
|
63
|
+
pixel_change_pct: data.pixelChangePct,
|
|
64
|
+
ocr_text: data.ocrText ?? null,
|
|
65
|
+
app_name: data.appName ?? null,
|
|
66
|
+
window_title: data.windowTitle ?? null,
|
|
67
|
+
url: data.url ?? null,
|
|
68
|
+
file_path: data.filePath ?? null,
|
|
69
|
+
retention_tier: data.retentionTier ?? 'full',
|
|
70
|
+
created_at: now,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getCapture(id: string): ScreenCaptureRow | null {
|
|
75
|
+
const db = getDb();
|
|
76
|
+
return db.prepare('SELECT * FROM screen_captures WHERE id = ?').get(id) as ScreenCaptureRow | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getRecentCaptures(limit: number = 50, appName?: string): ScreenCaptureRow[] {
|
|
80
|
+
const db = getDb();
|
|
81
|
+
if (appName) {
|
|
82
|
+
return db.prepare(
|
|
83
|
+
'SELECT * FROM screen_captures WHERE app_name = ? ORDER BY timestamp DESC LIMIT ?'
|
|
84
|
+
).all(appName, limit) as ScreenCaptureRow[];
|
|
85
|
+
}
|
|
86
|
+
return db.prepare(
|
|
87
|
+
'SELECT * FROM screen_captures ORDER BY timestamp DESC LIMIT ?'
|
|
88
|
+
).all(limit) as ScreenCaptureRow[];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getCapturesInRange(startTime: number, endTime: number): ScreenCaptureRow[] {
|
|
92
|
+
const db = getDb();
|
|
93
|
+
return db.prepare(
|
|
94
|
+
'SELECT * FROM screen_captures WHERE timestamp >= ? AND timestamp <= ? ORDER BY timestamp ASC'
|
|
95
|
+
).all(startTime, endTime) as ScreenCaptureRow[];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function getAppUsageStats(startTime: number, endTime: number): AppUsageStat[] {
|
|
99
|
+
const db = getDb();
|
|
100
|
+
const rows = db.prepare(`
|
|
101
|
+
SELECT app_name, COUNT(*) as capture_count
|
|
102
|
+
FROM screen_captures
|
|
103
|
+
WHERE timestamp >= ? AND timestamp <= ? AND app_name IS NOT NULL
|
|
104
|
+
GROUP BY app_name
|
|
105
|
+
ORDER BY capture_count DESC
|
|
106
|
+
`).all(startTime, endTime) as Array<{ app_name: string; capture_count: number }>;
|
|
107
|
+
|
|
108
|
+
const totalCaptures = rows.reduce((sum, r) => sum + r.capture_count, 0);
|
|
109
|
+
|
|
110
|
+
return rows.map(r => ({
|
|
111
|
+
app: r.app_name,
|
|
112
|
+
captureCount: r.capture_count,
|
|
113
|
+
minutes: Math.round((r.capture_count * 7) / 60), // ~7s per capture
|
|
114
|
+
percentage: totalCaptures > 0 ? Math.round((r.capture_count / totalCaptures) * 100) : 0,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function getCaptureCountSince(timestamp: number): number {
|
|
119
|
+
const db = getDb();
|
|
120
|
+
const row = db.prepare(
|
|
121
|
+
'SELECT COUNT(*) as count FROM screen_captures WHERE timestamp >= ?'
|
|
122
|
+
).get(timestamp) as { count: number };
|
|
123
|
+
return row.count;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function updateCaptureRetention(id: string, tier: 'full' | 'key_moment' | 'metadata_only'): void {
|
|
127
|
+
const db = getDb();
|
|
128
|
+
db.prepare('UPDATE screen_captures SET retention_tier = ? WHERE id = ?').run(tier, id);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function deleteCapturesBefore(timestamp: number, retentionTier: string): number {
|
|
132
|
+
const db = getDb();
|
|
133
|
+
const result = db.prepare(
|
|
134
|
+
'DELETE FROM screen_captures WHERE timestamp < ? AND retention_tier = ?'
|
|
135
|
+
).run(timestamp, retentionTier);
|
|
136
|
+
return result.changes;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function updateCaptureOcrText(id: string, ocrText: string): void {
|
|
140
|
+
const db = getDb();
|
|
141
|
+
db.prepare('UPDATE screen_captures SET ocr_text = ? WHERE id = ?').run(ocrText, id);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function getCapturesForSession(sessionId: string, limit: number = 50): ScreenCaptureRow[] {
|
|
145
|
+
const db = getDb();
|
|
146
|
+
return db.prepare(
|
|
147
|
+
'SELECT * FROM screen_captures WHERE session_id = ? ORDER BY timestamp DESC LIMIT ?'
|
|
148
|
+
).all(sessionId, limit) as ScreenCaptureRow[];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Awareness Sessions ──
|
|
152
|
+
|
|
153
|
+
export function createSession(data: {
|
|
154
|
+
startedAt: number;
|
|
155
|
+
apps?: string[];
|
|
156
|
+
projectContext?: string;
|
|
157
|
+
}): SessionRow {
|
|
158
|
+
const db = getDb();
|
|
159
|
+
const id = generateId();
|
|
160
|
+
const now = Date.now();
|
|
161
|
+
|
|
162
|
+
db.prepare(`
|
|
163
|
+
INSERT INTO awareness_sessions
|
|
164
|
+
(id, started_at, ended_at, topic, apps, project_context, action_types, entity_links, summary, capture_count, created_at)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
166
|
+
`).run(
|
|
167
|
+
id,
|
|
168
|
+
data.startedAt,
|
|
169
|
+
null,
|
|
170
|
+
null,
|
|
171
|
+
JSON.stringify(data.apps ?? []),
|
|
172
|
+
data.projectContext ?? null,
|
|
173
|
+
JSON.stringify([]),
|
|
174
|
+
JSON.stringify([]),
|
|
175
|
+
null,
|
|
176
|
+
0,
|
|
177
|
+
now,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
id,
|
|
182
|
+
started_at: data.startedAt,
|
|
183
|
+
ended_at: null,
|
|
184
|
+
topic: null,
|
|
185
|
+
apps: JSON.stringify(data.apps ?? []),
|
|
186
|
+
project_context: data.projectContext ?? null,
|
|
187
|
+
action_types: JSON.stringify([]),
|
|
188
|
+
entity_links: JSON.stringify([]),
|
|
189
|
+
summary: null,
|
|
190
|
+
capture_count: 0,
|
|
191
|
+
created_at: now,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function getSession(id: string): SessionRow | null {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
return db.prepare('SELECT * FROM awareness_sessions WHERE id = ?').get(id) as SessionRow | null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function updateSession(id: string, updates: Partial<{
|
|
201
|
+
ended_at: number | null;
|
|
202
|
+
topic: string | null;
|
|
203
|
+
apps: string[];
|
|
204
|
+
project_context: string | null;
|
|
205
|
+
action_types: string[];
|
|
206
|
+
entity_links: string[];
|
|
207
|
+
summary: string | null;
|
|
208
|
+
capture_count: number;
|
|
209
|
+
}>): void {
|
|
210
|
+
const db = getDb();
|
|
211
|
+
const setClauses: string[] = [];
|
|
212
|
+
const params: unknown[] = [];
|
|
213
|
+
|
|
214
|
+
if (updates.ended_at !== undefined) { setClauses.push('ended_at = ?'); params.push(updates.ended_at); }
|
|
215
|
+
if (updates.topic !== undefined) { setClauses.push('topic = ?'); params.push(updates.topic); }
|
|
216
|
+
if (updates.apps !== undefined) { setClauses.push('apps = ?'); params.push(JSON.stringify(updates.apps)); }
|
|
217
|
+
if (updates.project_context !== undefined) { setClauses.push('project_context = ?'); params.push(updates.project_context); }
|
|
218
|
+
if (updates.action_types !== undefined) { setClauses.push('action_types = ?'); params.push(JSON.stringify(updates.action_types)); }
|
|
219
|
+
if (updates.entity_links !== undefined) { setClauses.push('entity_links = ?'); params.push(JSON.stringify(updates.entity_links)); }
|
|
220
|
+
if (updates.summary !== undefined) { setClauses.push('summary = ?'); params.push(updates.summary); }
|
|
221
|
+
if (updates.capture_count !== undefined) { setClauses.push('capture_count = ?'); params.push(updates.capture_count); }
|
|
222
|
+
|
|
223
|
+
if (setClauses.length === 0) return;
|
|
224
|
+
|
|
225
|
+
params.push(id);
|
|
226
|
+
db.prepare(`UPDATE awareness_sessions SET ${setClauses.join(', ')} WHERE id = ?`).run(...params as any[]);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function endSession(id: string, summary?: string): void {
|
|
230
|
+
const db = getDb();
|
|
231
|
+
db.prepare(
|
|
232
|
+
'UPDATE awareness_sessions SET ended_at = ?, summary = ? WHERE id = ?'
|
|
233
|
+
).run(Date.now(), summary ?? null, id);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function getRecentSessions(limit: number = 20): SessionRow[] {
|
|
237
|
+
const db = getDb();
|
|
238
|
+
return db.prepare(
|
|
239
|
+
'SELECT * FROM awareness_sessions ORDER BY started_at DESC LIMIT ?'
|
|
240
|
+
).all(limit) as SessionRow[];
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function incrementSessionCaptureCount(id: string): void {
|
|
244
|
+
const db = getDb();
|
|
245
|
+
db.prepare(
|
|
246
|
+
'UPDATE awareness_sessions SET capture_count = capture_count + 1 WHERE id = ?'
|
|
247
|
+
).run(id);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ── Awareness Suggestions ──
|
|
251
|
+
|
|
252
|
+
export function createSuggestion(data: {
|
|
253
|
+
type: SuggestionType;
|
|
254
|
+
triggerCaptureId?: string;
|
|
255
|
+
title: string;
|
|
256
|
+
body: string;
|
|
257
|
+
context?: Record<string, unknown>;
|
|
258
|
+
}): SuggestionRow {
|
|
259
|
+
const db = getDb();
|
|
260
|
+
const id = generateId();
|
|
261
|
+
const now = Date.now();
|
|
262
|
+
|
|
263
|
+
db.prepare(`
|
|
264
|
+
INSERT INTO awareness_suggestions
|
|
265
|
+
(id, type, trigger_capture_id, title, body, context, delivered, delivered_at, delivery_channel, dismissed, acted_on, created_at)
|
|
266
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
267
|
+
`).run(
|
|
268
|
+
id,
|
|
269
|
+
data.type,
|
|
270
|
+
data.triggerCaptureId ?? null,
|
|
271
|
+
data.title,
|
|
272
|
+
data.body,
|
|
273
|
+
data.context ? JSON.stringify(data.context) : null,
|
|
274
|
+
0, null, null, 0, 0,
|
|
275
|
+
now,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
id,
|
|
280
|
+
type: data.type,
|
|
281
|
+
trigger_capture_id: data.triggerCaptureId ?? null,
|
|
282
|
+
title: data.title,
|
|
283
|
+
body: data.body,
|
|
284
|
+
context: data.context ? JSON.stringify(data.context) : null,
|
|
285
|
+
delivered: 0,
|
|
286
|
+
delivered_at: null,
|
|
287
|
+
delivery_channel: null,
|
|
288
|
+
dismissed: 0,
|
|
289
|
+
acted_on: 0,
|
|
290
|
+
created_at: now,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function markSuggestionDelivered(id: string, channel: string): void {
|
|
295
|
+
const db = getDb();
|
|
296
|
+
db.prepare(
|
|
297
|
+
'UPDATE awareness_suggestions SET delivered = 1, delivered_at = ?, delivery_channel = ? WHERE id = ?'
|
|
298
|
+
).run(Date.now(), channel, id);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function markSuggestionDismissed(id: string): void {
|
|
302
|
+
const db = getDb();
|
|
303
|
+
db.prepare('UPDATE awareness_suggestions SET dismissed = 1 WHERE id = ?').run(id);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function markSuggestionActedOn(id: string): void {
|
|
307
|
+
const db = getDb();
|
|
308
|
+
db.prepare('UPDATE awareness_suggestions SET acted_on = 1 WHERE id = ?').run(id);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getRecentSuggestions(limit: number = 20, type?: SuggestionType): SuggestionRow[] {
|
|
312
|
+
const db = getDb();
|
|
313
|
+
if (type) {
|
|
314
|
+
return db.prepare(
|
|
315
|
+
'SELECT * FROM awareness_suggestions WHERE type = ? ORDER BY created_at DESC LIMIT ?'
|
|
316
|
+
).all(type, limit) as SuggestionRow[];
|
|
317
|
+
}
|
|
318
|
+
return db.prepare(
|
|
319
|
+
'SELECT * FROM awareness_suggestions ORDER BY created_at DESC LIMIT ?'
|
|
320
|
+
).all(limit) as SuggestionRow[];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function getSuggestionCountSince(timestamp: number): number {
|
|
324
|
+
const db = getDb();
|
|
325
|
+
const row = db.prepare(
|
|
326
|
+
'SELECT COUNT(*) as count FROM awareness_suggestions WHERE created_at >= ?'
|
|
327
|
+
).get(timestamp) as { count: number };
|
|
328
|
+
return row.count;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export function getSuggestionStats(startTime: number, endTime: number): { total: number; actedOn: number } {
|
|
332
|
+
const db = getDb();
|
|
333
|
+
const row = db.prepare(`
|
|
334
|
+
SELECT
|
|
335
|
+
COUNT(*) as total,
|
|
336
|
+
SUM(CASE WHEN acted_on = 1 THEN 1 ELSE 0 END) as acted_on
|
|
337
|
+
FROM awareness_suggestions
|
|
338
|
+
WHERE created_at >= ? AND created_at <= ?
|
|
339
|
+
`).get(startTime, endTime) as { total: number; acted_on: number };
|
|
340
|
+
return { total: row.total, actedOn: row.acted_on ?? 0 };
|
|
341
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { getDb, generateId } from './schema.ts';
|
|
2
|
+
|
|
3
|
+
export type CommitmentPriority = 'low' | 'normal' | 'high' | 'critical';
|
|
4
|
+
export type CommitmentStatus = 'pending' | 'active' | 'completed' | 'failed' | 'escalated';
|
|
5
|
+
|
|
6
|
+
export type RetryPolicy = {
|
|
7
|
+
max_retries: number;
|
|
8
|
+
interval_ms: number;
|
|
9
|
+
escalate_after: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type Commitment = {
|
|
13
|
+
id: string;
|
|
14
|
+
what: string;
|
|
15
|
+
when_due: number | null;
|
|
16
|
+
context: string | null;
|
|
17
|
+
priority: CommitmentPriority;
|
|
18
|
+
status: CommitmentStatus;
|
|
19
|
+
retry_policy: RetryPolicy | null;
|
|
20
|
+
created_from: string | null;
|
|
21
|
+
assigned_to: string | null;
|
|
22
|
+
created_at: number;
|
|
23
|
+
completed_at: number | null;
|
|
24
|
+
result: string | null;
|
|
25
|
+
sort_order: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type CommitmentRow = {
|
|
29
|
+
id: string;
|
|
30
|
+
what: string;
|
|
31
|
+
when_due: number | null;
|
|
32
|
+
context: string | null;
|
|
33
|
+
priority: CommitmentPriority;
|
|
34
|
+
status: CommitmentStatus;
|
|
35
|
+
retry_policy: string | null;
|
|
36
|
+
created_from: string | null;
|
|
37
|
+
assigned_to: string | null;
|
|
38
|
+
created_at: number;
|
|
39
|
+
completed_at: number | null;
|
|
40
|
+
result: string | null;
|
|
41
|
+
sort_order: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse commitment row from database, deserializing JSON fields
|
|
46
|
+
*/
|
|
47
|
+
function parseCommitment(row: CommitmentRow): Commitment {
|
|
48
|
+
return {
|
|
49
|
+
...row,
|
|
50
|
+
retry_policy: row.retry_policy ? JSON.parse(row.retry_policy) : null,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a new commitment
|
|
56
|
+
*/
|
|
57
|
+
export function createCommitment(
|
|
58
|
+
what: string,
|
|
59
|
+
opts?: {
|
|
60
|
+
when_due?: number;
|
|
61
|
+
context?: string;
|
|
62
|
+
priority?: CommitmentPriority;
|
|
63
|
+
retry_policy?: RetryPolicy;
|
|
64
|
+
created_from?: string;
|
|
65
|
+
assigned_to?: string;
|
|
66
|
+
}
|
|
67
|
+
): Commitment {
|
|
68
|
+
const db = getDb();
|
|
69
|
+
const id = generateId();
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
const priority = opts?.priority ?? 'normal';
|
|
72
|
+
|
|
73
|
+
const stmt = db.prepare(
|
|
74
|
+
'INSERT INTO commitments (id, what, when_due, context, priority, status, retry_policy, created_from, assigned_to, created_at, completed_at, result) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
stmt.run(
|
|
78
|
+
id,
|
|
79
|
+
what,
|
|
80
|
+
opts?.when_due ?? null,
|
|
81
|
+
opts?.context ?? null,
|
|
82
|
+
priority,
|
|
83
|
+
'pending',
|
|
84
|
+
opts?.retry_policy ? JSON.stringify(opts.retry_policy) : null,
|
|
85
|
+
opts?.created_from ?? null,
|
|
86
|
+
opts?.assigned_to ?? null,
|
|
87
|
+
now,
|
|
88
|
+
null,
|
|
89
|
+
null
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id,
|
|
94
|
+
what,
|
|
95
|
+
when_due: opts?.when_due ?? null,
|
|
96
|
+
context: opts?.context ?? null,
|
|
97
|
+
priority,
|
|
98
|
+
status: 'pending',
|
|
99
|
+
retry_policy: opts?.retry_policy ?? null,
|
|
100
|
+
created_from: opts?.created_from ?? null,
|
|
101
|
+
assigned_to: opts?.assigned_to ?? null,
|
|
102
|
+
created_at: now,
|
|
103
|
+
completed_at: null,
|
|
104
|
+
result: null,
|
|
105
|
+
sort_order: 0,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get a commitment by ID
|
|
111
|
+
*/
|
|
112
|
+
export function getCommitment(id: string): Commitment | null {
|
|
113
|
+
const db = getDb();
|
|
114
|
+
const stmt = db.prepare('SELECT * FROM commitments WHERE id = ?');
|
|
115
|
+
const row = stmt.get(id) as CommitmentRow | null;
|
|
116
|
+
|
|
117
|
+
if (!row) return null;
|
|
118
|
+
|
|
119
|
+
return parseCommitment(row);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Find commitments matching query criteria
|
|
124
|
+
*/
|
|
125
|
+
export function findCommitments(query: {
|
|
126
|
+
status?: CommitmentStatus;
|
|
127
|
+
priority?: CommitmentPriority;
|
|
128
|
+
assigned_to?: string;
|
|
129
|
+
overdue?: boolean;
|
|
130
|
+
}): Commitment[] {
|
|
131
|
+
const db = getDb();
|
|
132
|
+
const conditions: string[] = [];
|
|
133
|
+
const params: unknown[] = [];
|
|
134
|
+
|
|
135
|
+
if (query.status) {
|
|
136
|
+
conditions.push('status = ?');
|
|
137
|
+
params.push(query.status);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (query.priority) {
|
|
141
|
+
conditions.push('priority = ?');
|
|
142
|
+
params.push(query.priority);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (query.assigned_to) {
|
|
146
|
+
conditions.push('assigned_to = ?');
|
|
147
|
+
params.push(query.assigned_to);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (query.overdue) {
|
|
151
|
+
conditions.push('when_due IS NOT NULL AND when_due <= ?');
|
|
152
|
+
params.push(Date.now());
|
|
153
|
+
conditions.push("status IN ('pending', 'active')");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
|
|
157
|
+
const stmt = db.prepare(`SELECT * FROM commitments ${where} ORDER BY sort_order ASC, created_at DESC`);
|
|
158
|
+
const rows = stmt.all(...params as any[]) as CommitmentRow[];
|
|
159
|
+
|
|
160
|
+
return rows.map(parseCommitment);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get upcoming commitments, ordered by due date
|
|
165
|
+
*/
|
|
166
|
+
export function getUpcoming(limit: number = 10): Commitment[] {
|
|
167
|
+
const db = getDb();
|
|
168
|
+
const stmt = db.prepare(
|
|
169
|
+
"SELECT * FROM commitments WHERE status IN ('pending', 'active') AND when_due IS NOT NULL ORDER BY when_due ASC LIMIT ?"
|
|
170
|
+
);
|
|
171
|
+
const rows = stmt.all(limit) as CommitmentRow[];
|
|
172
|
+
|
|
173
|
+
return rows.map(parseCommitment);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Mark a commitment as completed
|
|
178
|
+
*/
|
|
179
|
+
export function completeCommitment(id: string, result?: string): Commitment | null {
|
|
180
|
+
const db = getDb();
|
|
181
|
+
const commitment = getCommitment(id);
|
|
182
|
+
if (!commitment) return null;
|
|
183
|
+
|
|
184
|
+
const stmt = db.prepare(
|
|
185
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
186
|
+
);
|
|
187
|
+
stmt.run('completed', Date.now(), result ?? null, id);
|
|
188
|
+
|
|
189
|
+
return getCommitment(id);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Mark a commitment as failed
|
|
194
|
+
*/
|
|
195
|
+
export function failCommitment(id: string, reason?: string): Commitment | null {
|
|
196
|
+
const db = getDb();
|
|
197
|
+
const commitment = getCommitment(id);
|
|
198
|
+
if (!commitment) return null;
|
|
199
|
+
|
|
200
|
+
const stmt = db.prepare(
|
|
201
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
202
|
+
);
|
|
203
|
+
stmt.run('failed', Date.now(), reason ?? null, id);
|
|
204
|
+
|
|
205
|
+
return getCommitment(id);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Escalate a commitment
|
|
210
|
+
*/
|
|
211
|
+
export function escalateCommitment(id: string): Commitment | null {
|
|
212
|
+
const db = getDb();
|
|
213
|
+
const commitment = getCommitment(id);
|
|
214
|
+
if (!commitment) return null;
|
|
215
|
+
|
|
216
|
+
const stmt = db.prepare('UPDATE commitments SET status = ? WHERE id = ?');
|
|
217
|
+
stmt.run('escalated', id);
|
|
218
|
+
|
|
219
|
+
return getCommitment(id);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get commitments that are currently due
|
|
224
|
+
*/
|
|
225
|
+
export function getDueCommitments(): Commitment[] {
|
|
226
|
+
const db = getDb();
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
const stmt = db.prepare(
|
|
229
|
+
"SELECT * FROM commitments WHERE when_due IS NOT NULL AND when_due <= ? AND status IN ('pending', 'active') ORDER BY when_due ASC"
|
|
230
|
+
);
|
|
231
|
+
const rows = stmt.all(now) as CommitmentRow[];
|
|
232
|
+
|
|
233
|
+
return rows.map(parseCommitment);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Update a commitment's status to any valid status.
|
|
238
|
+
* Sets completed_at for terminal states (completed, failed).
|
|
239
|
+
*/
|
|
240
|
+
export function updateCommitmentStatus(
|
|
241
|
+
id: string,
|
|
242
|
+
status: CommitmentStatus,
|
|
243
|
+
result?: string
|
|
244
|
+
): Commitment | null {
|
|
245
|
+
const db = getDb();
|
|
246
|
+
const commitment = getCommitment(id);
|
|
247
|
+
if (!commitment) return null;
|
|
248
|
+
|
|
249
|
+
const isTerminal = status === 'completed' || status === 'failed';
|
|
250
|
+
const completedAt = isTerminal ? Date.now() : null;
|
|
251
|
+
|
|
252
|
+
db.prepare(
|
|
253
|
+
'UPDATE commitments SET status = ?, completed_at = ?, result = ? WHERE id = ?'
|
|
254
|
+
).run(status, completedAt, result ?? null, id);
|
|
255
|
+
|
|
256
|
+
return getCommitment(id);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Update a commitment's assigned_to field.
|
|
261
|
+
*/
|
|
262
|
+
export function updateCommitmentAssignee(id: string, assignedTo: string): Commitment | null {
|
|
263
|
+
const db = getDb();
|
|
264
|
+
const commitment = getCommitment(id);
|
|
265
|
+
if (!commitment) return null;
|
|
266
|
+
|
|
267
|
+
db.prepare('UPDATE commitments SET assigned_to = ? WHERE id = ?').run(assignedTo, id);
|
|
268
|
+
return getCommitment(id);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Update a commitment's due date.
|
|
273
|
+
*/
|
|
274
|
+
export function updateCommitmentDue(id: string, when_due: number | null): Commitment | null {
|
|
275
|
+
const db = getDb();
|
|
276
|
+
const commitment = getCommitment(id);
|
|
277
|
+
if (!commitment) return null;
|
|
278
|
+
|
|
279
|
+
db.prepare('UPDATE commitments SET when_due = ? WHERE id = ?').run(when_due, id);
|
|
280
|
+
return getCommitment(id);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Bulk update sort order for commitments (used by kanban drag & drop).
|
|
285
|
+
*/
|
|
286
|
+
export function reorderCommitments(
|
|
287
|
+
items: { id: string; sort_order: number }[]
|
|
288
|
+
): void {
|
|
289
|
+
const db = getDb();
|
|
290
|
+
const stmt = db.prepare('UPDATE commitments SET sort_order = ? WHERE id = ?');
|
|
291
|
+
|
|
292
|
+
const transaction = db.transaction(() => {
|
|
293
|
+
for (const item of items) {
|
|
294
|
+
stmt.run(item.sort_order, item.id);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
transaction();
|
|
299
|
+
}
|