@meridiona/meridian-darwin-arm64 1.53.0 → 1.54.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 +15 -0
- package/services/agents/_prompts.py +14 -2
- package/services/agents/run_task_linker_mlx.py +69 -5
- package/services/pyproject.toml +1 -1
- package/ui.tar.gz +0 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.54.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.54.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": {
|
|
@@ -540,6 +540,21 @@ if [[ "${SKIP_PERMISSIONS}" -eq 0 ]]; then
|
|
|
540
540
|
echo " ${HOME}/.meridian/bin/meridian-a11y-helper"
|
|
541
541
|
echo " Without the a11y helper, Electron apps (Claude, Codex, Slack, …) stay invisible to capture."
|
|
542
542
|
read -r -p " Press Enter once all are granted… " _ || true
|
|
543
|
+
|
|
544
|
+
# Notifications: the tray surfaces desktop toasts (plan nudges, worklog
|
|
545
|
+
# drafts, faults). macOS hides ALL notifications while the screen is being
|
|
546
|
+
# recorded/shared unless this is on — and screenpipe records continuously, so
|
|
547
|
+
# without it every Meridian toast is silently suppressed. No API/prompt exists
|
|
548
|
+
# for this toggle, so we can only walk the user to it.
|
|
549
|
+
echo "→ Meridian's tray shows desktop notifications. Because screenpipe records"
|
|
550
|
+
echo " the screen, macOS hides notifications during screen sharing unless allowed."
|
|
551
|
+
read -r -p " Press Enter to open Notifications settings… " _ || true
|
|
552
|
+
open "x-apple.systempreferences:com.apple.Notifications-Settings.extension" 2>/dev/null || true
|
|
553
|
+
echo " → Scroll to the bottom and turn ON"
|
|
554
|
+
echo " 'Allow notifications when mirroring or sharing the display'."
|
|
555
|
+
echo " → When 'Meridian Tray' appears, ensure its notifications are allowed"
|
|
556
|
+
echo " (style Banners or Alerts, not None)."
|
|
557
|
+
read -r -p " Press Enter when done… " _ || true
|
|
543
558
|
fi
|
|
544
559
|
|
|
545
560
|
# Enable a11y mode in installed VS Code-family editors (idempotent). Without
|
|
@@ -106,8 +106,11 @@ def _format_candidates(tasks: list[dict]) -> str:
|
|
|
106
106
|
desc = desc[:240] + "…"
|
|
107
107
|
meta_parts = [p for p in [issue_type, f"Epic: {epic_title}" if epic_title else "", sprint_name, f"tags: {tags}" if tags else ""] if p]
|
|
108
108
|
meta = " [" + " · ".join(meta_parts) + "]" if meta_parts else ""
|
|
109
|
+
# The dev declared this ticket as today's focus on the plan page. It's a
|
|
110
|
+
# tie-breaking prior, not a forced answer — only matches if the evidence fits.
|
|
111
|
+
focus = " ★ TODAY'S FOCUS" if task.get("is_today_focus") else ""
|
|
109
112
|
rows.append(
|
|
110
|
-
f"{i}. {task['task_key']}{meta}\n"
|
|
113
|
+
f"{i}. {task['task_key']}{focus}{meta}\n"
|
|
111
114
|
f" title: {title}\n"
|
|
112
115
|
f" description: {desc or '(empty)'}"
|
|
113
116
|
)
|
|
@@ -152,12 +155,21 @@ def build_user_message(
|
|
|
152
155
|
f"{_format_recent_sessions(sessions)}\n"
|
|
153
156
|
"\n"
|
|
154
157
|
) if has_any_task_key else ""
|
|
158
|
+
# When the dev declared a focus for the day, name it in the header so the model
|
|
159
|
+
# treats ★ rows as a prior — preferred when the evidence plausibly fits, but
|
|
160
|
+
# never forced. Recall is preserved: every candidate is still listed.
|
|
161
|
+
has_focus = any(c.get("is_today_focus") for c in candidates)
|
|
162
|
+
candidate_header = (
|
|
163
|
+
"CANDIDATE TICKETS (★ = the dev declared this as a task they're working on "
|
|
164
|
+
"today; prefer a ★ ticket when the session plausibly matches it, but only "
|
|
165
|
+
"if the evidence fits — never force a match):\n"
|
|
166
|
+
) if has_focus else "CANDIDATE TICKETS:\n"
|
|
155
167
|
return (
|
|
156
168
|
f"{recent_block}"
|
|
157
169
|
"SESSION:\n"
|
|
158
170
|
f"{_format_session(session)}\n"
|
|
159
171
|
"\n"
|
|
160
|
-
"
|
|
172
|
+
f"{candidate_header}"
|
|
161
173
|
f"{_format_candidates(candidates)}"
|
|
162
174
|
)
|
|
163
175
|
|
|
@@ -24,6 +24,7 @@ Method tag in results: "mlx_direct".
|
|
|
24
24
|
"""
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import datetime as _dt
|
|
27
28
|
import json
|
|
28
29
|
import logging
|
|
29
30
|
import os
|
|
@@ -483,7 +484,53 @@ def _fetch_recent_sessions(
|
|
|
483
484
|
return result
|
|
484
485
|
|
|
485
486
|
|
|
486
|
-
def
|
|
487
|
+
def _local_day(started_at: str) -> str:
|
|
488
|
+
"""The local calendar day (YYYY-MM-DD) of a session's UTC `started_at`.
|
|
489
|
+
|
|
490
|
+
`daily_plan.plan_date` is the dev's *local* day (the dashboard stamps it from
|
|
491
|
+
the browser's local date), but `app_sessions.started_at` is stored UTC. We
|
|
492
|
+
convert UTC → local here so a session is matched to the plan the dev actually
|
|
493
|
+
declared for that day. Returns "" on an unparseable timestamp (→ no boost).
|
|
494
|
+
"""
|
|
495
|
+
if not started_at:
|
|
496
|
+
return ""
|
|
497
|
+
try:
|
|
498
|
+
# `astimezone()` with no arg converts an aware datetime to the host's
|
|
499
|
+
# local zone — the same zone the dashboard used to compute plan_date.
|
|
500
|
+
return _dt.datetime.fromisoformat(started_at).astimezone().date().isoformat()
|
|
501
|
+
except ValueError:
|
|
502
|
+
return ""
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _fetch_plan_focus(con: _sqlite3.Connection, plan_date: str) -> list[str]:
|
|
506
|
+
"""Ordered task_keys the dev CONFIRMED as their focus for `plan_date`.
|
|
507
|
+
|
|
508
|
+
Empty (→ no boost, classification proceeds exactly as before) when the day is
|
|
509
|
+
unconfirmed, explicitly skipped, has no plan rows, or the plan tables don't
|
|
510
|
+
exist yet (pre-migration-041 DB). This is a ranking signal only — never a
|
|
511
|
+
filter — so an empty result can only ever cost the boost, never recall.
|
|
512
|
+
"""
|
|
513
|
+
if not plan_date:
|
|
514
|
+
return []
|
|
515
|
+
try:
|
|
516
|
+
meta = con.execute(
|
|
517
|
+
"SELECT confirmed_at, skipped FROM daily_plan_meta WHERE plan_date = ?",
|
|
518
|
+
(plan_date,),
|
|
519
|
+
).fetchone()
|
|
520
|
+
if meta is None or meta["skipped"] or not meta["confirmed_at"]:
|
|
521
|
+
return []
|
|
522
|
+
rows = con.execute(
|
|
523
|
+
"SELECT task_key FROM daily_plan WHERE plan_date = ? ORDER BY position",
|
|
524
|
+
(plan_date,),
|
|
525
|
+
).fetchall()
|
|
526
|
+
return [r["task_key"] for r in rows]
|
|
527
|
+
except _sqlite3.OperationalError:
|
|
528
|
+
return []
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _fetch_pm_tasks(
|
|
532
|
+
con: _sqlite3.Connection, focus_keys: list[str] | None = None
|
|
533
|
+
) -> list[dict[str, Any]]:
|
|
487
534
|
# Candidate set for classification. Tickets the user explicitly EXCLUDED during
|
|
488
535
|
# onboarding board-cleanup (pm_task_curation.decision = 'excluded') are dropped
|
|
489
536
|
# so a cleaned-up dead ticket can never be a classification target. Everything
|
|
@@ -512,7 +559,20 @@ def _fetch_pm_tasks(con: _sqlite3.Connection) -> list[dict[str, Any]]:
|
|
|
512
559
|
# Pre-migration-038 DB (no pm_task_curation): degrade to the unfiltered
|
|
513
560
|
# candidate set rather than crashing the whole /classify_sessions call.
|
|
514
561
|
rows = con.execute(base_cols).fetchall()
|
|
515
|
-
|
|
562
|
+
tasks = [dict(r) for r in rows]
|
|
563
|
+
|
|
564
|
+
# Today's-focus boost: tag the tickets the dev declared for the day and float
|
|
565
|
+
# them to the top of the candidate list, in their declared order. This is a
|
|
566
|
+
# BOOST, never a filter — every other candidate still follows, so recall is
|
|
567
|
+
# untouched. A focus key that isn't in `tasks` (e.g. excluded by curation)
|
|
568
|
+
# simply has no effect; we never resurrect a filtered-out ticket.
|
|
569
|
+
focus = focus_keys or []
|
|
570
|
+
if focus:
|
|
571
|
+
order = {key: i for i, key in enumerate(focus)}
|
|
572
|
+
for t in tasks:
|
|
573
|
+
t["is_today_focus"] = t["task_key"] in order
|
|
574
|
+
tasks.sort(key=lambda t: (0, order[t["task_key"]]) if t.get("is_today_focus") else (1, 0))
|
|
575
|
+
return tasks
|
|
516
576
|
|
|
517
577
|
|
|
518
578
|
# ---------------------------------------------------------------------------
|
|
@@ -555,10 +615,13 @@ def _classify_one(
|
|
|
555
615
|
session_id, f"session {session_id} not found in DB", 0.0, "mlx_error"
|
|
556
616
|
)
|
|
557
617
|
|
|
558
|
-
|
|
559
|
-
|
|
618
|
+
plan_date = _local_day(session_raw.get("started_at") or "")
|
|
619
|
+
focus_keys = _fetch_plan_focus(con, plan_date)
|
|
620
|
+
pm_tasks = _fetch_pm_tasks(con, focus_keys)
|
|
621
|
+
recent = _fetch_recent_sessions(con, session_id)
|
|
560
622
|
|
|
561
623
|
db_span.set_attribute("pm_tasks_count", len(pm_tasks))
|
|
624
|
+
db_span.set_attribute("today_focus_count", len(focus_keys))
|
|
562
625
|
db_span.set_attribute("recent_sessions_count", len(recent))
|
|
563
626
|
|
|
564
627
|
session_text = session_raw.get("session_text") or ""
|
|
@@ -785,7 +848,8 @@ def _classify_one_logged(
|
|
|
785
848
|
"""Classify one session and append a full record to the run log."""
|
|
786
849
|
# Gather inputs before classification so we can log them even on error.
|
|
787
850
|
session_raw = _fetch_session(con, session_id)
|
|
788
|
-
|
|
851
|
+
focus_keys = _fetch_plan_focus(con, _local_day(session_raw.get("started_at") or "")) if session_raw else []
|
|
852
|
+
pm_tasks = _fetch_pm_tasks(con, focus_keys) if session_raw else []
|
|
789
853
|
recent = _fetch_recent_sessions(con, session_id) if session_raw else []
|
|
790
854
|
|
|
791
855
|
if session_raw:
|
package/services/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "meridian-agents"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.54.0"
|
|
8
8
|
description = "Meridian agents — MLX classifier server and Jira worklog synthesis for meridian.db"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
authors = [{ name = "Meridiona" }]
|
package/ui.tar.gz
CHANGED
|
Binary file
|