@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,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daily Rhythm — Morning Plan + Evening Review
|
|
3
|
+
*
|
|
4
|
+
* Morning: queries active goals + calendar → LLM generates focus areas,
|
|
5
|
+
* daily actions, warnings (drill sergeant tone) → creates check-in.
|
|
6
|
+
* Evening: gets morning plan + day's progress → LLM scores day,
|
|
7
|
+
* generates accountability assessment → updates scores.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Goal, GoalCheckIn } from './types.ts';
|
|
11
|
+
import type { GoalEvent } from './events.ts';
|
|
12
|
+
import * as vault from '../vault/goals.ts';
|
|
13
|
+
|
|
14
|
+
export type MorningPlanResult = {
|
|
15
|
+
checkIn: GoalCheckIn;
|
|
16
|
+
focusAreas: string[];
|
|
17
|
+
dailyActions: string[];
|
|
18
|
+
warnings: string[];
|
|
19
|
+
message: string; // Drill sergeant message to the user
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type EveningReviewResult = {
|
|
23
|
+
checkIn: GoalCheckIn;
|
|
24
|
+
scoreUpdates: { goalId: string; newScore: number; reason: string }[];
|
|
25
|
+
assessment: string; // Day summary
|
|
26
|
+
message: string; // Drill sergeant verdict
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export class DailyRhythm {
|
|
30
|
+
private llmManager: any; // LLMManager
|
|
31
|
+
private eventCallback: ((event: GoalEvent) => void) | null = null;
|
|
32
|
+
private accountabilityStyle: 'drill_sergeant' | 'supportive' | 'balanced';
|
|
33
|
+
|
|
34
|
+
constructor(llmManager: unknown, style: 'drill_sergeant' | 'supportive' | 'balanced' = 'drill_sergeant') {
|
|
35
|
+
this.llmManager = llmManager;
|
|
36
|
+
this.accountabilityStyle = style;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setEventCallback(cb: (event: GoalEvent) => void): void {
|
|
40
|
+
this.eventCallback = cb;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private emit(event: GoalEvent): void {
|
|
44
|
+
if (this.eventCallback) this.eventCallback(event);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Run morning planning session.
|
|
49
|
+
*/
|
|
50
|
+
async runMorningPlan(): Promise<MorningPlanResult> {
|
|
51
|
+
const activeGoals = vault.findGoals({ status: 'active', limit: 20 });
|
|
52
|
+
const overdueGoals = vault.getOverdueGoals();
|
|
53
|
+
const yesterdayEvening = vault.getRecentCheckIns('evening_review', 1);
|
|
54
|
+
|
|
55
|
+
const goalSummary = activeGoals.map(g =>
|
|
56
|
+
`- ${g.title} (${g.level}, score: ${g.score}, health: ${g.health}, deadline: ${g.deadline ? new Date(g.deadline).toLocaleDateString() : 'none'})`
|
|
57
|
+
).join('\n');
|
|
58
|
+
|
|
59
|
+
const overdueSummary = overdueGoals.length > 0
|
|
60
|
+
? `\n\nOVERDUE GOALS:\n${overdueGoals.map(g => `- ${g.title} (due ${new Date(g.deadline!).toLocaleDateString()})`).join('\n')}`
|
|
61
|
+
: '';
|
|
62
|
+
|
|
63
|
+
const yesterdaySummary = yesterdayEvening.length > 0
|
|
64
|
+
? `\n\nYesterday's review:\n${yesterdayEvening[0]!.summary}`
|
|
65
|
+
: '';
|
|
66
|
+
|
|
67
|
+
const prompt = [
|
|
68
|
+
{ role: 'system' as const, content: this.buildMorningPrompt() },
|
|
69
|
+
{
|
|
70
|
+
role: 'user' as const,
|
|
71
|
+
content: `Active goals:\n${goalSummary}${overdueSummary}${yesterdaySummary}\n\nGenerate today's morning plan. Respond with ONLY valid JSON.`,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const response = await this.llmManager.chat(prompt, {
|
|
77
|
+
temperature: 0.4,
|
|
78
|
+
max_tokens: 2000,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
|
|
82
|
+
const json = text.match(/\{[\s\S]*\}/)?.[0];
|
|
83
|
+
const plan = json ? JSON.parse(json) : this.fallbackMorningPlan(activeGoals);
|
|
84
|
+
|
|
85
|
+
const focusAreas: string[] = plan.focus_areas ?? [];
|
|
86
|
+
const dailyActions: string[] = plan.daily_actions ?? [];
|
|
87
|
+
const warnings: string[] = plan.warnings ?? [];
|
|
88
|
+
const message: string = plan.message ?? 'Time to work.';
|
|
89
|
+
|
|
90
|
+
const goalsReviewed = activeGoals.map(g => g.id);
|
|
91
|
+
|
|
92
|
+
const checkIn = vault.createCheckIn(
|
|
93
|
+
'morning_plan',
|
|
94
|
+
`Focus: ${focusAreas.join(', ')}`,
|
|
95
|
+
goalsReviewed,
|
|
96
|
+
dailyActions,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
this.emit({
|
|
100
|
+
type: 'check_in_morning',
|
|
101
|
+
data: { checkInId: checkIn.id, focusAreas, dailyActions, warnings },
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return { checkIn, focusAreas, dailyActions, warnings, message };
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error('[DailyRhythm] Morning plan LLM error:', err);
|
|
108
|
+
const fallback = this.fallbackMorningPlan(activeGoals);
|
|
109
|
+
const checkIn = vault.createCheckIn(
|
|
110
|
+
'morning_plan',
|
|
111
|
+
'Morning plan (fallback)',
|
|
112
|
+
activeGoals.map(g => g.id),
|
|
113
|
+
fallback.daily_actions,
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
checkIn,
|
|
117
|
+
focusAreas: fallback.focus_areas,
|
|
118
|
+
dailyActions: fallback.daily_actions,
|
|
119
|
+
warnings: fallback.warnings,
|
|
120
|
+
message: fallback.message,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run evening review session.
|
|
127
|
+
*/
|
|
128
|
+
async runEveningReview(): Promise<EveningReviewResult> {
|
|
129
|
+
const activeGoals = vault.findGoals({ status: 'active', limit: 20 });
|
|
130
|
+
const morningCheckIn = vault.getTodayCheckIn('morning_plan');
|
|
131
|
+
|
|
132
|
+
const goalSummary = activeGoals.map(g =>
|
|
133
|
+
`- ${g.title} (${g.level}, score: ${g.score}, health: ${g.health})`
|
|
134
|
+
).join('\n');
|
|
135
|
+
|
|
136
|
+
const plannedActions = morningCheckIn?.actions_planned ?? [];
|
|
137
|
+
const morningContext = morningCheckIn
|
|
138
|
+
? `\nMorning plan:\n- Focus: ${morningCheckIn.summary}\n- Planned actions:\n${plannedActions.map(a => ` * ${a}`).join('\n')}`
|
|
139
|
+
: '\nNo morning plan was created today.';
|
|
140
|
+
|
|
141
|
+
const prompt = [
|
|
142
|
+
{ role: 'system' as const, content: this.buildEveningPrompt() },
|
|
143
|
+
{
|
|
144
|
+
role: 'user' as const,
|
|
145
|
+
content: `Active goals:\n${goalSummary}${morningContext}\n\nReview the day and score progress. Respond with ONLY valid JSON.`,
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const response = await this.llmManager.chat(prompt, {
|
|
151
|
+
temperature: 0.4,
|
|
152
|
+
max_tokens: 2000,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
|
|
156
|
+
const json = text.match(/\{[\s\S]*\}/)?.[0];
|
|
157
|
+
const review = json ? JSON.parse(json) : this.fallbackEveningReview();
|
|
158
|
+
|
|
159
|
+
const scoreUpdates: { goalId: string; newScore: number; reason: string }[] = review.score_updates ?? [];
|
|
160
|
+
const assessment: string = review.assessment ?? 'Day complete.';
|
|
161
|
+
const message: string = review.message ?? 'Another day done.';
|
|
162
|
+
const actionsCompleted: string[] = review.actions_completed ?? [];
|
|
163
|
+
|
|
164
|
+
// Apply score updates
|
|
165
|
+
for (const update of scoreUpdates) {
|
|
166
|
+
vault.updateGoalScore(update.goalId, update.newScore, update.reason, 'daily_review');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const goalsReviewed = activeGoals.map(g => g.id);
|
|
170
|
+
const checkIn = vault.createCheckIn(
|
|
171
|
+
'evening_review',
|
|
172
|
+
assessment,
|
|
173
|
+
goalsReviewed,
|
|
174
|
+
[],
|
|
175
|
+
actionsCompleted,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
this.emit({
|
|
179
|
+
type: 'check_in_evening',
|
|
180
|
+
data: { checkInId: checkIn.id, scoreUpdates, assessment },
|
|
181
|
+
timestamp: Date.now(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return { checkIn, scoreUpdates, assessment, message };
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error('[DailyRhythm] Evening review LLM error:', err);
|
|
187
|
+
const checkIn = vault.createCheckIn(
|
|
188
|
+
'evening_review',
|
|
189
|
+
'Evening review (fallback)',
|
|
190
|
+
activeGoals.map(g => g.id),
|
|
191
|
+
[],
|
|
192
|
+
[],
|
|
193
|
+
);
|
|
194
|
+
return {
|
|
195
|
+
checkIn,
|
|
196
|
+
scoreUpdates: [],
|
|
197
|
+
assessment: 'Review completed without LLM.',
|
|
198
|
+
message: 'Day over. Check your goals manually.',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── Prompts ──────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
private buildMorningPrompt(): string {
|
|
206
|
+
const tone = this.getToneInstructions();
|
|
207
|
+
return `You are JARVIS, an AI assistant running a morning planning session.${tone}
|
|
208
|
+
|
|
209
|
+
Analyze the user's active goals and generate today's plan.
|
|
210
|
+
|
|
211
|
+
Respond with ONLY valid JSON:
|
|
212
|
+
{
|
|
213
|
+
"focus_areas": ["top 1-3 priorities for today"],
|
|
214
|
+
"daily_actions": ["specific actionable tasks for today"],
|
|
215
|
+
"warnings": ["any urgent warnings about deadlines, health, or missed targets"],
|
|
216
|
+
"message": "motivational/accountability message to the user"
|
|
217
|
+
}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private buildEveningPrompt(): string {
|
|
221
|
+
const tone = this.getToneInstructions();
|
|
222
|
+
return `You are JARVIS, an AI assistant running an evening review session.${tone}
|
|
223
|
+
|
|
224
|
+
Compare the morning plan against the day's reality. Score progress honestly.
|
|
225
|
+
|
|
226
|
+
Respond with ONLY valid JSON:
|
|
227
|
+
{
|
|
228
|
+
"score_updates": [{ "goalId": "id", "newScore": 0.0-1.0, "reason": "why" }],
|
|
229
|
+
"actions_completed": ["what got done today"],
|
|
230
|
+
"assessment": "honest day summary",
|
|
231
|
+
"message": "accountability verdict for the user"
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
Only include score_updates for goals where you have evidence of progress or regression.`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private getToneInstructions(): string {
|
|
238
|
+
switch (this.accountabilityStyle) {
|
|
239
|
+
case 'drill_sergeant':
|
|
240
|
+
return `\n\nYour tone is DRILL SERGEANT: direct, blunt, no sugarcoating. Call out laziness. Praise only exceptional effort. Use short, punchy sentences. No pleasantries.`;
|
|
241
|
+
case 'supportive':
|
|
242
|
+
return `\n\nYour tone is SUPPORTIVE: encouraging, empathetic, focus on progress over perfection. Celebrate small wins. Gently point out areas for improvement.`;
|
|
243
|
+
case 'balanced':
|
|
244
|
+
return `\n\nYour tone is BALANCED: honest but fair. Acknowledge good work, directly address problems. Mix encouragement with accountability.`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── Fallbacks ────────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
private fallbackMorningPlan(goals: Goal[]) {
|
|
251
|
+
const overdueGoals = goals.filter(g => g.deadline && g.deadline < Date.now());
|
|
252
|
+
const behindGoals = goals.filter(g => g.health === 'behind' || g.health === 'critical');
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
focus_areas: goals.slice(0, 3).map(g => g.title),
|
|
256
|
+
daily_actions: goals.slice(0, 5).map(g => `Work on: ${g.title}`),
|
|
257
|
+
warnings: [
|
|
258
|
+
...overdueGoals.map(g => `OVERDUE: ${g.title}`),
|
|
259
|
+
...behindGoals.map(g => `BEHIND: ${g.title}`),
|
|
260
|
+
],
|
|
261
|
+
message: overdueGoals.length > 0
|
|
262
|
+
? 'You have overdue goals. Fix that today.'
|
|
263
|
+
: 'Get to work.',
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private fallbackEveningReview() {
|
|
268
|
+
return {
|
|
269
|
+
score_updates: [],
|
|
270
|
+
actions_completed: [],
|
|
271
|
+
assessment: 'Review completed without LLM analysis.',
|
|
272
|
+
message: 'Check your goals manually.',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach } from 'bun:test';
|
|
2
|
+
import { initDatabase } from '../vault/schema.ts';
|
|
3
|
+
import { GoalService } from './service.ts';
|
|
4
|
+
import type { GoalEvent } from './events.ts';
|
|
5
|
+
import type { GoalConfig } from '../config/types.ts';
|
|
6
|
+
|
|
7
|
+
const defaultConfig: GoalConfig = {
|
|
8
|
+
enabled: true,
|
|
9
|
+
morning_window: { start: 7, end: 9 },
|
|
10
|
+
evening_window: { start: 20, end: 22 },
|
|
11
|
+
accountability_style: 'drill_sergeant',
|
|
12
|
+
escalation_weeks: { pressure: 1, root_cause: 3, suggest_kill: 4 },
|
|
13
|
+
auto_decompose: true,
|
|
14
|
+
calendar_ownership: false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe('GoalService', () => {
|
|
18
|
+
let service: GoalService;
|
|
19
|
+
let events: GoalEvent[];
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
initDatabase(':memory:');
|
|
23
|
+
service = new GoalService(defaultConfig);
|
|
24
|
+
events = [];
|
|
25
|
+
service.setEventCallback((e) => events.push(e));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('start and stop lifecycle', async () => {
|
|
29
|
+
expect(service.status()).toBe('stopped');
|
|
30
|
+
await service.start();
|
|
31
|
+
expect(service.status()).toBe('running');
|
|
32
|
+
await service.stop();
|
|
33
|
+
expect(service.status()).toBe('stopped');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('disabled config skips start', async () => {
|
|
37
|
+
const disabled = new GoalService({ ...defaultConfig, enabled: false });
|
|
38
|
+
await disabled.start();
|
|
39
|
+
expect(disabled.status()).toBe('stopped');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('createGoal emits goal_created event', () => {
|
|
43
|
+
const goal = service.createGoal('Test Goal', 'objective');
|
|
44
|
+
expect(goal.title).toBe('Test Goal');
|
|
45
|
+
expect(events.length).toBe(1);
|
|
46
|
+
expect(events[0]!.type).toBe('goal_created');
|
|
47
|
+
expect(events[0]!.goalId).toBe(goal.id);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('updateGoal emits goal_updated event', () => {
|
|
51
|
+
const goal = service.createGoal('Original', 'task');
|
|
52
|
+
events = [];
|
|
53
|
+
|
|
54
|
+
const updated = service.updateGoal(goal.id, { title: 'Changed' });
|
|
55
|
+
expect(updated!.title).toBe('Changed');
|
|
56
|
+
expect(events.length).toBe(1);
|
|
57
|
+
expect(events[0]!.type).toBe('goal_updated');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('scoreGoal emits goal_scored event', () => {
|
|
61
|
+
const goal = service.createGoal('Scored', 'key_result');
|
|
62
|
+
events = [];
|
|
63
|
+
|
|
64
|
+
service.scoreGoal(goal.id, 0.5, 'halfway');
|
|
65
|
+
expect(events.length).toBe(1);
|
|
66
|
+
expect(events[0]!.type).toBe('goal_scored');
|
|
67
|
+
expect(events[0]!.data.score).toBe(0.5);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('updateStatus emits correct event type', () => {
|
|
71
|
+
const goal = service.createGoal('Status test', 'task', { status: 'active' });
|
|
72
|
+
events = [];
|
|
73
|
+
|
|
74
|
+
service.updateStatus(goal.id, 'completed');
|
|
75
|
+
expect(events.length).toBe(1);
|
|
76
|
+
expect(events[0]!.type).toBe('goal_completed');
|
|
77
|
+
|
|
78
|
+
const g2 = service.createGoal('Fail test', 'task', { status: 'active' });
|
|
79
|
+
events = [];
|
|
80
|
+
service.updateStatus(g2.id, 'failed');
|
|
81
|
+
expect(events[0]!.type).toBe('goal_failed');
|
|
82
|
+
|
|
83
|
+
const g3 = service.createGoal('Kill test', 'task', { status: 'active' });
|
|
84
|
+
events = [];
|
|
85
|
+
service.updateStatus(g3.id, 'killed');
|
|
86
|
+
expect(events[0]!.type).toBe('goal_killed');
|
|
87
|
+
|
|
88
|
+
const g4 = service.createGoal('Pause test', 'task', { status: 'active' });
|
|
89
|
+
events = [];
|
|
90
|
+
service.updateStatus(g4.id, 'paused');
|
|
91
|
+
expect(events[0]!.type).toBe('goal_status_changed');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('updateHealth emits goal_health_changed event', () => {
|
|
95
|
+
const goal = service.createGoal('Health test', 'objective');
|
|
96
|
+
events = [];
|
|
97
|
+
|
|
98
|
+
service.updateHealth(goal.id, 'at_risk');
|
|
99
|
+
expect(events.length).toBe(1);
|
|
100
|
+
expect(events[0]!.type).toBe('goal_health_changed');
|
|
101
|
+
expect(events[0]!.data.health).toBe('at_risk');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('deleteGoal emits goal_deleted event', () => {
|
|
105
|
+
const goal = service.createGoal('Delete me', 'task');
|
|
106
|
+
events = [];
|
|
107
|
+
|
|
108
|
+
service.deleteGoal(goal.id);
|
|
109
|
+
expect(events.length).toBe(1);
|
|
110
|
+
expect(events[0]!.type).toBe('goal_deleted');
|
|
111
|
+
expect(events[0]!.goalId).toBe(goal.id);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('getGoal returns null for non-existent', () => {
|
|
115
|
+
expect(service.getGoal('nope')).toBeNull();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('getMetrics returns aggregated data', () => {
|
|
119
|
+
service.createGoal('Active', 'task', { status: 'active' });
|
|
120
|
+
service.createGoal('Draft', 'task');
|
|
121
|
+
|
|
122
|
+
const metrics = service.getMetrics();
|
|
123
|
+
expect(metrics.total).toBe(2);
|
|
124
|
+
expect(metrics.active).toBe(1);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('no events when update returns null', () => {
|
|
128
|
+
events = [];
|
|
129
|
+
service.updateGoal('nonexistent', { title: 'X' });
|
|
130
|
+
service.scoreGoal('nonexistent', 0.5, 'nope');
|
|
131
|
+
service.updateHealth('nonexistent', 'behind');
|
|
132
|
+
service.deleteGoal('nonexistent');
|
|
133
|
+
expect(events.length).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
});
|