@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,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accountability Engine — Drill Sergeant Escalation
|
|
3
|
+
*
|
|
4
|
+
* Staged escalation for goals that are behind or at risk:
|
|
5
|
+
* - Week 1-2: Pressure — daily blunt reminders
|
|
6
|
+
* - Week 3: Root Cause — LLM analyzes why the goal is failing
|
|
7
|
+
* - Week 4+: Suggest Kill — recommend killing or replanning the goal
|
|
8
|
+
*
|
|
9
|
+
* Also generates replan options when goals need course correction.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Goal, EscalationStage } from './types.ts';
|
|
13
|
+
import * as vault from '../vault/goals.ts';
|
|
14
|
+
|
|
15
|
+
export type EscalationAction = {
|
|
16
|
+
goalId: string;
|
|
17
|
+
goalTitle: string;
|
|
18
|
+
currentStage: EscalationStage;
|
|
19
|
+
newStage: EscalationStage;
|
|
20
|
+
message: string;
|
|
21
|
+
weeksBehind: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ReplanOption = {
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
description: string;
|
|
28
|
+
impact: 'low' | 'medium' | 'high';
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type ReplanAnalysis = {
|
|
32
|
+
options: ReplanOption[];
|
|
33
|
+
analysis: string;
|
|
34
|
+
recommendation: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export class AccountabilityEngine {
|
|
38
|
+
private llmManager: any; // LLMManager
|
|
39
|
+
private accountabilityStyle: 'drill_sergeant' | 'supportive' | 'balanced';
|
|
40
|
+
private escalationWeeks: { pressure: number; root_cause: number; suggest_kill: number };
|
|
41
|
+
|
|
42
|
+
constructor(
|
|
43
|
+
llmManager: unknown,
|
|
44
|
+
style: 'drill_sergeant' | 'supportive' | 'balanced' = 'drill_sergeant',
|
|
45
|
+
escalationWeeks = { pressure: 1, root_cause: 3, suggest_kill: 4 },
|
|
46
|
+
) {
|
|
47
|
+
this.llmManager = llmManager;
|
|
48
|
+
this.accountabilityStyle = style;
|
|
49
|
+
this.escalationWeeks = escalationWeeks;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check all active goals and return escalation actions needed.
|
|
54
|
+
*/
|
|
55
|
+
runEscalationCheck(): EscalationAction[] {
|
|
56
|
+
const needingEscalation = vault.getGoalsNeedingEscalation();
|
|
57
|
+
const actions: EscalationAction[] = [];
|
|
58
|
+
|
|
59
|
+
for (const goal of needingEscalation) {
|
|
60
|
+
const action = this.evaluateEscalation(goal);
|
|
61
|
+
if (action) actions.push(action);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return actions;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate an escalation message for a goal at a given stage.
|
|
69
|
+
*/
|
|
70
|
+
async generateEscalationMessage(goal: Goal, stage: EscalationStage): Promise<string> {
|
|
71
|
+
if (stage === 'none') return '';
|
|
72
|
+
|
|
73
|
+
const history = vault.getProgressHistory(goal.id, 10);
|
|
74
|
+
const progressContext = history.length > 0
|
|
75
|
+
? `\nProgress history:\n${history.map(h => ` ${new Date(h.created_at).toLocaleDateString()}: ${h.score_before} → ${h.score_after} (${h.note})`).join('\n')}`
|
|
76
|
+
: '';
|
|
77
|
+
|
|
78
|
+
const prompt = [
|
|
79
|
+
{
|
|
80
|
+
role: 'system' as const,
|
|
81
|
+
content: this.getEscalationSystemPrompt(stage),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
role: 'user' as const,
|
|
85
|
+
content: `Goal: ${goal.title}\nLevel: ${goal.level}\nScore: ${goal.score}\nHealth: ${goal.health}\nDeadline: ${goal.deadline ? new Date(goal.deadline).toLocaleDateString() : 'none'}\nStarted: ${goal.started_at ? new Date(goal.started_at).toLocaleDateString() : 'unknown'}${progressContext}\n\nGenerate the escalation message.`,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const response = await this.llmManager.chat(prompt, {
|
|
91
|
+
temperature: 0.5,
|
|
92
|
+
max_tokens: 500,
|
|
93
|
+
});
|
|
94
|
+
return typeof response.content === 'string' ? response.content : String(response.content);
|
|
95
|
+
} catch {
|
|
96
|
+
return this.getFallbackMessage(goal, stage);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Generate replan options for a struggling goal.
|
|
102
|
+
*/
|
|
103
|
+
async generateReplanOptions(goal: Goal): Promise<ReplanAnalysis> {
|
|
104
|
+
const children = vault.getGoalChildren(goal.id);
|
|
105
|
+
const history = vault.getProgressHistory(goal.id, 20);
|
|
106
|
+
|
|
107
|
+
const childContext = children.length > 0
|
|
108
|
+
? `\nChild goals:\n${children.map(c => `- ${c.title} (${c.level}, score: ${c.score}, status: ${c.status})`).join('\n')}`
|
|
109
|
+
: '';
|
|
110
|
+
|
|
111
|
+
const historyContext = history.length > 0
|
|
112
|
+
? `\nRecent progress:\n${history.slice(0, 5).map(h => ` ${h.score_before} → ${h.score_after}: ${h.note}`).join('\n')}`
|
|
113
|
+
: '';
|
|
114
|
+
|
|
115
|
+
const prompt = [
|
|
116
|
+
{
|
|
117
|
+
role: 'system' as const,
|
|
118
|
+
content: `You are an OKR coach analyzing a struggling goal. Generate replan options.
|
|
119
|
+
|
|
120
|
+
Respond with ONLY valid JSON:
|
|
121
|
+
{
|
|
122
|
+
"options": [
|
|
123
|
+
{ "id": "unique_id", "label": "short label", "description": "what this option means", "impact": "low|medium|high" }
|
|
124
|
+
],
|
|
125
|
+
"analysis": "why the goal is struggling",
|
|
126
|
+
"recommendation": "which option you recommend and why"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
Always include these standard options:
|
|
130
|
+
- Extend deadline
|
|
131
|
+
- Reduce scope
|
|
132
|
+
- Change approach
|
|
133
|
+
- Kill the goal
|
|
134
|
+
Plus 1-2 context-specific options.`,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
role: 'user' as const,
|
|
138
|
+
content: `Goal: ${goal.title}\nDescription: ${goal.description}\nSuccess criteria: ${goal.success_criteria}\nScore: ${goal.score}\nHealth: ${goal.health}\nDeadline: ${goal.deadline ? new Date(goal.deadline).toLocaleDateString() : 'none'}${childContext}${historyContext}`,
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const response = await this.llmManager.chat(prompt, {
|
|
144
|
+
temperature: 0.3,
|
|
145
|
+
max_tokens: 1500,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
|
|
149
|
+
const json = text.match(/\{[\s\S]*\}/)?.[0];
|
|
150
|
+
if (!json) throw new Error('No JSON in response');
|
|
151
|
+
|
|
152
|
+
return JSON.parse(json) as ReplanAnalysis;
|
|
153
|
+
} catch {
|
|
154
|
+
return this.getFallbackReplanOptions(goal);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Private ──────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
private evaluateEscalation(goal: Goal): EscalationAction | null {
|
|
161
|
+
const startedAt = goal.escalation_started_at ?? goal.updated_at;
|
|
162
|
+
const weeksBehind = (Date.now() - startedAt) / (7 * 24 * 60 * 60 * 1000);
|
|
163
|
+
|
|
164
|
+
let newStage: EscalationStage | null = null;
|
|
165
|
+
|
|
166
|
+
if (goal.escalation_stage === 'none' && weeksBehind >= this.escalationWeeks.pressure) {
|
|
167
|
+
newStage = 'pressure';
|
|
168
|
+
} else if (goal.escalation_stage === 'pressure') {
|
|
169
|
+
const escalationStart = goal.escalation_started_at ?? goal.updated_at;
|
|
170
|
+
const weeksSinceEscalation = (Date.now() - escalationStart) / (7 * 24 * 60 * 60 * 1000);
|
|
171
|
+
if (weeksSinceEscalation >= this.escalationWeeks.root_cause) {
|
|
172
|
+
newStage = 'root_cause';
|
|
173
|
+
}
|
|
174
|
+
} else if (goal.escalation_stage === 'root_cause') {
|
|
175
|
+
const escalationStart = goal.escalation_started_at ?? goal.updated_at;
|
|
176
|
+
const weeksSinceEscalation = (Date.now() - escalationStart) / (7 * 24 * 60 * 60 * 1000);
|
|
177
|
+
if (weeksSinceEscalation >= this.escalationWeeks.suggest_kill) {
|
|
178
|
+
newStage = 'suggest_kill';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!newStage) return null;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
goalId: goal.id,
|
|
186
|
+
goalTitle: goal.title,
|
|
187
|
+
currentStage: goal.escalation_stage,
|
|
188
|
+
newStage,
|
|
189
|
+
message: this.getFallbackMessage(goal, newStage),
|
|
190
|
+
weeksBehind: Math.round(weeksBehind * 10) / 10,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private getEscalationSystemPrompt(stage: EscalationStage): string {
|
|
195
|
+
const tone = this.accountabilityStyle === 'drill_sergeant'
|
|
196
|
+
? 'Be brutally honest. No sugarcoating. Short, punchy sentences.'
|
|
197
|
+
: this.accountabilityStyle === 'supportive'
|
|
198
|
+
? 'Be encouraging but honest about the situation.'
|
|
199
|
+
: 'Be direct but fair.';
|
|
200
|
+
|
|
201
|
+
switch (stage) {
|
|
202
|
+
case 'pressure':
|
|
203
|
+
return `You are generating a PRESSURE escalation message for a goal that's falling behind. ${tone} Focus on urgency and the consequences of continued inaction. Keep it under 100 words.`;
|
|
204
|
+
case 'root_cause':
|
|
205
|
+
return `You are generating a ROOT CAUSE analysis for a failing goal. ${tone} Identify why this goal is stalling and what needs to change. Keep it under 150 words.`;
|
|
206
|
+
case 'suggest_kill':
|
|
207
|
+
return `You are generating a KILL SUGGESTION for a goal that has been failing for too long. ${tone} Make the case for either killing the goal or doing a major replan. Be direct about sunk cost. Keep it under 150 words.`;
|
|
208
|
+
default:
|
|
209
|
+
return '';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private getFallbackMessage(goal: Goal, stage: EscalationStage): string {
|
|
214
|
+
switch (stage) {
|
|
215
|
+
case 'pressure':
|
|
216
|
+
return `"${goal.title}" is behind schedule. Score: ${goal.score}. ${goal.deadline ? `Deadline: ${new Date(goal.deadline).toLocaleDateString()}.` : ''} Stop making excuses and deliver.`;
|
|
217
|
+
case 'root_cause':
|
|
218
|
+
return `"${goal.title}" has been behind for weeks. Score: ${goal.score}. Time to figure out what's actually wrong. Is the goal realistic? Are you avoiding something? Fix it or kill it.`;
|
|
219
|
+
case 'suggest_kill':
|
|
220
|
+
return `"${goal.title}" has been failing for over a month. Score: ${goal.score}. Consider killing this goal. Sunk cost is sunk. Either commit to a radical replan or move on.`;
|
|
221
|
+
default:
|
|
222
|
+
return '';
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private getFallbackReplanOptions(goal: Goal): ReplanAnalysis {
|
|
227
|
+
return {
|
|
228
|
+
options: [
|
|
229
|
+
{ id: 'extend', label: 'Extend deadline', description: 'Push the deadline back 2-4 weeks', impact: 'low' },
|
|
230
|
+
{ id: 'reduce', label: 'Reduce scope', description: 'Cut non-essential success criteria', impact: 'medium' },
|
|
231
|
+
{ id: 'pivot', label: 'Change approach', description: 'Try a fundamentally different strategy', impact: 'high' },
|
|
232
|
+
{ id: 'kill', label: 'Kill the goal', description: 'Accept the loss and reallocate effort', impact: 'high' },
|
|
233
|
+
],
|
|
234
|
+
analysis: `"${goal.title}" has a score of ${goal.score} and is ${goal.health}. The current approach isn't working.`,
|
|
235
|
+
recommendation: goal.score < 0.2
|
|
236
|
+
? 'Consider killing this goal or making a major pivot.'
|
|
237
|
+
: 'Extend the deadline and reduce scope to focus on the most impactful parts.',
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Awareness → Goals Bridge
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to awareness events and fuzzy-matches detected context
|
|
5
|
+
* (apps, windows, files, text) against active goal descriptions.
|
|
6
|
+
* Auto-logs progress entries as 'auto_detected' when matches are found.
|
|
7
|
+
* Feeds the evening review with detected activity.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Goal } from './types.ts';
|
|
11
|
+
import * as vault from '../vault/goals.ts';
|
|
12
|
+
|
|
13
|
+
export type AwarenessGoalMatch = {
|
|
14
|
+
goalId: string;
|
|
15
|
+
goalTitle: string;
|
|
16
|
+
matchScore: number;
|
|
17
|
+
matchedTerms: string[];
|
|
18
|
+
source: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Process an awareness event and check if it relates to any active goals.
|
|
23
|
+
* Returns any matches found so the caller can log them.
|
|
24
|
+
*/
|
|
25
|
+
export function matchAwarenessToGoals(
|
|
26
|
+
eventData: Record<string, unknown>,
|
|
27
|
+
): AwarenessGoalMatch[] {
|
|
28
|
+
const activeGoals = vault.findGoals({ status: 'active' });
|
|
29
|
+
if (activeGoals.length === 0) return [];
|
|
30
|
+
|
|
31
|
+
// Extract searchable text from awareness event
|
|
32
|
+
const eventText = extractEventText(eventData);
|
|
33
|
+
if (!eventText) return [];
|
|
34
|
+
|
|
35
|
+
const eventWords = tokenize(eventText);
|
|
36
|
+
if (eventWords.length === 0) return [];
|
|
37
|
+
|
|
38
|
+
const matches: AwarenessGoalMatch[] = [];
|
|
39
|
+
|
|
40
|
+
for (const goal of activeGoals) {
|
|
41
|
+
const goalWords = tokenize(`${goal.title} ${goal.description} ${goal.success_criteria}`);
|
|
42
|
+
if (goalWords.length === 0) continue;
|
|
43
|
+
|
|
44
|
+
const { score, matched } = fuzzyMatch(eventWords, goalWords);
|
|
45
|
+
|
|
46
|
+
// Threshold: require at least 2 matching terms and 0.15 score
|
|
47
|
+
if (score >= 0.15 && matched.length >= 2) {
|
|
48
|
+
matches.push({
|
|
49
|
+
goalId: goal.id,
|
|
50
|
+
goalTitle: goal.title,
|
|
51
|
+
matchScore: score,
|
|
52
|
+
matchedTerms: matched,
|
|
53
|
+
source: String(eventData.app_name ?? eventData.window_title ?? 'awareness'),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Sort by match score descending
|
|
59
|
+
matches.sort((a, b) => b.matchScore - a.matchScore);
|
|
60
|
+
|
|
61
|
+
return matches;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Log auto-detected progress for matched goals.
|
|
66
|
+
* Only logs if the goal hasn't had a recent auto-detection (within 30 min).
|
|
67
|
+
*/
|
|
68
|
+
export function logAutoDetectedProgress(
|
|
69
|
+
matches: AwarenessGoalMatch[],
|
|
70
|
+
eventType: string,
|
|
71
|
+
): void {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
const thirtyMinutes = 30 * 60 * 1000;
|
|
74
|
+
|
|
75
|
+
for (const match of matches) {
|
|
76
|
+
// Check for recent auto-detection to avoid spam
|
|
77
|
+
const recentProgress = vault.getProgressHistory(match.goalId, 5);
|
|
78
|
+
const hasRecentAutoDetect = recentProgress.some(
|
|
79
|
+
p => p.type === 'auto_detected' && (now - p.created_at) < thirtyMinutes
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (hasRecentAutoDetect) continue;
|
|
83
|
+
|
|
84
|
+
const goal = vault.getGoal(match.goalId);
|
|
85
|
+
if (!goal) continue;
|
|
86
|
+
|
|
87
|
+
// Log progress entry (no score change, just detection)
|
|
88
|
+
vault.addProgressEntry(
|
|
89
|
+
match.goalId,
|
|
90
|
+
'auto_detected',
|
|
91
|
+
goal.score,
|
|
92
|
+
goal.score, // no automatic score change
|
|
93
|
+
`Activity detected: ${eventType} via ${match.source} (matched: ${match.matchedTerms.join(', ')})`,
|
|
94
|
+
'awareness',
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extract searchable text from an awareness event's data payload.
|
|
101
|
+
*/
|
|
102
|
+
function extractEventText(data: Record<string, unknown>): string {
|
|
103
|
+
const parts: string[] = [];
|
|
104
|
+
|
|
105
|
+
// Common awareness event fields
|
|
106
|
+
if (data.app_name) parts.push(String(data.app_name));
|
|
107
|
+
if (data.window_title) parts.push(String(data.window_title));
|
|
108
|
+
if (data.ocr_text) parts.push(String(data.ocr_text));
|
|
109
|
+
if (data.file_path) parts.push(String(data.file_path));
|
|
110
|
+
if (data.url) parts.push(String(data.url));
|
|
111
|
+
if (data.title) parts.push(String(data.title));
|
|
112
|
+
if (data.body) parts.push(String(data.body));
|
|
113
|
+
if (data.description) parts.push(String(data.description));
|
|
114
|
+
if (data.context) parts.push(String(data.context));
|
|
115
|
+
|
|
116
|
+
// Session data
|
|
117
|
+
if (data.dominant_app) parts.push(String(data.dominant_app));
|
|
118
|
+
if (data.summary) parts.push(String(data.summary));
|
|
119
|
+
if (data.activities && Array.isArray(data.activities)) {
|
|
120
|
+
for (const a of data.activities) {
|
|
121
|
+
if (typeof a === 'string') parts.push(a);
|
|
122
|
+
else if (a && typeof a === 'object' && 'description' in a) parts.push(String(a.description));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return parts.join(' ');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Common words to filter from matching
|
|
130
|
+
const STOP_WORDS = new Set([
|
|
131
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
132
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
133
|
+
'should', 'may', 'might', 'must', 'shall', 'can', 'to', 'of', 'in',
|
|
134
|
+
'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into', 'about',
|
|
135
|
+
'and', 'but', 'or', 'not', 'no', 'if', 'than', 'so', 'up', 'out',
|
|
136
|
+
'that', 'this', 'it', 'its', 'my', 'your', 'his', 'her', 'our',
|
|
137
|
+
'all', 'each', 'every', 'both', 'few', 'more', 'most', 'some',
|
|
138
|
+
'new', 'file', 'window', 'app', 'application', 'open', 'close',
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Tokenize text into meaningful words (lowercase, filtered).
|
|
143
|
+
*/
|
|
144
|
+
function tokenize(text: string): string[] {
|
|
145
|
+
return text
|
|
146
|
+
.toLowerCase()
|
|
147
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
148
|
+
.filter(w => w.length >= 3 && !STOP_WORDS.has(w));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fuzzy match: check how many of the event words overlap with goal words.
|
|
153
|
+
* Returns a score (0-1) and the matched terms.
|
|
154
|
+
*/
|
|
155
|
+
function fuzzyMatch(
|
|
156
|
+
eventWords: string[],
|
|
157
|
+
goalWords: string[],
|
|
158
|
+
): { score: number; matched: string[] } {
|
|
159
|
+
const goalSet = new Set(goalWords);
|
|
160
|
+
const matched: string[] = [];
|
|
161
|
+
|
|
162
|
+
for (const word of eventWords) {
|
|
163
|
+
if (goalSet.has(word)) {
|
|
164
|
+
matched.push(word);
|
|
165
|
+
} else {
|
|
166
|
+
// Partial match: check if event word is substring of any goal word or vice versa
|
|
167
|
+
for (const gw of goalSet) {
|
|
168
|
+
if (gw.length >= 4 && word.length >= 4) {
|
|
169
|
+
if (gw.includes(word) || word.includes(gw)) {
|
|
170
|
+
matched.push(word);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Deduplicate
|
|
179
|
+
const uniqueMatched = [...new Set(matched)];
|
|
180
|
+
|
|
181
|
+
// Score: proportion of goal words that were matched
|
|
182
|
+
const score = goalWords.length > 0 ? uniqueMatched.length / goalWords.length : 0;
|
|
183
|
+
|
|
184
|
+
return { score, matched: uniqueMatched };
|
|
185
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal Estimator — Hybrid LLM + historical estimation
|
|
3
|
+
*
|
|
4
|
+
* Blends LLM estimates with personal historical data from completed goals.
|
|
5
|
+
* 60/40 split (history/LLM) when history is available, 100% LLM otherwise.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { GoalEstimate, GoalLevel } from './types.ts';
|
|
9
|
+
import * as vault from '../vault/goals.ts';
|
|
10
|
+
|
|
11
|
+
export class GoalEstimator {
|
|
12
|
+
private llmManager: any; // LLMManager
|
|
13
|
+
|
|
14
|
+
constructor(llmManager: unknown) {
|
|
15
|
+
this.llmManager = llmManager;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Estimate hours for a goal by combining LLM judgment with historical data.
|
|
20
|
+
*/
|
|
21
|
+
async estimate(goalId: string): Promise<GoalEstimate | null> {
|
|
22
|
+
const goal = vault.getGoal(goalId);
|
|
23
|
+
if (!goal) return null;
|
|
24
|
+
|
|
25
|
+
// Find similar completed goals
|
|
26
|
+
const similarGoals = this.findSimilarCompleted(goal.title, goal.level, goal.description);
|
|
27
|
+
|
|
28
|
+
// Get LLM estimate
|
|
29
|
+
const llmEstimate = await this.getLLMEstimate(goal.title, goal.level, goal.description, goal.success_criteria);
|
|
30
|
+
|
|
31
|
+
// Calculate historical estimate
|
|
32
|
+
const historicalEstimate = this.calculateHistoricalEstimate(similarGoals);
|
|
33
|
+
|
|
34
|
+
// Blend estimates
|
|
35
|
+
let finalEstimate: number;
|
|
36
|
+
let confidence: number;
|
|
37
|
+
let reasoning: string;
|
|
38
|
+
|
|
39
|
+
if (historicalEstimate !== null && similarGoals.length >= 2) {
|
|
40
|
+
// 60% history, 40% LLM when we have good historical data
|
|
41
|
+
finalEstimate = historicalEstimate * 0.6 + llmEstimate.hours * 0.4;
|
|
42
|
+
confidence = Math.min(0.9, 0.5 + similarGoals.length * 0.1);
|
|
43
|
+
reasoning = `Blended estimate: ${similarGoals.length} similar past goals averaged ${historicalEstimate.toFixed(1)}h (actual), LLM estimates ${llmEstimate.hours.toFixed(1)}h. ${llmEstimate.reasoning}`;
|
|
44
|
+
} else if (historicalEstimate !== null) {
|
|
45
|
+
// Some history, less weight
|
|
46
|
+
finalEstimate = historicalEstimate * 0.4 + llmEstimate.hours * 0.6;
|
|
47
|
+
confidence = 0.4;
|
|
48
|
+
reasoning = `Limited history (${similarGoals.length} similar goal${similarGoals.length === 1 ? '' : 's'}): ${historicalEstimate.toFixed(1)}h actual. LLM estimates ${llmEstimate.hours.toFixed(1)}h. ${llmEstimate.reasoning}`;
|
|
49
|
+
} else {
|
|
50
|
+
// No history, pure LLM
|
|
51
|
+
finalEstimate = llmEstimate.hours;
|
|
52
|
+
confidence = Math.min(0.5, llmEstimate.confidence);
|
|
53
|
+
reasoning = `No historical data. LLM estimate: ${llmEstimate.reasoning}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
llm_estimate_hours: llmEstimate.hours,
|
|
58
|
+
historical_estimate_hours: historicalEstimate,
|
|
59
|
+
final_estimate_hours: Math.round(finalEstimate * 10) / 10,
|
|
60
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
61
|
+
reasoning,
|
|
62
|
+
similar_past_goals: similarGoals.map(g => g.id),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Quick estimate without LLM — uses only historical data and heuristics.
|
|
68
|
+
*/
|
|
69
|
+
quickEstimate(title: string, level: GoalLevel): { hours: number; confidence: number } {
|
|
70
|
+
const similar = this.findSimilarCompleted(title, level);
|
|
71
|
+
const historical = this.calculateHistoricalEstimate(similar);
|
|
72
|
+
|
|
73
|
+
if (historical !== null && similar.length >= 2) {
|
|
74
|
+
return { hours: Math.round(historical * 10) / 10, confidence: 0.6 };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Heuristic fallback based on level
|
|
78
|
+
const levelDefaults: Record<GoalLevel, number> = {
|
|
79
|
+
objective: 200,
|
|
80
|
+
key_result: 40,
|
|
81
|
+
milestone: 20,
|
|
82
|
+
task: 4,
|
|
83
|
+
daily_action: 1,
|
|
84
|
+
};
|
|
85
|
+
return { hours: levelDefaults[level], confidence: 0.2 };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Private ──────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
private findSimilarCompleted(title: string, level: GoalLevel, description?: string): ReturnType<typeof vault.findGoals> {
|
|
91
|
+
// Find completed goals at the same level
|
|
92
|
+
const completed = vault.findGoals({ status: 'completed', level, limit: 50 });
|
|
93
|
+
|
|
94
|
+
// Score similarity using simple word overlap
|
|
95
|
+
const titleWords = new Set(title.toLowerCase().split(/\s+/).filter(w => w.length > 2));
|
|
96
|
+
const descWords = description
|
|
97
|
+
? new Set(description.toLowerCase().split(/\s+/).filter(w => w.length > 2))
|
|
98
|
+
: new Set<string>();
|
|
99
|
+
|
|
100
|
+
const scored = completed
|
|
101
|
+
.map(goal => {
|
|
102
|
+
const goalWords = new Set([
|
|
103
|
+
...goal.title.toLowerCase().split(/\s+/).filter(w => w.length > 2),
|
|
104
|
+
...goal.description.toLowerCase().split(/\s+/).filter(w => w.length > 2),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
let overlap = 0;
|
|
108
|
+
for (const word of titleWords) {
|
|
109
|
+
if (goalWords.has(word)) overlap += 2; // Title matches worth more
|
|
110
|
+
}
|
|
111
|
+
for (const word of descWords) {
|
|
112
|
+
if (goalWords.has(word)) overlap += 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return { goal, score: overlap };
|
|
116
|
+
})
|
|
117
|
+
.filter(({ score }) => score > 0)
|
|
118
|
+
.sort((a, b) => b.score - a.score)
|
|
119
|
+
.slice(0, 5);
|
|
120
|
+
|
|
121
|
+
return scored.map(s => s.goal);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private calculateHistoricalEstimate(goals: ReturnType<typeof vault.findGoals>): number | null {
|
|
125
|
+
const withHours = goals.filter(g => g.actual_hours > 0);
|
|
126
|
+
if (withHours.length === 0) return null;
|
|
127
|
+
|
|
128
|
+
const total = withHours.reduce((sum, g) => sum + g.actual_hours, 0);
|
|
129
|
+
return total / withHours.length;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async getLLMEstimate(
|
|
133
|
+
title: string,
|
|
134
|
+
level: GoalLevel,
|
|
135
|
+
description: string,
|
|
136
|
+
successCriteria: string,
|
|
137
|
+
): Promise<{ hours: number; confidence: number; reasoning: string }> {
|
|
138
|
+
const prompt = [
|
|
139
|
+
{
|
|
140
|
+
role: 'system' as const,
|
|
141
|
+
content: `You are a project estimation expert. Estimate the total hours needed to complete a goal.
|
|
142
|
+
|
|
143
|
+
Consider:
|
|
144
|
+
- Goal level: objective (large), key_result (medium), milestone (small), task (tiny), daily_action (1-2h)
|
|
145
|
+
- Include planning, execution, review, and buffer time
|
|
146
|
+
- Be realistic, not optimistic
|
|
147
|
+
|
|
148
|
+
Respond with ONLY valid JSON: { "hours": number, "confidence": number (0-1), "reasoning": "brief explanation" }`,
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
role: 'user' as const,
|
|
152
|
+
content: `Estimate hours for:\nLevel: ${level}\nTitle: ${title}\nDescription: ${description}\nSuccess Criteria: ${successCriteria}`,
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const response = await this.llmManager.chat(prompt, {
|
|
158
|
+
temperature: 0.2,
|
|
159
|
+
max_tokens: 500,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const text = typeof response.content === 'string' ? response.content : JSON.stringify(response.content);
|
|
163
|
+
const json = text.match(/\{[\s\S]*\}/)?.[0];
|
|
164
|
+
if (!json) throw new Error('No JSON in response');
|
|
165
|
+
|
|
166
|
+
const parsed = JSON.parse(json);
|
|
167
|
+
return {
|
|
168
|
+
hours: Math.max(0.5, Number(parsed.hours) || 4),
|
|
169
|
+
confidence: Math.max(0, Math.min(1, Number(parsed.confidence) || 0.3)),
|
|
170
|
+
reasoning: String(parsed.reasoning || 'LLM estimate'),
|
|
171
|
+
};
|
|
172
|
+
} catch (err) {
|
|
173
|
+
console.error('[GoalEstimator] LLM estimate failed:', err instanceof Error ? err.message : err);
|
|
174
|
+
// Fallback heuristic
|
|
175
|
+
const defaults: Record<GoalLevel, number> = {
|
|
176
|
+
objective: 200, key_result: 40, milestone: 20, task: 4, daily_action: 1,
|
|
177
|
+
};
|
|
178
|
+
return {
|
|
179
|
+
hours: defaults[level],
|
|
180
|
+
confidence: 0.1,
|
|
181
|
+
reasoning: 'Fallback heuristic (LLM unavailable)',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal Event Types for M16 — Autonomous Goal Pursuit
|
|
3
|
+
*
|
|
4
|
+
* Events emitted by GoalService and broadcast via WebSocket.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type GoalEventType =
|
|
8
|
+
| 'goal_created'
|
|
9
|
+
| 'goal_updated'
|
|
10
|
+
| 'goal_scored'
|
|
11
|
+
| 'goal_status_changed'
|
|
12
|
+
| 'goal_completed'
|
|
13
|
+
| 'goal_failed'
|
|
14
|
+
| 'goal_killed'
|
|
15
|
+
| 'goal_health_changed'
|
|
16
|
+
| 'goal_escalated'
|
|
17
|
+
| 'goal_deleted'
|
|
18
|
+
| 'check_in_morning'
|
|
19
|
+
| 'check_in_evening'
|
|
20
|
+
| 'daily_actions_generated'
|
|
21
|
+
| 'replan_triggered';
|
|
22
|
+
|
|
23
|
+
export type GoalEvent = {
|
|
24
|
+
type: GoalEventType;
|
|
25
|
+
goalId?: string;
|
|
26
|
+
data: Record<string, unknown>;
|
|
27
|
+
timestamp: number;
|
|
28
|
+
};
|