nexo-brain 5.3.19 → 5.3.21
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/.claude-plugin/plugin.json +1 -1
- package/bin/nexo-brain.js +52 -10
- package/package.json +1 -1
- package/src/auto_update.py +11 -8
- package/src/dashboard/static/favicon 2.svg +32 -0
- package/src/dashboard/static/nexo-logo 2.png +0 -0
- package/src/dashboard/static/nexo-logo 2.svg +40 -0
- package/src/dashboard/static/style 2.css +2458 -0
- package/src/dashboard/templates/adaptive 2.html +118 -0
- package/src/dashboard/templates/artifacts 2.html +133 -0
- package/src/dashboard/templates/backups 2.html +136 -0
- package/src/dashboard/templates/base 2.html +417 -0
- package/src/dashboard/templates/calendar 2.html +591 -0
- package/src/dashboard/templates/chat 2.html +356 -0
- package/src/dashboard/templates/claims 2.html +259 -0
- package/src/dashboard/templates/cortex 2.html +321 -0
- package/src/dashboard/templates/credentials 2.html +128 -0
- package/src/dashboard/templates/crons 2.html +370 -0
- package/src/dashboard/templates/dashboard 2.html +494 -0
- package/src/dashboard/templates/dreams 2.html +252 -0
- package/src/dashboard/templates/email 2.html +160 -0
- package/src/dashboard/templates/evolution 2.html +189 -0
- package/src/dashboard/templates/feed 2.html +249 -0
- package/src/dashboard/templates/followup_health 2.html +170 -0
- package/src/dashboard/templates/graph 2.html +201 -0
- package/src/dashboard/templates/guard 2.html +259 -0
- package/src/dashboard/templates/inbox 2.html +251 -0
- package/src/dashboard/templates/memory 2.html +420 -0
- package/src/dashboard/templates/operations 2.html +608 -0
- package/src/dashboard/templates/plugins 2.html +185 -0
- package/src/dashboard/templates/protocol 2.html +199 -0
- package/src/dashboard/templates/rules 2.html +246 -0
- package/src/dashboard/templates/sentiment 2.html +247 -0
- package/src/dashboard/templates/sessions 2.html +218 -0
- package/src/dashboard/templates/skills 2.html +329 -0
- package/src/dashboard/templates/somatic 2.html +73 -0
- package/src/dashboard/templates/triggers 2.html +133 -0
- package/src/dashboard/templates/trust 2.html +360 -0
- package/src/db/__init__ 2.py +259 -0
- package/src/db/_core 2.py +437 -0
- package/src/db/_credentials 2.py +124 -0
- package/src/db/_episodic 2.py +762 -0
- package/src/db/_evolution 2.py +54 -0
- package/src/db/_fts 2.py +406 -0
- package/src/db/_goal_profiles 2.py +376 -0
- package/src/db/_hot_context 2.py +660 -0
- package/src/db/_outcomes 2.py +800 -0
- package/src/db/_personal_scripts 2.py +582 -0
- package/src/db/_sessions 2.py +330 -0
- package/src/db/_tasks 2.py +91 -0
- package/src/db/_watchers 2.py +173 -0
- package/src/doctor/formatters 2.py +52 -0
- package/src/doctor/models 2.py +69 -0
- package/src/doctor/planes 2.py +87 -0
- package/src/doctor/providers/__init__ 2.py +1 -0
- package/src/doctor/providers/deep 2.py +367 -0
- package/src/evolution_cycle 2.py +519 -0
- package/src/hooks/auto_capture 2.py +208 -0
- package/src/hooks/caffeinate-guard 2.sh +8 -0
- package/src/hooks/capture-session 2.sh +21 -0
- package/src/hooks/capture-tool-logs 2.sh +158 -0
- package/src/hooks/daily-briefing-check 2.sh +33 -0
- package/src/hooks/heartbeat-enforcement 2.py +90 -0
- package/src/hooks/heartbeat-posttool 2.sh +18 -0
- package/src/hooks/inbox-hook 2.sh +76 -0
- package/src/hooks/post-compact 2.sh +152 -0
- package/src/hooks/pre-compact 2.sh +169 -0
- package/src/hooks/protocol-guardrail 2.sh +10 -0
- package/src/hooks/protocol-pretool-guardrail 2.sh +9 -0
- package/src/hooks/session-stop 2.sh +52 -0
- package/src/kg_populate 2.py +292 -0
- package/src/maintenance 2.py +53 -0
- package/src/memory_backends 2.py +71 -0
- package/src/migrate_embeddings 2.py +124 -0
- package/src/nexo_sdk 2.py +103 -0
- package/src/observability 2.py +199 -0
- package/src/plugin_loader 2.py +217 -0
- package/src/plugins/__init__ 2.py +0 -0
- package/src/plugins/artifact_registry 2.py +450 -0
- package/src/plugins/backup 2.py +127 -0
- package/src/plugins/claims_tools 2.py +119 -0
- package/src/plugins/cognitive_memory 2.py +609 -0
- package/src/plugins/core_rules 2.py +252 -0
- package/src/plugins/cortex 2.py +1155 -0
- package/src/plugins/entities 2.py +67 -0
- package/src/plugins/episodic_memory 2.py +560 -0
- package/src/plugins/evolution 2.py +167 -0
- package/src/plugins/goal_engine 2.py +142 -0
- package/src/plugins/guard 2.py +862 -0
- package/src/plugins/impact 2.py +29 -0
- package/src/plugins/knowledge_graph_tools 2.py +137 -0
- package/src/plugins/media_memory_tools 2.py +98 -0
- package/src/plugins/memory_export 2.py +196 -0
- package/src/plugins/outcomes 2.py +130 -0
- package/src/plugins/personal_scripts 2.py +117 -0
- package/src/plugins/preferences 2.py +47 -0
- package/src/plugins/protocol 2.py +1449 -0
- package/src/plugins/simple_api 2.py +106 -0
- package/src/plugins/skills 2.py +341 -0
- package/src/plugins/state_watchers 2.py +79 -0
- package/src/plugins/update 2.py +986 -0
- package/src/plugins/user_state_tools 2.py +43 -0
- package/src/plugins/workflow 2.py +588 -0
- package/src/protocol_settings 2.py +59 -0
- package/src/public_contribution 2.py +466 -0
- package/src/public_evolution_queue 2.py +241 -0
- package/src/requirements 2.txt +14 -0
- package/src/retroactive_learnings 2.py +373 -0
- package/src/rules/__init__ 2.py +0 -0
- package/src/rules/core-rules 2.json +331 -0
- package/src/rules/migrate 2.py +207 -0
- package/src/runtime_power 2.py +874 -0
- package/src/script_registry 2.py +1559 -0
- package/src/scripts/check-context 2.py +272 -0
- package/src/scripts/deep-sleep/apply_findings 2.py +2327 -0
- package/src/scripts/deep-sleep/collect 2.py +928 -0
- package/src/scripts/deep-sleep/extract 2.py +330 -0
- package/src/scripts/deep-sleep/extract-prompt 2.md +285 -0
- package/src/scripts/deep-sleep/synthesize 2.py +312 -0
- package/src/scripts/deep-sleep/synthesize-prompt 2.md +336 -0
- package/src/scripts/nexo-agent-run 2.py +75 -0
- package/src/scripts/nexo-auto-update 2.py +6 -0
- package/src/scripts/nexo-backup 2.sh +25 -0
- package/src/scripts/nexo-brain-activation 2.sh +140 -0
- package/src/scripts/nexo-catchup 2.py +300 -0
- package/src/scripts/nexo-cognitive-decay 2.py +257 -0
- package/src/scripts/nexo-cortex-cycle 2.py +293 -0
- package/src/scripts/nexo-cron-wrapper 2.sh +53 -0
- package/src/scripts/nexo-daily-self-audit 2.py +2161 -0
- package/src/scripts/nexo-dashboard 2.sh +29 -0
- package/src/scripts/nexo-deep-sleep 2.sh +86 -0
- package/src/scripts/nexo-evolution-run 2.py +1664 -0
- package/src/scripts/nexo-followup-hygiene 2.py +139 -0
- package/src/scripts/nexo-hook-record 2.py +42 -0
- package/src/scripts/nexo-immune 2.py +936 -0
- package/src/scripts/nexo-impact-scorer 2.py +117 -0
- package/src/scripts/nexo-inbox-hook 2.sh +74 -0
- package/src/scripts/nexo-install 2.py +6 -0
- package/src/scripts/nexo-learning-housekeep 2.py +401 -0
- package/src/scripts/nexo-learning-validator 2.py +266 -0
- package/src/scripts/nexo-migrate 2.py +260 -0
- package/src/scripts/nexo-outcome-checker 2.py +127 -0
- package/src/scripts/nexo-postmortem-consolidator 2.py +456 -0
- package/src/scripts/nexo-pre-commit 2.py +120 -0
- package/src/scripts/nexo-prevent-sleep 2.sh +35 -0
- package/src/scripts/nexo-proactive-dashboard 2.py +354 -0
- package/src/scripts/nexo-reflection 2.py +256 -0
- package/src/scripts/nexo-runtime-preflight 2.py +274 -0
- package/src/scripts/nexo-sleep 2.py +631 -0
- package/src/scripts/nexo-snapshot-restore 2.sh +35 -0
- package/src/scripts/nexo-sync-clients 2.py +16 -0
- package/src/scripts/nexo-synthesis 2.py +475 -0
- package/src/scripts/nexo-tcc-approve 2.sh +79 -0
- package/src/scripts/nexo-update 2.sh +306 -0
- package/src/scripts/nexo-watchdog 2.sh +1207 -0
- package/src/scripts/nexo-watchdog-smoke 2.py +119 -0
- package/src/scripts/rehydrate_learnings_from_archive 2.py +245 -0
- package/src/server 2.py +1296 -0
- package/src/skills/run-nexo-audit-phase/guide 2.md +43 -0
- package/src/skills/run-nexo-audit-phase/skill 2.json +59 -0
- package/src/skills/run-nexo-core-fix-cycle/guide 2.md +17 -0
- package/src/skills/run-nexo-core-fix-cycle/script 2.py +276 -0
- package/src/skills/run-nexo-core-fix-cycle/skill 2.json +58 -0
- package/src/skills/run-release-final-audit/guide 2.md +16 -0
- package/src/skills/run-release-final-audit/script 2.py +259 -0
- package/src/skills/run-release-final-audit/skill 2.json +77 -0
- package/src/skills/run-runtime-doctor/guide 2.md +12 -0
- package/src/skills/run-runtime-doctor/script 2.py +21 -0
- package/src/skills/run-runtime-doctor/skill 2.json +25 -0
- package/src/skills_runtime 2.py +932 -0
- package/src/state_watchers_runtime 2.py +475 -0
- package/src/storage_router 2.py +32 -0
- package/src/system_catalog 2.py +786 -0
- package/src/tools_coordination 2.py +103 -0
- package/src/tools_credentials 2.py +68 -0
- package/src/tools_drive 2.py +487 -0
- package/src/tools_hot_context 2.py +163 -0
- package/src/tools_learnings 2.py +612 -0
- package/src/tools_menu 2.py +229 -0
- package/src/tools_reminders 2.py +88 -0
- package/src/tools_reminders_crud 2.py +363 -0
- package/src/tools_sessions 2.py +1054 -0
- package/src/tools_system_catalog 2.py +19 -0
- package/src/tools_task_history 2.py +57 -0
- package/src/tools_transcripts 2.py +98 -0
- package/src/transcript_utils 2.py +412 -0
- package/src/user_context 2.py +46 -0
- package/src/user_data_portability 2.py +328 -0
- package/src/user_state_model 2.py +170 -0
- package/templates/CLAUDE.md 2.template +108 -0
- package/templates/CODEX.AGENTS.md 2.template +66 -0
- package/templates/launchagents/README 2.md +132 -0
- package/templates/launchagents/com.nexo.auto-close-sessions 2.plist +39 -0
- package/templates/launchagents/com.nexo.catchup 2.plist +39 -0
- package/templates/launchagents/com.nexo.cognitive-decay 2.plist +40 -0
- package/templates/launchagents/com.nexo.dashboard 2.plist +43 -0
- package/templates/launchagents/com.nexo.deep-sleep 2.plist +43 -0
- package/templates/launchagents/com.nexo.evolution 2.plist +44 -0
- package/templates/launchagents/com.nexo.followup-hygiene 2.plist +45 -0
- package/templates/launchagents/com.nexo.immune 2.plist +41 -0
- package/templates/launchagents/com.nexo.postmortem 2.plist +45 -0
- package/templates/launchagents/com.nexo.self-audit 2.plist +47 -0
- package/templates/launchagents/com.nexo.synthesis 2.plist +45 -0
- package/templates/launchagents/com.nexo.watchdog 2.plist +37 -0
- package/templates/nexo_helper 2.py +301 -0
- package/templates/openclaw 2.json +13 -0
- package/templates/plugin-template 2.py +40 -0
- package/templates/script-template 2.py +59 -0
- package/templates/script-template 2.sh +13 -0
- package/templates/skill-script-template 2.py +48 -0
- package/templates/skill-template 2.md +33 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""Goal Engine v1 — explicit optimization profiles for durable goals and decisions."""
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from db._core import get_db
|
|
7
|
+
from db._workflow import get_workflow_goal
|
|
8
|
+
|
|
9
|
+
VALID_SCOPE_TYPES = {"default", "area", "task_type", "goal_id"}
|
|
10
|
+
VALID_STATUSES = {"active", "disabled"}
|
|
11
|
+
WEIGHT_KEYS = ("impact", "success", "risk", "somatic")
|
|
12
|
+
DEFAULT_WEIGHTS = {
|
|
13
|
+
"impact": 0.35,
|
|
14
|
+
"success": 0.30,
|
|
15
|
+
"risk": 0.20,
|
|
16
|
+
"somatic": 0.15,
|
|
17
|
+
}
|
|
18
|
+
DEFAULT_GOAL_PROFILES = (
|
|
19
|
+
{
|
|
20
|
+
"profile_id": "default_balanced",
|
|
21
|
+
"profile_name": "Balanced default",
|
|
22
|
+
"description": "Balancea impacto, exito, riesgo y huella somatica para decisiones generales.",
|
|
23
|
+
"scope_type": "default",
|
|
24
|
+
"scope_value": "",
|
|
25
|
+
"goal_labels": ["maximise_success", "minimise_risk", "preserve_trust"],
|
|
26
|
+
"weights": DEFAULT_WEIGHTS,
|
|
27
|
+
"status": "active",
|
|
28
|
+
"source": "system",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"profile_id": "release_safety",
|
|
32
|
+
"profile_name": "Release safety",
|
|
33
|
+
"description": "Favorece decisiones reversibles y verificadas en release, deploy y cambios publicos.",
|
|
34
|
+
"scope_type": "area",
|
|
35
|
+
"scope_value": "release",
|
|
36
|
+
"goal_labels": ["minimise_risk", "preserve_trust", "maximise_success"],
|
|
37
|
+
"weights": {
|
|
38
|
+
"impact": 0.24,
|
|
39
|
+
"success": 0.28,
|
|
40
|
+
"risk": 0.30,
|
|
41
|
+
"somatic": 0.18,
|
|
42
|
+
},
|
|
43
|
+
"status": "active",
|
|
44
|
+
"source": "system",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"profile_id": "customer_trust",
|
|
48
|
+
"profile_name": "Customer trust",
|
|
49
|
+
"description": "Favorece decisiones que preservan confianza y reducen friccion con clientes.",
|
|
50
|
+
"scope_type": "area",
|
|
51
|
+
"scope_value": "customer",
|
|
52
|
+
"goal_labels": ["preserve_trust", "maximise_success", "minimise_risk"],
|
|
53
|
+
"weights": {
|
|
54
|
+
"impact": 0.25,
|
|
55
|
+
"success": 0.31,
|
|
56
|
+
"risk": 0.26,
|
|
57
|
+
"somatic": 0.18,
|
|
58
|
+
},
|
|
59
|
+
"status": "active",
|
|
60
|
+
"source": "system",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"profile_id": "ops_efficiency",
|
|
64
|
+
"profile_name": "Operations efficiency",
|
|
65
|
+
"description": "Favorece throughput operativo manteniendo riesgo contenido en ejecucion rutinaria.",
|
|
66
|
+
"scope_type": "task_type",
|
|
67
|
+
"scope_value": "execute",
|
|
68
|
+
"goal_labels": ["maximise_efficiency", "maximise_success", "minimise_risk"],
|
|
69
|
+
"weights": {
|
|
70
|
+
"impact": 0.38,
|
|
71
|
+
"success": 0.28,
|
|
72
|
+
"risk": 0.20,
|
|
73
|
+
"somatic": 0.14,
|
|
74
|
+
},
|
|
75
|
+
"status": "active",
|
|
76
|
+
"source": "system",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"profile_id": "business_growth",
|
|
80
|
+
"profile_name": "Business growth",
|
|
81
|
+
"description": "Da mas peso a impacto y exito cuando el contexto busca crecimiento o revenue.",
|
|
82
|
+
"scope_type": "area",
|
|
83
|
+
"scope_value": "business",
|
|
84
|
+
"goal_labels": ["maximise_business_impact", "maximise_success"],
|
|
85
|
+
"weights": {
|
|
86
|
+
"impact": 0.56,
|
|
87
|
+
"success": 0.22,
|
|
88
|
+
"risk": 0.14,
|
|
89
|
+
"somatic": 0.08,
|
|
90
|
+
},
|
|
91
|
+
"status": "active",
|
|
92
|
+
"source": "system",
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _parse_json(value, default):
|
|
98
|
+
if value in (None, ""):
|
|
99
|
+
return default
|
|
100
|
+
if isinstance(value, (dict, list)):
|
|
101
|
+
return value
|
|
102
|
+
try:
|
|
103
|
+
return json.loads(value)
|
|
104
|
+
except Exception:
|
|
105
|
+
return default
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _normalize_goal_labels(labels) -> list[str]:
|
|
109
|
+
parsed = _parse_json(labels, labels if isinstance(labels, list) else [])
|
|
110
|
+
if not isinstance(parsed, list):
|
|
111
|
+
return []
|
|
112
|
+
seen: set[str] = set()
|
|
113
|
+
result: list[str] = []
|
|
114
|
+
for item in parsed:
|
|
115
|
+
clean = str(item or "").strip()
|
|
116
|
+
if not clean or clean in seen:
|
|
117
|
+
continue
|
|
118
|
+
seen.add(clean)
|
|
119
|
+
result.append(clean)
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _normalize_weights(weights) -> dict:
|
|
124
|
+
parsed = _parse_json(weights, weights if isinstance(weights, dict) else {})
|
|
125
|
+
if not isinstance(parsed, dict):
|
|
126
|
+
parsed = {}
|
|
127
|
+
collected: dict[str, float] = {}
|
|
128
|
+
for key in WEIGHT_KEYS:
|
|
129
|
+
try:
|
|
130
|
+
value = float(parsed.get(key, DEFAULT_WEIGHTS[key]))
|
|
131
|
+
except (TypeError, ValueError):
|
|
132
|
+
value = DEFAULT_WEIGHTS[key]
|
|
133
|
+
collected[key] = max(0.01, value)
|
|
134
|
+
total = sum(collected.values())
|
|
135
|
+
if total <= 0:
|
|
136
|
+
collected = dict(DEFAULT_WEIGHTS)
|
|
137
|
+
total = sum(collected.values())
|
|
138
|
+
return {key: round(collected[key] / total, 4) for key in WEIGHT_KEYS}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _row_to_goal_profile(row, *, resolved_by: str = "") -> dict | None:
|
|
142
|
+
if not row:
|
|
143
|
+
return None
|
|
144
|
+
profile = dict(row)
|
|
145
|
+
profile["goal_labels"] = _normalize_goal_labels(profile.get("goal_labels"))
|
|
146
|
+
profile["weights"] = _normalize_weights(profile.get("weights"))
|
|
147
|
+
if resolved_by:
|
|
148
|
+
profile["resolved_by"] = resolved_by
|
|
149
|
+
return profile
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def ensure_default_goal_profiles() -> None:
|
|
153
|
+
conn = get_db()
|
|
154
|
+
for profile in DEFAULT_GOAL_PROFILES:
|
|
155
|
+
conn.execute(
|
|
156
|
+
"""INSERT OR IGNORE INTO goal_profiles (
|
|
157
|
+
profile_id, profile_name, description, scope_type, scope_value,
|
|
158
|
+
goal_labels, weights, status, source
|
|
159
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
160
|
+
(
|
|
161
|
+
profile["profile_id"],
|
|
162
|
+
profile["profile_name"],
|
|
163
|
+
profile["description"],
|
|
164
|
+
profile["scope_type"],
|
|
165
|
+
profile["scope_value"],
|
|
166
|
+
json.dumps(profile["goal_labels"], ensure_ascii=False),
|
|
167
|
+
json.dumps(_normalize_weights(profile["weights"]), ensure_ascii=False),
|
|
168
|
+
profile["status"],
|
|
169
|
+
profile["source"],
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
conn.commit()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def get_goal_profile(profile_id: str) -> dict | None:
|
|
176
|
+
ensure_default_goal_profiles()
|
|
177
|
+
conn = get_db()
|
|
178
|
+
row = conn.execute(
|
|
179
|
+
"SELECT * FROM goal_profiles WHERE profile_id = ?",
|
|
180
|
+
((profile_id or "").strip(),),
|
|
181
|
+
).fetchone()
|
|
182
|
+
return _row_to_goal_profile(row)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def list_goal_profiles(*, scope_type: str = "", status: str = "active", limit: int = 50) -> list[dict]:
|
|
186
|
+
ensure_default_goal_profiles()
|
|
187
|
+
conn = get_db()
|
|
188
|
+
clauses = []
|
|
189
|
+
params: list[object] = []
|
|
190
|
+
clean_scope = (scope_type or "").strip()
|
|
191
|
+
clean_status = (status or "").strip()
|
|
192
|
+
if clean_scope:
|
|
193
|
+
clauses.append("scope_type = ?")
|
|
194
|
+
params.append(clean_scope)
|
|
195
|
+
if clean_status:
|
|
196
|
+
clauses.append("status = ?")
|
|
197
|
+
params.append(clean_status)
|
|
198
|
+
where = f"WHERE {' AND '.join(clauses)}" if clauses else ""
|
|
199
|
+
rows = conn.execute(
|
|
200
|
+
f"""SELECT * FROM goal_profiles
|
|
201
|
+
{where}
|
|
202
|
+
ORDER BY
|
|
203
|
+
CASE scope_type
|
|
204
|
+
WHEN 'default' THEN 0
|
|
205
|
+
WHEN 'area' THEN 1
|
|
206
|
+
WHEN 'task_type' THEN 2
|
|
207
|
+
WHEN 'goal_id' THEN 3
|
|
208
|
+
ELSE 9
|
|
209
|
+
END,
|
|
210
|
+
profile_id ASC
|
|
211
|
+
LIMIT ?""",
|
|
212
|
+
params + [max(1, int(limit))],
|
|
213
|
+
).fetchall()
|
|
214
|
+
return [_row_to_goal_profile(row) for row in rows if row]
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def upsert_goal_profile(
|
|
218
|
+
*,
|
|
219
|
+
profile_id: str,
|
|
220
|
+
profile_name: str = "",
|
|
221
|
+
description: str = "",
|
|
222
|
+
scope_type: str = "default",
|
|
223
|
+
scope_value: str = "",
|
|
224
|
+
goal_labels=None,
|
|
225
|
+
weights=None,
|
|
226
|
+
status: str = "active",
|
|
227
|
+
source: str = "manual",
|
|
228
|
+
) -> dict:
|
|
229
|
+
ensure_default_goal_profiles()
|
|
230
|
+
clean_id = (profile_id or "").strip()
|
|
231
|
+
if not clean_id:
|
|
232
|
+
raise ValueError("profile_id is required")
|
|
233
|
+
clean_scope = (scope_type or "default").strip()
|
|
234
|
+
if clean_scope not in VALID_SCOPE_TYPES:
|
|
235
|
+
raise ValueError(f"scope_type must be one of: {', '.join(sorted(VALID_SCOPE_TYPES))}")
|
|
236
|
+
clean_status = (status or "active").strip().lower()
|
|
237
|
+
if clean_status not in VALID_STATUSES:
|
|
238
|
+
raise ValueError(f"status must be one of: {', '.join(sorted(VALID_STATUSES))}")
|
|
239
|
+
|
|
240
|
+
normalized_weights = _normalize_weights(weights)
|
|
241
|
+
normalized_labels = _normalize_goal_labels(goal_labels)
|
|
242
|
+
conn = get_db()
|
|
243
|
+
existing = conn.execute(
|
|
244
|
+
"SELECT * FROM goal_profiles WHERE profile_id = ?",
|
|
245
|
+
(clean_id,),
|
|
246
|
+
).fetchone()
|
|
247
|
+
if existing:
|
|
248
|
+
current = dict(existing)
|
|
249
|
+
conn.execute(
|
|
250
|
+
"""UPDATE goal_profiles
|
|
251
|
+
SET profile_name = ?,
|
|
252
|
+
description = ?,
|
|
253
|
+
scope_type = ?,
|
|
254
|
+
scope_value = ?,
|
|
255
|
+
goal_labels = ?,
|
|
256
|
+
weights = ?,
|
|
257
|
+
status = ?,
|
|
258
|
+
source = ?,
|
|
259
|
+
updated_at = datetime('now')
|
|
260
|
+
WHERE profile_id = ?""",
|
|
261
|
+
(
|
|
262
|
+
(profile_name or current.get("profile_name") or clean_id).strip(),
|
|
263
|
+
(description or current.get("description") or "").strip(),
|
|
264
|
+
clean_scope,
|
|
265
|
+
(scope_value or current.get("scope_value") or "").strip().lower(),
|
|
266
|
+
json.dumps(normalized_labels or _normalize_goal_labels(current.get("goal_labels")), ensure_ascii=False),
|
|
267
|
+
json.dumps(normalized_weights, ensure_ascii=False),
|
|
268
|
+
clean_status,
|
|
269
|
+
(source or current.get("source") or "manual").strip(),
|
|
270
|
+
clean_id,
|
|
271
|
+
),
|
|
272
|
+
)
|
|
273
|
+
else:
|
|
274
|
+
conn.execute(
|
|
275
|
+
"""INSERT INTO goal_profiles (
|
|
276
|
+
profile_id, profile_name, description, scope_type, scope_value,
|
|
277
|
+
goal_labels, weights, status, source
|
|
278
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
|
279
|
+
(
|
|
280
|
+
clean_id,
|
|
281
|
+
(profile_name or clean_id).strip(),
|
|
282
|
+
(description or "").strip(),
|
|
283
|
+
clean_scope,
|
|
284
|
+
(scope_value or "").strip().lower(),
|
|
285
|
+
json.dumps(normalized_labels, ensure_ascii=False),
|
|
286
|
+
json.dumps(normalized_weights, ensure_ascii=False),
|
|
287
|
+
clean_status,
|
|
288
|
+
(source or "manual").strip(),
|
|
289
|
+
),
|
|
290
|
+
)
|
|
291
|
+
conn.commit()
|
|
292
|
+
return get_goal_profile(clean_id) or {}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def resolve_goal_profile(
|
|
296
|
+
*,
|
|
297
|
+
profile_id: str = "",
|
|
298
|
+
area: str = "",
|
|
299
|
+
task_type: str = "",
|
|
300
|
+
goal_id: str = "",
|
|
301
|
+
) -> dict:
|
|
302
|
+
ensure_default_goal_profiles()
|
|
303
|
+
conn = get_db()
|
|
304
|
+
explicit_id = (profile_id or "").strip()
|
|
305
|
+
if explicit_id:
|
|
306
|
+
explicit = get_goal_profile(explicit_id)
|
|
307
|
+
if not explicit:
|
|
308
|
+
raise ValueError(f"Unknown goal profile: {explicit_id}")
|
|
309
|
+
if explicit.get("status") != "active":
|
|
310
|
+
raise ValueError(f"Goal profile {explicit_id} is not active")
|
|
311
|
+
explicit["resolved_by"] = "explicit"
|
|
312
|
+
return explicit
|
|
313
|
+
|
|
314
|
+
clean_goal_id = (goal_id or "").strip()
|
|
315
|
+
if clean_goal_id:
|
|
316
|
+
workflow_goal = get_workflow_goal(clean_goal_id)
|
|
317
|
+
if workflow_goal:
|
|
318
|
+
shared_state = workflow_goal.get("shared_state") or {}
|
|
319
|
+
shared_profile_id = str(shared_state.get("goal_profile_id", "")).strip()
|
|
320
|
+
if shared_profile_id:
|
|
321
|
+
linked = get_goal_profile(shared_profile_id)
|
|
322
|
+
if linked and linked.get("status") == "active":
|
|
323
|
+
linked["resolved_by"] = "workflow_goal.shared_state"
|
|
324
|
+
return linked
|
|
325
|
+
row = conn.execute(
|
|
326
|
+
"""SELECT * FROM goal_profiles
|
|
327
|
+
WHERE scope_type = 'goal_id' AND scope_value = ? AND status = 'active'
|
|
328
|
+
ORDER BY updated_at DESC, profile_id ASC
|
|
329
|
+
LIMIT 1""",
|
|
330
|
+
(clean_goal_id,),
|
|
331
|
+
).fetchone()
|
|
332
|
+
if row:
|
|
333
|
+
return _row_to_goal_profile(row, resolved_by="goal_id") or {}
|
|
334
|
+
|
|
335
|
+
clean_area = (area or "").strip().lower()
|
|
336
|
+
if clean_area:
|
|
337
|
+
row = conn.execute(
|
|
338
|
+
"""SELECT * FROM goal_profiles
|
|
339
|
+
WHERE scope_type = 'area' AND scope_value = ? AND status = 'active'
|
|
340
|
+
ORDER BY updated_at DESC, profile_id ASC
|
|
341
|
+
LIMIT 1""",
|
|
342
|
+
(clean_area,),
|
|
343
|
+
).fetchone()
|
|
344
|
+
if row:
|
|
345
|
+
return _row_to_goal_profile(row, resolved_by="area") or {}
|
|
346
|
+
|
|
347
|
+
clean_type = (task_type or "").strip().lower()
|
|
348
|
+
if clean_type:
|
|
349
|
+
row = conn.execute(
|
|
350
|
+
"""SELECT * FROM goal_profiles
|
|
351
|
+
WHERE scope_type = 'task_type' AND scope_value = ? AND status = 'active'
|
|
352
|
+
ORDER BY updated_at DESC, profile_id ASC
|
|
353
|
+
LIMIT 1""",
|
|
354
|
+
(clean_type,),
|
|
355
|
+
).fetchone()
|
|
356
|
+
if row:
|
|
357
|
+
return _row_to_goal_profile(row, resolved_by="task_type") or {}
|
|
358
|
+
|
|
359
|
+
row = conn.execute(
|
|
360
|
+
"""SELECT * FROM goal_profiles
|
|
361
|
+
WHERE scope_type = 'default' AND status = 'active'
|
|
362
|
+
ORDER BY updated_at DESC, profile_id ASC
|
|
363
|
+
LIMIT 1"""
|
|
364
|
+
).fetchone()
|
|
365
|
+
return _row_to_goal_profile(row, resolved_by="default") or {
|
|
366
|
+
"profile_id": "default_balanced",
|
|
367
|
+
"profile_name": "Balanced default",
|
|
368
|
+
"description": DEFAULT_GOAL_PROFILES[0]["description"],
|
|
369
|
+
"scope_type": "default",
|
|
370
|
+
"scope_value": "",
|
|
371
|
+
"goal_labels": list(DEFAULT_GOAL_PROFILES[0]["goal_labels"]),
|
|
372
|
+
"weights": dict(DEFAULT_WEIGHTS),
|
|
373
|
+
"status": "active",
|
|
374
|
+
"source": "system",
|
|
375
|
+
"resolved_by": "fallback_default",
|
|
376
|
+
}
|