@meridiona/meridian-darwin-arm64 1.63.0 → 1.65.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/VERSION +1 -1
- package/bin/meridian +0 -0
- package/bin/meridian-tray +0 -0
- package/package.json +1 -1
- package/scripts/install-from-bundle.sh +42 -160
- package/scripts/meridian-cli.sh +11 -5
- package/scripts/meridian-npm-setup.sh +15 -32
- package/services/README.md +6 -12
- package/services/agents/README.md +10 -16
- package/services/agents/_state.py +34 -0
- package/services/agents/agno_viewer.py +60 -0
- package/services/agents/config.py +8 -0
- package/services/agents/mlx_classifier.py +195 -0
- package/services/agents/observability.py +48 -2
- package/services/agents/pm_worklog_update/config.py +3 -3
- package/services/agents/prompts/__init__.py +0 -0
- package/services/agents/prompts/activity_report.py +71 -0
- package/services/agents/prompts/coding_agent_session_summary.py +28 -0
- package/services/agents/reranker.py +118 -0
- package/services/agents/routes/__init__.py +6 -0
- package/services/agents/routes/activity.py +162 -0
- package/services/agents/routes/chat.py +175 -0
- package/services/agents/routes/distill.py +103 -0
- package/services/agents/routes/health.py +41 -0
- package/services/agents/routes/prefetch.py +138 -0
- package/services/agents/routes/rerank.py +79 -0
- package/services/agents/routes/summarise.py +131 -0
- package/services/agents/routes/worklog.py +103 -0
- package/services/agents/server.py +58 -904
- package/services/agents/session_distiller.py +537 -0
- package/services/agents/worklog_pipeline/__init__.py +11 -0
- package/services/agents/worklog_pipeline/agent_io.py +75 -0
- package/services/agents/worklog_pipeline/db.py +277 -0
- package/services/agents/worklog_pipeline/match.py +145 -0
- package/services/agents/worklog_pipeline/models.py +98 -0
- package/services/agents/worklog_pipeline/pipeline.py +418 -0
- package/services/agents/worklog_pipeline/prompts/__init__.py +1 -0
- package/services/agents/worklog_pipeline/prompts/match_tasks.py +38 -0
- package/services/agents/worklog_pipeline/prompts/propose_ticket.py +19 -0
- package/services/agents/worklog_pipeline/prompts/worklog.py +20 -0
- package/services/agents/worklog_pipeline/workflow.py +217 -0
- package/services/agents/worklog_pipeline/worklog.py +41 -0
- package/services/pyproject.toml +1 -1
- package/services/scripts/com.meridiona.mlx-server.plist +3 -3
- package/services/scripts/count_classifier_tokens.py +1 -1
- package/services/scripts/install-mlx-server-daemon.sh +3 -3
- package/services/tests/evals/README.md +7 -7
- package/services/tests/evals/configs/baseline_a_meridian.json +3 -3
- package/services/tests/evals/configs/baseline_b_generic.json +3 -3
- package/services/tests/evals/configs/extract_then_classify_b_generic.json +3 -3
- package/services/uv.lock +1 -1
- package/bin/node-runtime.meta +0 -2
- package/scripts/com.meridiona.ui.plist +0 -62
- package/scripts/install-ui-daemon.sh +0 -95
- package/scripts/ui-start.sh +0 -68
- package/scripts/uninstall-ui-daemon.sh +0 -22
- package/services/agents/_parser.py +0 -187
- package/services/agents/_prompts.py +0 -238
- package/services/agents/_system_context.py +0 -42
- package/services/agents/llm_selector.py +0 -728
- package/services/agents/run_task_linker_mlx.py +0 -1837
- package/services/agents/tests/test_llm_selector.py +0 -183
- package/services/agents/tests/test_parser.py +0 -134
- package/services/agents/tests/test_run_task_linker_mlx.py +0 -1127
- package/services/observability/dashboards/classifier-debug.json +0 -187
- package/services/observability/dashboards/pm-worklog-debug.json +0 -140
- package/services/skills/activity/task-classifier/FEEDBACK.json +0 -2656
- package/services/skills/activity/task-classifier/SKILL.applefm.md +0 -77
- package/services/skills/activity/task-classifier/SKILL.md +0 -136
- package/services/tests/conftest.py +0 -7
- package/services/tests/evals/build_dataset.py +0 -178
- package/services/tests/evals/build_real_goldens.py +0 -180
- package/services/tests/evals/classify_session.py +0 -126
- package/services/tests/evals/compare_pipeline.py +0 -262
- package/services/tests/evals/conftest.py +0 -5
- package/services/tests/evals/data/labels/real_2026-05-28.json +0 -1172
- package/services/tests/evals/data/labels/real_curated.json +0 -166
- package/services/tests/evals/data/labels/real_curated_holdout.json +0 -97
- package/services/tests/evals/data/labels/real_curated_holdout2.json +0 -64
- package/services/tests/evals/data/seeds/sessions_a_meridian.json +0 -1757
- package/services/tests/evals/data/seeds/sessions_b_generic.json +0 -1319
- package/services/tests/evals/data/seeds/tickets_generic.json +0 -72
- package/services/tests/evals/data/seeds/tickets_meridian.json +0 -87
- package/services/tests/evals/eval_classifier.py +0 -660
- package/services/tests/evals/metrics.py +0 -260
- package/services/tests/evals/render_seeds.py +0 -143
- package/services/tests/evals/strategies.py +0 -655
- package/services/tests/evals/test_classifier.py +0 -343
- package/services/tests/evals/test_model_sweep.py +0 -197
- package/services/tests/test_continuity_context.py +0 -202
- package/services/tests/test_fetch_pm_tasks.py +0 -120
- package/services/tests/test_format_candidates.py +0 -45
- package/services/tests/test_llm_selector.py +0 -223
- package/services/tests/test_prompt_cache_equivalence.py +0 -97
- package/ui.tar.gz +0 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.65.0
|
package/bin/meridian
CHANGED
|
Binary file
|
package/bin/meridian-tray
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meridiona/meridian-darwin-arm64",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.65.0",
|
|
4
4
|
"description": "Prebuilt Meridian app for macOS arm64 (daemon binary + dashboard + Python services). Installed via @meridiona/meridian.",
|
|
5
5
|
"homepage": "https://github.com/Meridiona/meridian",
|
|
6
6
|
"repository": {
|
|
@@ -10,9 +10,14 @@
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
12
|
APP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
13
|
+
# ⚠️ LICENSE PIN — DO NOT BUMP past 0.4.6 without legal review.
|
|
14
|
+
# screenpipe relicensed MIT → Commercial on 2026-06-10. 0.4.6 (published
|
|
15
|
+
# 2026-06-05) is the LAST MIT npm release; >= 0.4.17 (2026-06-11+) is Commercial,
|
|
16
|
+
# whose "competing product" clause would then bind our users. Meridian ships zero
|
|
17
|
+
# screenpipe code, so MIT 0.4.6 keeps the install license-clean. Bumping this is a
|
|
18
|
+
# deliberate legal decision. CI enforces it — .github/workflows/ci.yml `screenpipe-license-pin`.
|
|
13
19
|
SCREENPIPE_VERSION="0.4.6"
|
|
14
20
|
MLX_PORT="${MLX_PORT:-7823}"
|
|
15
|
-
UI_PORT="${MERIDIAN_UI_PORT:-3939}" # dashboard port (override via MERIDIAN_UI_PORT)
|
|
16
21
|
SKIP_PERMISSIONS=0
|
|
17
22
|
[[ "${1:-}" == "--skip-permissions" ]] && SKIP_PERMISSIONS=1
|
|
18
23
|
|
|
@@ -22,7 +27,6 @@ SKIP_PERMISSIONS=0
|
|
|
22
27
|
_HASH_FILE="${HOME}/.meridian/.component-hashes"
|
|
23
28
|
_load_old_hash() { grep "^$1=" "${_HASH_FILE}" 2>/dev/null | cut -d= -f2 || true; }
|
|
24
29
|
_OLD_DAEMON_HASH="$(_load_old_hash daemon_bin)"
|
|
25
|
-
_OLD_UI_HASH="$(_load_old_hash ui_tarball)"
|
|
26
30
|
_OLD_TRAY_HASH="$(_load_old_hash tray_bin)"
|
|
27
31
|
|
|
28
32
|
info() { echo "→ $*" >&2; }
|
|
@@ -125,111 +129,28 @@ source "${APP_ROOT}/scripts/lib-trello-setup.sh"
|
|
|
125
129
|
GUI_TARGET="gui/$(id -u)"
|
|
126
130
|
LAUNCH_AGENTS="${HOME}/Library/LaunchAgents"
|
|
127
131
|
|
|
128
|
-
#
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
# to system node are LOUD because they may not match the addon's ABI.
|
|
137
|
-
resolve_node_runtime() {
|
|
138
|
-
local meta="${APP_ROOT}/bin/node-runtime.meta"
|
|
139
|
-
# Dev/source install (no meta file): use system/Homebrew node as-is. Such a
|
|
140
|
-
# build compiles its own better-sqlite3 against that node, so ABI matches.
|
|
141
|
-
if [[ ! -f "${meta}" ]]; then
|
|
142
|
-
local _n
|
|
143
|
-
for _n in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
|
|
144
|
-
[[ -x "${_n}" ]] && { echo "${_n}"; return 0; }
|
|
145
|
-
done
|
|
146
|
-
return 1
|
|
147
|
-
fi
|
|
148
|
-
local ver sha
|
|
149
|
-
ver="$(grep '^NODE_RUNTIME_VERSION=' "${meta}" | cut -d= -f2 | tr -d '[:space:]')"
|
|
150
|
-
sha="$(grep '^NODE_RUNTIME_SHA=' "${meta}" | cut -d= -f2 | tr -d '[:space:]')"
|
|
151
|
-
if [[ -z "${ver}" || -z "${sha}" ]]; then
|
|
152
|
-
warn "node-runtime.meta is malformed (missing VERSION or SHA) — falling back to system node"
|
|
153
|
-
for _n in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
|
|
154
|
-
[[ -x "${_n}" ]] && { echo "${_n}"; return 0; }
|
|
155
|
-
done
|
|
156
|
-
return 1
|
|
157
|
-
fi
|
|
158
|
-
local cache_dir="${HOME}/.meridian/node-runtime/v${ver}"
|
|
159
|
-
local cache_bin="${cache_dir}/bin/node"
|
|
160
|
-
if [[ -x "${cache_bin}" ]]; then echo "${cache_bin}"; return 0; fi
|
|
161
|
-
local tmp tgz url got
|
|
162
|
-
tmp="$(mktemp -d)"; tgz="${tmp}/node.tar.gz"
|
|
163
|
-
url="https://nodejs.org/dist/v${ver}/node-v${ver}-darwin-arm64.tar.gz"
|
|
164
|
-
info "Downloading Node ${ver} runtime for the dashboard (one-time, ~40 MB)…"
|
|
165
|
-
if curl -fsSL --retry 3 "${url}" -o "${tgz}"; then
|
|
166
|
-
got="$(shasum -a 256 "${tgz}" | cut -d' ' -f1)"
|
|
167
|
-
if [[ "${got}" == "${sha}" ]]; then
|
|
168
|
-
tar -xzf "${tgz}" -C "${tmp}"
|
|
169
|
-
rm -rf "${cache_dir}"; mkdir -p "$(dirname "${cache_dir}")"
|
|
170
|
-
mv "${tmp}/node-v${ver}-darwin-arm64" "${cache_dir}"
|
|
171
|
-
rm -rf "${tmp}"
|
|
172
|
-
ok "Node ${ver} runtime cached (ABI-matched to the dashboard)"
|
|
173
|
-
echo "${cache_bin}"; return 0
|
|
174
|
-
fi
|
|
175
|
-
warn "Node ${ver} SHA-256 mismatch (expected ${sha}, got ${got}) — not using it"
|
|
176
|
-
else
|
|
177
|
-
warn "Node ${ver} download failed (offline?) — the dashboard needs it to match better-sqlite3's ABI"
|
|
178
|
-
fi
|
|
179
|
-
rm -rf "${tmp}"
|
|
180
|
-
local _n
|
|
181
|
-
for _n in /opt/homebrew/bin/node /usr/local/bin/node /usr/bin/node; do
|
|
182
|
-
if [[ -x "${_n}" ]]; then
|
|
183
|
-
warn "Falling back to ${_n} — if the dashboard fails to load, re-run 'meridian update' with a connection"
|
|
184
|
-
echo "${_n}"; return 0
|
|
185
|
-
fi
|
|
186
|
-
done
|
|
187
|
-
return 1
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
# Register the dashboard as a launchd agent that runs the prebuilt Next.js
|
|
191
|
-
# standalone server (`node ui/server.js`) — no `npm start`, no node_modules
|
|
192
|
-
# install. Mirrors the EIO-safe bootout/bootstrap pattern of the other agents.
|
|
193
|
-
install_ui_standalone() {
|
|
132
|
+
# Retire the legacy standalone dashboard Node server. The dashboard now ships
|
|
133
|
+
# embedded INSIDE the tray binary (static export, no Node server), so any
|
|
134
|
+
# `com.meridiona.ui` launchd agent from a pre-fold install is a zombie — a
|
|
135
|
+
# KeepAlive=true Node server holding the old dashboard port. Boot it out + remove
|
|
136
|
+
# its plist, and drop the now-orphaned ABI-matched Node runtime cache. Idempotent
|
|
137
|
+
# (no-op when absent) and non-fatal (a launchctl hiccup must not abort the
|
|
138
|
+
# update) — but logged, never silent.
|
|
139
|
+
retire_legacy_ui_server() {
|
|
194
140
|
local label="com.meridiona.ui"
|
|
195
141
|
local plist="${LAUNCH_AGENTS}/${label}.plist"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
<array><string>${start_script}</string></array>
|
|
209
|
-
<key>WorkingDirectory</key><string>${APP_ROOT}/ui</string>
|
|
210
|
-
<key>EnvironmentVariables</key>
|
|
211
|
-
<dict>
|
|
212
|
-
<key>PORT</key><string>${UI_PORT}</string>
|
|
213
|
-
<key>HOSTNAME</key><string>127.0.0.1</string>
|
|
214
|
-
<key>MERIDIAN_DB_PATH</key><string>${HOME}/.meridian/meridian.db</string>
|
|
215
|
-
<key>MERIDIAN_NODE_BIN</key><string>${node_bin}</string>
|
|
216
|
-
</dict>
|
|
217
|
-
<key>RunAtLoad</key><true/>
|
|
218
|
-
<key>KeepAlive</key><true/>
|
|
219
|
-
<key>StandardOutPath</key><string>${HOME}/.meridian/logs/ui.log</string>
|
|
220
|
-
<key>StandardErrorPath</key><string>${HOME}/.meridian/logs/ui-error.log</string>
|
|
221
|
-
<key>ProcessType</key><string>Background</string>
|
|
222
|
-
</dict></plist>
|
|
223
|
-
PLIST
|
|
224
|
-
plutil -lint "${plist}" >/dev/null 2>&1 || { warn "ui plist failed lint"; return 1; }
|
|
225
|
-
launchctl bootout "${GUI_TARGET}/${label}" 2>/dev/null || true
|
|
226
|
-
local w=0
|
|
227
|
-
while launchctl print "${GUI_TARGET}/${label}" >/dev/null 2>&1; do
|
|
228
|
-
sleep 1; w=$((w+1)); [[ $w -ge 15 ]] && break
|
|
229
|
-
done
|
|
230
|
-
launchctl enable "${GUI_TARGET}/${label}" 2>/dev/null || true
|
|
231
|
-
launchctl bootstrap "${GUI_TARGET}" "${plist}"
|
|
232
|
-
launchctl kickstart -k "${GUI_TARGET}/${label}" 2>/dev/null || true
|
|
142
|
+
if launchctl print "${GUI_TARGET}/${label}" >/dev/null 2>&1; then
|
|
143
|
+
info "Retiring the legacy dashboard server (now bundled in the tray app)…"
|
|
144
|
+
launchctl bootout "${GUI_TARGET}/${label}" 2>/dev/null \
|
|
145
|
+
|| warn "could not bootout ${label} (continuing)"
|
|
146
|
+
fi
|
|
147
|
+
if [[ -f "${plist}" ]]; then
|
|
148
|
+
rm -f "${plist}" && ok "removed legacy ${label} launchd agent" \
|
|
149
|
+
|| warn "could not remove ${plist} (continuing)"
|
|
150
|
+
fi
|
|
151
|
+
# The pinned Node runtime existed only to ABI-match better-sqlite3 in the old
|
|
152
|
+
# server; nothing uses it now.
|
|
153
|
+
rm -rf "${HOME}/.meridian/node-runtime" 2>/dev/null || true
|
|
233
154
|
}
|
|
234
155
|
|
|
235
156
|
# Enable accessibility mode in VS Code / Cursor / Antigravity so screenpipe
|
|
@@ -339,10 +260,10 @@ ok "node + python ($(${PYTHON_BIN} --version 2>&1)) + uv ($(${UV_BIN} --version
|
|
|
339
260
|
if ! command -v screenpipe >/dev/null 2>&1; then
|
|
340
261
|
info "Installing screenpipe ${SCREENPIPE_VERSION} via npm…"
|
|
341
262
|
if npm_global_writable; then
|
|
342
|
-
npm install -g "screenpipe@${SCREENPIPE_VERSION}"
|
|
263
|
+
npm install -g --ignore-scripts "screenpipe@${SCREENPIPE_VERSION}"
|
|
343
264
|
else
|
|
344
265
|
warn "global npm prefix needs root — elevating just this install (you may be prompted)…"
|
|
345
|
-
sudo npm install -g "screenpipe@${SCREENPIPE_VERSION}"
|
|
266
|
+
sudo npm install -g --ignore-scripts "screenpipe@${SCREENPIPE_VERSION}"
|
|
346
267
|
fi
|
|
347
268
|
fi
|
|
348
269
|
ok "screenpipe"
|
|
@@ -367,22 +288,18 @@ fi
|
|
|
367
288
|
if ! command -v ffmpeg >/dev/null 2>&1; then info "Installing ffmpeg…"; brew install ffmpeg; fi
|
|
368
289
|
ok "ffmpeg"
|
|
369
290
|
|
|
370
|
-
# ── 2. Config:
|
|
371
|
-
|
|
291
|
+
# ── 2. Config: user credential file ──────────────────────────────────────────
|
|
292
|
+
# Canonical location is ~/.meridian/.env — install-independent, next to
|
|
293
|
+
# meridian.db and settings.json, never inside app/ (the binary tree).
|
|
294
|
+
ENV_FILE="${HOME}/.meridian/.env"
|
|
372
295
|
if [[ ! -f "${ENV_FILE}" ]]; then
|
|
373
296
|
cp "${APP_ROOT}/.env.example" "${ENV_FILE}"
|
|
374
|
-
info "created ${ENV_FILE} from template — add your
|
|
297
|
+
info "created ${ENV_FILE} from template — add your credentials: meridian config edit"
|
|
375
298
|
fi
|
|
376
299
|
# MLX is the default backend.
|
|
377
300
|
grep -q '^CLASSIFIER_BACKEND=' "${ENV_FILE}" || echo "CLASSIFIER_BACKEND=mlx" >> "${ENV_FILE}"
|
|
378
301
|
grep -q '^MLX_SERVER_PORT=' "${ENV_FILE}" || echo "MLX_SERVER_PORT=${MLX_PORT}" >> "${ENV_FILE}"
|
|
379
|
-
|
|
380
|
-
if grep -q '^MERIDIAN_UI_PORT=' "${ENV_FILE}"; then
|
|
381
|
-
UI_PORT="$(grep '^MERIDIAN_UI_PORT=' "${ENV_FILE}" | tail -n1 | cut -d= -f2 | tr -d '[:space:]')"
|
|
382
|
-
else
|
|
383
|
-
echo "MERIDIAN_UI_PORT=${UI_PORT}" >> "${ENV_FILE}"
|
|
384
|
-
fi
|
|
385
|
-
ok "config at ${ENV_FILE} (dashboard port ${UI_PORT})"
|
|
302
|
+
ok "config at ${ENV_FILE}"
|
|
386
303
|
|
|
387
304
|
# Interactive tracker-credential walkthrough — parity with install.sh. A fresh
|
|
388
305
|
# `meridian setup` collects Jira/GitHub/Linear keys here; `meridian update`
|
|
@@ -552,7 +469,7 @@ if [[ "${SKIP_PERMISSIONS}" -eq 0 ]]; then
|
|
|
552
469
|
open "x-apple.systempreferences:com.apple.Notifications-Settings.extension" 2>/dev/null || true
|
|
553
470
|
echo " → Scroll to the bottom and turn ON"
|
|
554
471
|
echo " 'Allow notifications when mirroring or sharing the display'."
|
|
555
|
-
echo " → When 'Meridian
|
|
472
|
+
echo " → When 'Meridian' appears, ensure its notifications are allowed"
|
|
556
473
|
echo " (style Banners or Alerts, not None)."
|
|
557
474
|
read -r -p " Press Enter when done… " _ || true
|
|
558
475
|
fi
|
|
@@ -561,30 +478,10 @@ fi
|
|
|
561
478
|
# this, screenpipe falls back to OCR for those editors instead of their a11y tree.
|
|
562
479
|
configure_editor_accessibility
|
|
563
480
|
|
|
564
|
-
# ── 5b.
|
|
565
|
-
# The
|
|
566
|
-
#
|
|
567
|
-
|
|
568
|
-
# When meridian-npm-setup.sh detected the tarball hash was unchanged it preserved
|
|
569
|
-
# the existing ui/ dir and deleted ui.tar.gz — in that case skip extraction too.
|
|
570
|
-
_ui_changed=1
|
|
571
|
-
_new_ui_hash=""
|
|
572
|
-
if [[ -f "${APP_ROOT}/ui.tar.gz" ]]; then
|
|
573
|
-
_new_ui_hash="$(shasum -a 256 "${APP_ROOT}/ui.tar.gz" | cut -d' ' -f1)"
|
|
574
|
-
info "Unpacking dashboard…"
|
|
575
|
-
rm -rf "${APP_ROOT}/ui"
|
|
576
|
-
mkdir -p "${APP_ROOT}/ui"
|
|
577
|
-
tar -xzf "${APP_ROOT}/ui.tar.gz" -C "${APP_ROOT}/ui"
|
|
578
|
-
rm -f "${APP_ROOT}/ui.tar.gz"
|
|
579
|
-
ok "dashboard unpacked ($(find "${APP_ROOT}/ui/.next/node_modules" -type l 2>/dev/null | wc -l | tr -d ' ') external symlink(s) restored)"
|
|
580
|
-
elif [[ -d "${APP_ROOT}/ui" ]]; then
|
|
581
|
-
# ui/ was preserved by meridian-npm-setup.sh — hash matched, no re-extraction needed
|
|
582
|
-
ok "dashboard unchanged — reusing existing build"
|
|
583
|
-
_ui_changed=0
|
|
584
|
-
else
|
|
585
|
-
err "Dashboard bundle missing from ${APP_ROOT} — re-run the installer: curl -fsSL https://raw.githubusercontent.com/Meridiona/meridian/main/scripts/bootstrap.sh | bash"
|
|
586
|
-
exit 1
|
|
587
|
-
fi
|
|
481
|
+
# ── 5b. Retire the legacy dashboard Node server (one-time, for pre-fold installs) ─
|
|
482
|
+
# The dashboard is now embedded in the tray binary, so an old standalone UI agent
|
|
483
|
+
# is a zombie. This runs on every (re)install/update — idempotent + non-fatal.
|
|
484
|
+
retire_legacy_ui_server
|
|
588
485
|
|
|
589
486
|
# ── 6. Daemons — restart only what changed ───────────────────────────────────
|
|
590
487
|
# screenpipe: external npm binary, plist may have changed → always refresh.
|
|
@@ -679,7 +576,7 @@ if [[ -x "${APP_ROOT}/bin/meridian-tray" ]]; then
|
|
|
679
576
|
if [[ "${_tray_changed}" -eq 0 ]]; then
|
|
680
577
|
ok "Tray app unchanged — skipping restart"
|
|
681
578
|
else
|
|
682
|
-
info "Installing Meridian
|
|
579
|
+
info "Installing Meridian tray agent…"
|
|
683
580
|
bash "${APP_ROOT}/scripts/install-tray-daemon.sh" || warn "tray agent install failed"
|
|
684
581
|
fi
|
|
685
582
|
else
|
|
@@ -716,31 +613,16 @@ if command -v cursor >/dev/null 2>&1 || [[ -d "${HOME}/Library/Application Suppo
|
|
|
716
613
|
else
|
|
717
614
|
info " Cursor detected but the cursor-agent CLI is missing — Cursor summaries will use the local model (MLX)."
|
|
718
615
|
info " To summarise with Cursor's own CLI: curl https://cursor.com/install -fsS | bash then: cursor-agent login"
|
|
719
|
-
info " Or let the daemon install it on demand: add CURSOR_AGENT_AUTO_INSTALL=1 to ${HOME}/.meridian
|
|
616
|
+
info " Or let the daemon install it on demand: add CURSOR_AGENT_AUTO_INSTALL=1 to ${HOME}/.meridian/.env"
|
|
720
617
|
fi
|
|
721
618
|
fi
|
|
722
619
|
|
|
723
|
-
# UI: even when the build is unchanged we ALWAYS re-pin the Node runtime and
|
|
724
|
-
# reload the launchd job. The better-sqlite3 addon is ABI-locked to one Node
|
|
725
|
-
# version; skipping this reload is how a stale UI job survives `update` and ends
|
|
726
|
-
# up running a mismatched system Node (NODE_MODULE_VERSION crash-loop). The
|
|
727
|
-
# expensive re-extraction is already skipped above when unchanged, so this is
|
|
728
|
-
# cheap — resolve_node_runtime hits the cache, then bootout/bootstrap reloads.
|
|
729
|
-
if [[ "${_ui_changed}" -eq 0 ]]; then
|
|
730
|
-
info "Dashboard build unchanged — re-pinning Node runtime and reloading UI agent…"
|
|
731
|
-
else
|
|
732
|
-
info "Installing the dashboard (UI) launchd agent…"
|
|
733
|
-
fi
|
|
734
|
-
install_ui_standalone
|
|
735
|
-
|
|
736
620
|
# Persist component hashes for the next update's differential check.
|
|
737
621
|
# Write to a temp file and rename atomically so a crash mid-write never leaves
|
|
738
622
|
# a half-written or empty hash file (which would force a full reinstall).
|
|
739
|
-
_final_ui_hash="${_new_ui_hash:-${_OLD_UI_HASH}}"
|
|
740
623
|
_final_tray_hash="${_new_tray_hash:-${_OLD_TRAY_HASH}}"
|
|
741
624
|
{
|
|
742
625
|
[[ -n "${_new_daemon_hash}" ]] && printf 'daemon_bin=%s\n' "${_new_daemon_hash}"
|
|
743
|
-
[[ -n "${_final_ui_hash}" ]] && printf 'ui_tarball=%s\n' "${_final_ui_hash}"
|
|
744
626
|
[[ -n "${_final_tray_hash}" ]] && printf 'tray_bin=%s\n' "${_final_tray_hash}"
|
|
745
627
|
} > "${_HASH_FILE}.tmp" && mv "${_HASH_FILE}.tmp" "${_HASH_FILE}"
|
|
746
628
|
|
|
@@ -771,7 +653,7 @@ echo "✓ Meridian installed at ${APP_ROOT}"
|
|
|
771
653
|
echo " meridian status # check the daemons"
|
|
772
654
|
echo " meridian logs -f # watch the pipeline"
|
|
773
655
|
echo " meridian config edit # add Jira creds"
|
|
774
|
-
echo " open
|
|
656
|
+
echo " open the Meridian tray icon in the menu bar → Open Dashboard"
|
|
775
657
|
echo ""
|
|
776
658
|
echo "Jira worklogs are DRAFTED only — approve them in the dashboard (Worklogs"
|
|
777
659
|
echo "view) and the daemon posts approved worklogs within ~60s."
|
package/scripts/meridian-cli.sh
CHANGED
|
@@ -13,9 +13,10 @@ LABEL_SCREENPIPE="com.meridiona.screenpipe"
|
|
|
13
13
|
LABEL_DAEMON="com.meridiona.daemon"
|
|
14
14
|
LABEL_UI="com.meridiona.ui"
|
|
15
15
|
LABEL_MLX="com.meridiona.mlx-server"
|
|
16
|
+
# Capture runs in-process in the tray (no screenpipe launchd agent since Bucket-2).
|
|
16
17
|
# Jira worklogs and coding-agent ingest run inside the Rust daemon — no
|
|
17
|
-
# separate launchd agents. Only these
|
|
18
|
-
readonly LABELS=("${
|
|
18
|
+
# separate launchd agents. Only these two are managed.
|
|
19
|
+
readonly LABELS=("${LABEL_DAEMON}" "${LABEL_MLX}")
|
|
19
20
|
GUI_TARGET="gui/$(id -u)"
|
|
20
21
|
LAUNCH_AGENTS="${HOME}/Library/LaunchAgents"
|
|
21
22
|
LOG_DIR="${HOME}/.meridian/logs"
|
|
@@ -40,7 +41,7 @@ Commands:
|
|
|
40
41
|
restart Stop, wait 1s, start
|
|
41
42
|
status Show running state of all daemons
|
|
42
43
|
logs [target] Tail log files
|
|
43
|
-
target: daemon|daemon-error|
|
|
44
|
+
target: daemon|daemon-error|mlx-server|mlx-server-error|tray|tray-error
|
|
44
45
|
-f Follow (stream)
|
|
45
46
|
-n N Last N lines (default 100)
|
|
46
47
|
doctor Run environment health checks (includes pipeline smoke)
|
|
@@ -179,7 +180,12 @@ cmd_logs() {
|
|
|
179
180
|
ui-error) log_file="${LOG_DIR}/ui-error.log" ;;
|
|
180
181
|
mlx-server) log_file="${LOG_DIR}/mlx-server.log" ;;
|
|
181
182
|
mlx-server-error) log_file="${LOG_DIR}/mlx-server-error.log" ;;
|
|
182
|
-
|
|
183
|
+
tray) log_file="${LOG_DIR}/tray.log" ;;
|
|
184
|
+
tray-error) log_file="${LOG_DIR}/tray-error.log" ;;
|
|
185
|
+
# screenpipe/ui are retired post-v1.64.0 (capture is in-process in the
|
|
186
|
+
# tray; the dashboard is embedded) — kept here so old log files remain
|
|
187
|
+
# tailable, but `tray`/`tray-error` are the live targets now.
|
|
188
|
+
*) err "unknown log target: ${target} (daemon|daemon-error|mlx-server|mlx-server-error|tray|tray-error)"; exit 1 ;;
|
|
183
189
|
esac
|
|
184
190
|
|
|
185
191
|
if [[ ! -f "$log_file" ]]; then
|
|
@@ -563,7 +569,7 @@ PYEOF
|
|
|
563
569
|
"models--mlx-community--phi-4-4bit"
|
|
564
570
|
"models--mlx-community--DeepSeek-R1-Distill-Qwen-14B-4bit"
|
|
565
571
|
"models--mlx-community--gemma-3-12b-it-qat-4bit"
|
|
566
|
-
"models--mlx-community--Qwen3.5-
|
|
572
|
+
"models--mlx-community--Qwen3.5-2B-OptiQ-4bit"
|
|
567
573
|
"models--mlx-community--Qwen3.5-4B-MLX-4bit"
|
|
568
574
|
"models--mlx-community--Llama-3.2-3B-Instruct-4bit"
|
|
569
575
|
)
|
|
@@ -43,13 +43,22 @@ clone_dir() { # <src> <dst>
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
mkdir -p "${STAGE}"
|
|
46
|
-
# Copy the prebuilt payload (bin/
|
|
46
|
+
# Copy the prebuilt payload (bin/ services/ scripts/ .env.example VERSION).
|
|
47
47
|
cp -R "${BUNDLE}/." "${STAGE}/"
|
|
48
48
|
# Drop npm-package metadata that isn't part of the app.
|
|
49
49
|
rm -f "${STAGE}/package.json" "${STAGE}/README.md" "${STAGE}/.gitignore" "${STAGE}/.npmignore"
|
|
50
50
|
|
|
51
|
-
#
|
|
52
|
-
|
|
51
|
+
# One-time migration: move credentials from the old app/.env location to the
|
|
52
|
+
# canonical ~/.meridian/.env (outside the swap area — untouched by updates).
|
|
53
|
+
# If both exist, the canonical wins and the old copy is removed.
|
|
54
|
+
if [[ -f "${APP}/.env" ]]; then
|
|
55
|
+
if [[ ! -f "${HOME}/.meridian/.env" ]]; then
|
|
56
|
+
mv "${APP}/.env" "${HOME}/.meridian/.env"
|
|
57
|
+
echo "migrated credentials: ~/.meridian/app/.env → ~/.meridian/.env"
|
|
58
|
+
else
|
|
59
|
+
rm -f "${APP}/.env"
|
|
60
|
+
fi
|
|
61
|
+
fi
|
|
53
62
|
|
|
54
63
|
# Preserve the Python venv across updates. The venv is built from PyPI via
|
|
55
64
|
# uv sync at install time; preserving it means install-from-bundle.sh only
|
|
@@ -60,35 +69,9 @@ if [[ -d "${APP}/services/.venv" ]]; then
|
|
|
60
69
|
clone_dir "${APP}/services/.venv" "${STAGE}/services/.venv"
|
|
61
70
|
fi
|
|
62
71
|
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
# "no tarball + ui/ present" as unchanged and skips re-extraction.
|
|
67
|
-
# * changed: pre-extract the tarball into staging AND keep the tarball. The
|
|
68
|
-
# stage is then complete BEFORE the swap, so an installer crash after the
|
|
69
|
-
# swap still leaves a runnable dashboard; the installer's own extraction
|
|
70
|
-
# (which records the new hash) re-does only a few seconds of work.
|
|
71
|
-
_hash_file="${HOME}/.meridian/.component-hashes"
|
|
72
|
-
_ui_preserved=0
|
|
73
|
-
if [[ -d "${APP}/ui" ]]; then
|
|
74
|
-
if [[ -f "${STAGE}/ui.tar.gz" ]] && [[ -f "${_hash_file}" ]]; then
|
|
75
|
-
_old_ui_hash="$(grep '^ui_tarball=' "${_hash_file}" 2>/dev/null | cut -d= -f2 || true)"
|
|
76
|
-
_new_ui_hash="$(shasum -a 256 "${STAGE}/ui.tar.gz" | cut -d' ' -f1)"
|
|
77
|
-
if [[ -n "${_old_ui_hash}" && "${_new_ui_hash}" == "${_old_ui_hash}" ]]; then
|
|
78
|
-
clone_dir "${APP}/ui" "${STAGE}/ui"
|
|
79
|
-
rm -f "${STAGE}/ui.tar.gz"
|
|
80
|
-
_ui_preserved=1
|
|
81
|
-
fi
|
|
82
|
-
elif [[ ! -f "${STAGE}/ui.tar.gz" ]]; then
|
|
83
|
-
# No tarball in bundle = UI unchanged since last release; keep existing build.
|
|
84
|
-
clone_dir "${APP}/ui" "${STAGE}/ui"
|
|
85
|
-
_ui_preserved=1
|
|
86
|
-
fi
|
|
87
|
-
fi
|
|
88
|
-
if [[ "${_ui_preserved}" -eq 0 && -f "${STAGE}/ui.tar.gz" ]]; then
|
|
89
|
-
mkdir -p "${STAGE}/ui"
|
|
90
|
-
tar -xzf "${STAGE}/ui.tar.gz" -C "${STAGE}/ui"
|
|
91
|
-
fi
|
|
72
|
+
# The dashboard is no longer staged here: it's embedded in the tray binary, so
|
|
73
|
+
# the bundle ships neither ui.tar.gz nor a ui/ dir. Any old ~/.meridian/app/ui
|
|
74
|
+
# from a pre-fold install is discarded by the swap below (APP → OLD → rm).
|
|
92
75
|
|
|
93
76
|
# The swap: two renames on one filesystem. Running daemons keep their open
|
|
94
77
|
# inodes from the old tree until they restart; the installer (exec'd next)
|
package/services/README.md
CHANGED
|
@@ -4,7 +4,7 @@ The model layer that runs alongside the Rust daemon. A persistent MLX inference
|
|
|
4
4
|
|
|
5
5
|
The Rust daemon owns all DDL; this service only does SELECT/INSERT/UPDATE on its agent-side tables.
|
|
6
6
|
|
|
7
|
-
> **Note:** the coding-agent indexer + summariser, and the pm-worklog (Stage 4) driver, all run **inside the Rust daemon** (`src/coding_agent_session_ingest/`, `src/pm_worklog/`). This service is the model layer: `agents/server.py` is the persistent MLX server, which serves `/
|
|
7
|
+
> **Note:** the coding-agent indexer + summariser, and the pm-worklog (Stage 4) driver, all run **inside the Rust daemon** (`src/coding_agent_session_ingest/`, `src/pm_worklog/`). This service is the model layer: `agents/server.py` is the persistent MLX server, which serves `/summarise` (the summariser's fallback) and `/synthesise_worklog` (the agno worklog synthesiser). The Rust daemon drives both over HTTP.
|
|
8
8
|
|
|
9
9
|
For the deep technical reference (classification logic, schema, recipes), see [`agents/README.md`](agents/README.md).
|
|
10
10
|
|
|
@@ -15,14 +15,8 @@ For the deep technical reference (classification logic, schema, recipes), see [`
|
|
|
15
15
|
```
|
|
16
16
|
app_sessions row (Rust ETL writes it)
|
|
17
17
|
│
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
┌──────────────────────────────────────────────┐
|
|
21
|
-
│ MLX inference server (FastAPI, port 7823) │ Apple Silicon only
|
|
22
|
-
│ model: mlx-community/Qwen3.5-9B-OptiQ-4bit│
|
|
23
|
-
│ loaded once at startup, served until killed│
|
|
24
|
-
│ returns: task_key, session_type, confidence│
|
|
25
|
-
└──────────────────────────────────────────────┘
|
|
18
|
+
▼
|
|
19
|
+
Rust daemon classifies sessions (src/intelligence/task_linker/)
|
|
26
20
|
│
|
|
27
21
|
▼
|
|
28
22
|
Rust writes ticket_links + session_dimensions → meridian.db
|
|
@@ -50,7 +44,7 @@ The `hermes-agent` package is fetched from GitHub at the pinned tag; an internet
|
|
|
50
44
|
|
|
51
45
|
## MLX server
|
|
52
46
|
|
|
53
|
-
The Rust daemon calls the MLX server for
|
|
47
|
+
The Rust daemon calls the MLX server for summarisation and worklog synthesis. It must be running before the daemon starts.
|
|
54
48
|
|
|
55
49
|
### Install as a launchd daemon (recommended)
|
|
56
50
|
|
|
@@ -74,7 +68,7 @@ RUST_LOG=meridian=debug CLASSIFIER_BACKEND=mlx cargo run --bin meridian
|
|
|
74
68
|
python -m agents.server --backend mlx --port 7823
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
The model (`Qwen3.5-
|
|
71
|
+
The model (`Qwen3.5-2B-OptiQ-4bit`) is downloaded from Hugging Face on first run. Subsequent starts load from local cache in ~5 s.
|
|
78
72
|
|
|
79
73
|
---
|
|
80
74
|
|
|
@@ -96,7 +90,7 @@ Additional variables are documented in [`agents/README.md`](agents/README.md#con
|
|
|
96
90
|
|
|
97
91
|
### Task classifier
|
|
98
92
|
|
|
99
|
-
The task classifier runs automatically
|
|
93
|
+
The task classifier runs automatically inside the Rust daemon on each intelligence tick. To inspect or re-run classification:
|
|
100
94
|
|
|
101
95
|
```bash
|
|
102
96
|
# Re-run classification with full logging — does NOT write to DB
|
|
@@ -11,7 +11,7 @@ For a higher-level overview (installation, daemon ops, configuration), see [`ser
|
|
|
11
11
|
The Rust daemon owns the pipeline (ETL, coding-agent ingest, classification
|
|
12
12
|
trigger, pm-worklog driver) and the database. This service is the **model
|
|
13
13
|
layer**: one persistent MLX server (`server.py`) that the daemon calls over HTTP
|
|
14
|
-
for the
|
|
14
|
+
for the two jobs that need a local LLM.
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
screenpipe.db (read-only)
|
|
@@ -29,16 +29,10 @@ meridian.db → app_sessions
|
|
|
29
29
|
│ MLX server's /summarise is the fallback. Rows walk task_method:
|
|
30
30
|
│ coding_agent_live → pending_summariser → pending_classifier.
|
|
31
31
|
│
|
|
32
|
-
├── classification (src/intelligence/task_linker/, Rust
|
|
32
|
+
├── classification (src/intelligence/task_linker/, Rust)
|
|
33
33
|
│ Rust reads unclassified rows and the pending_classifier queue,
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
│ {session_ids: [...], meridian_db: ...}
|
|
37
|
-
│ MLX server (Qwen3.5-9B-OptiQ-4bit, FSM-constrained outlines):
|
|
38
|
-
│ fetch session + pm_tasks + recent context
|
|
39
|
-
│ → SessionClassification {task_key, session_type, confidence,
|
|
40
|
-
│ reasoning, method, dimensions}
|
|
41
|
-
│ Rust writes ticket_links + session_dimensions, advances cursor.
|
|
34
|
+
│ classifies them internally, and writes ticket_links +
|
|
35
|
+
│ session_dimensions, then advances the cursor.
|
|
42
36
|
│
|
|
43
37
|
└── pm-worklog (Stage 4) (src/pm_worklog/, Rust → MLX)
|
|
44
38
|
The Rust hour-driven driver collects each (task, hour) bundle and
|
|
@@ -260,7 +254,7 @@ When the screen is locked the selector uses `min(0.8, budget_pct × 1.5)` as the
|
|
|
260
254
|
2. **Largest cached model fits** — if the preferred is too large, return the largest catalog model whose files are already in the HF cache and whose `min_ram_gb ≤ budget`. Avoids surprising multi-GB downloads on constrained machines.
|
|
261
255
|
3. **Largest catalog model that fits (may download)** — if nothing cached fits, return the largest catalog entry where `min_ram_gb ≤ budget`, regardless of cache. This triggers a one-time download of the best available model rather than loading an oversized one that exceeds available memory. Falls back to `preferred_hf_id` only when **no catalog model fits** at all (budget so low even the 1.8 GB model won't load).
|
|
262
256
|
|
|
263
|
-
**Why stage 3 matters on low-RAM machines:** an M1 Air (8 GB) has Metal headroom ≈ 5.4 GB. At `LLM_BUDGET_PCT=0.5` the budget is ~2.7 GB. The default preferred model is 6.5 GB (`Qwen3.5-
|
|
257
|
+
**Why stage 3 matters on low-RAM machines:** an M1 Air (8 GB) has Metal headroom ≈ 5.4 GB. At `LLM_BUDGET_PCT=0.5` the budget is ~2.7 GB. The default preferred model is 6.5 GB (`Qwen3.5-2B-OptiQ-4bit`). Without the fix, stage 3 returned the preferred unconditionally — the server then attempted to load a 6.5 GB model into a 2.7 GB budget, causing memory pressure or an outright load failure. With the fix, stage 3 selects `Qwen3.5-4B-MLX-4bit` (2.5 GB) or `Llama-3.2-3B-Instruct-4bit` (1.8 GB) — whichever is largest and fits — and downloads it on first use.
|
|
264
258
|
|
|
265
259
|
**Check what would be selected:**
|
|
266
260
|
|
|
@@ -270,7 +264,7 @@ cd services
|
|
|
270
264
|
from agents.llm_selector import select_mlx_model_id, probe_compute
|
|
271
265
|
snap = probe_compute()
|
|
272
266
|
print(f'Headroom: {snap.metal_headroom_gb:.1f} GB thermal: {snap.thermal_level}')
|
|
273
|
-
model = select_mlx_model_id('mlx-community/Qwen3.5-
|
|
267
|
+
model = select_mlx_model_id('mlx-community/Qwen3.5-2B-OptiQ-4bit', 6.5, 0.5)
|
|
274
268
|
print(f'Would load: {model}')
|
|
275
269
|
"
|
|
276
270
|
```
|
|
@@ -350,14 +344,14 @@ pip install -e ".[local-llm]"
|
|
|
350
344
|
FROM ticket_links WHERE session_id = <ID>;
|
|
351
345
|
```
|
|
352
346
|
|
|
353
|
-
2. **Re-run the classifier against a live session**
|
|
347
|
+
2. **Re-run the classifier against a live session** using the daemon CLI:
|
|
348
|
+
|
|
354
349
|
```bash
|
|
355
|
-
|
|
356
|
-
-H "Content-Type: application/json" \
|
|
357
|
-
-d "{\"session_ids\": [<ID>]}" | jq .
|
|
350
|
+
meridian coding-agent-classify
|
|
358
351
|
```
|
|
359
352
|
|
|
360
353
|
Or use the standalone MLX script (reads from stdin, prints JSON to stdout):
|
|
354
|
+
|
|
361
355
|
```bash
|
|
362
356
|
cd services
|
|
363
357
|
echo '{"session_ids": [<ID>], "meridian_db": "'"$HOME"'/.meridian/meridian.db"}' \
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Process-global server state shared between the FastAPI app and route modules."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import threading
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
# Populated in _lifespan (mlx_module, loaded_at, model_sem, tracer, self_url, …)
|
|
9
|
+
# and read by every route module.
|
|
10
|
+
app_state: dict[str, Any] = {}
|
|
11
|
+
|
|
12
|
+
# Shared prefetch progress, guarded by prefetch_lock. states: idle|downloading|done|error
|
|
13
|
+
prefetch_state: dict[str, Any] = {
|
|
14
|
+
"state": "idle",
|
|
15
|
+
"model_id": None,
|
|
16
|
+
"received": 0,
|
|
17
|
+
"total": 0,
|
|
18
|
+
"error": None,
|
|
19
|
+
}
|
|
20
|
+
prefetch_lock = threading.Lock()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def model_sem() -> "asyncio.Semaphore":
|
|
24
|
+
"""Return the process-global single-slot model semaphore.
|
|
25
|
+
|
|
26
|
+
Created once in _lifespan and stored in app_state. Every endpoint that
|
|
27
|
+
runs a model inference acquires this before calling run_in_threadpool so
|
|
28
|
+
that concurrent requests never compete on the GPU.
|
|
29
|
+
"""
|
|
30
|
+
sem = app_state.get("model_sem")
|
|
31
|
+
if sem is None: # fallback if called before lifespan (e.g. tests)
|
|
32
|
+
sem = asyncio.Semaphore(1)
|
|
33
|
+
app_state["model_sem"] = sem
|
|
34
|
+
return sem
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Minimal AgentOS viewer for browsing worklog pipeline traces.
|
|
2
|
+
|
|
3
|
+
Run this script to expose the agno_traces.db as an AgentOS runtime, then:
|
|
4
|
+
1. Open https://os.agno.com, sign in (free account)
|
|
5
|
+
2. Add new OS → Environment: Local → URL: http://localhost:8000
|
|
6
|
+
3. Click "CONNECT" → refresh the page
|
|
7
|
+
|
|
8
|
+
Alternatively, browse directly at http://localhost:8000 without the hosted UI.
|
|
9
|
+
|
|
10
|
+
The DB path defaults to ~/.meridian/agno_traces.db (set AGNO_TRACE_DB to override).
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
from agno.agent import Agent
|
|
21
|
+
from agno.db.sqlite import SqliteDb
|
|
22
|
+
from agno.os import AgentOS
|
|
23
|
+
from agno.workflow import Workflow
|
|
24
|
+
|
|
25
|
+
_DEFAULT_DB = Path("~/.meridian/agno_traces.db").expanduser()
|
|
26
|
+
_db_file = Path(os.environ.get("AGNO_TRACE_DB", "") or _DEFAULT_DB).expanduser()
|
|
27
|
+
|
|
28
|
+
db = SqliteDb(db_file=str(_db_file))
|
|
29
|
+
|
|
30
|
+
_agent = Agent(
|
|
31
|
+
name="Meridian Worklog Pipeline",
|
|
32
|
+
description="Worklog pipeline agent — match sessions to PM tasks and draft worklogs.",
|
|
33
|
+
db=db,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Stub workflow registered so os.agno.com marks the OS as active.
|
|
37
|
+
# The real pipeline runs inside server.py; this is traces-only.
|
|
38
|
+
_workflow = Workflow(
|
|
39
|
+
name="worklog_hour",
|
|
40
|
+
description="Hour-level worklog pipeline: distil → report → rerank → match → draft/propose.",
|
|
41
|
+
db=db,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
agent_os = AgentOS(
|
|
45
|
+
id="meridian-worklog",
|
|
46
|
+
name="Meridian Worklog Traces",
|
|
47
|
+
description="Viewer for worklog pipeline traces — worklog.hour, match, propose, draft spans.",
|
|
48
|
+
agents=[_agent],
|
|
49
|
+
workflows=[_workflow],
|
|
50
|
+
db=db,
|
|
51
|
+
tracing=True,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
app = agent_os.get_app()
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
log.info("trace DB: %s", _db_file)
|
|
58
|
+
log.info("dashboard: http://localhost:8000")
|
|
59
|
+
log.info("control: https://os.agno.com → Add new OS → http://localhost:8000")
|
|
60
|
+
agent_os.serve(app="agno_viewer:app", reload=False, port=8000)
|