@kl-c/matrixos 0.3.54 → 0.3.56

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/dist/cli/index.js CHANGED
@@ -2163,7 +2163,7 @@ var package_default;
2163
2163
  var init_package = __esm(() => {
2164
2164
  package_default = {
2165
2165
  name: "@kl-c/matrixos",
2166
- version: "0.3.54",
2166
+ version: "0.3.56",
2167
2167
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2168
2168
  main: "./dist/index.js",
2169
2169
  types: "dist/index.d.ts",
@@ -189164,18 +189164,54 @@ ${row.description ? `Description: ${row.description}
189164
189164
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
189165
189165
  }
189166
189166
  },
189167
- async getIncidents(limit = 50) {
189167
+ getIncidents(limit = 50) {
189168
189168
  const lines = readJsonl("anti-loop.jsonl");
189169
189169
  if (lines.length === 0) {
189170
- return [];
189170
+ return Promise.resolve([]);
189171
189171
  }
189172
- return lines.slice(0, limit).map((l2) => {
189172
+ return Promise.resolve(lines.slice(0, limit).map((l2) => {
189173
189173
  try {
189174
189174
  return JSON.parse(l2);
189175
189175
  } catch {
189176
189176
  return null;
189177
189177
  }
189178
- }).filter(Boolean);
189178
+ }).filter(Boolean));
189179
+ },
189180
+ async getNotifications() {
189181
+ const notes = [];
189182
+ try {
189183
+ const board = await this.getKanbanTasks();
189184
+ for (const col of Object.values(board.columns)) {
189185
+ for (const t2 of col) {
189186
+ if (t2.status === "failed") {
189187
+ notes.push({
189188
+ id: `kanban-${t2.id}`,
189189
+ kind: "kanban_failed",
189190
+ title: `Kanban task failed: ${t2.title}`,
189191
+ detail: t2.agent ? `agent: ${t2.agent}` : "",
189192
+ ts: Date.parse(t2.updatedAt) || Date.parse(t2.createdAt) || 0
189193
+ });
189194
+ }
189195
+ }
189196
+ }
189197
+ } catch {}
189198
+ try {
189199
+ const incidents = readJsonl("anti-loop.jsonl").slice(0, 10);
189200
+ for (const line of incidents) {
189201
+ try {
189202
+ const inc = JSON.parse(line);
189203
+ notes.push({
189204
+ id: `incident-${inc.timestamp}`,
189205
+ kind: "incident",
189206
+ title: inc.trigger ?? "Incident",
189207
+ detail: inc.detail ?? inc.actionTaken ?? "",
189208
+ ts: inc.timestamp ?? 0
189209
+ });
189210
+ } catch {}
189211
+ }
189212
+ } catch {}
189213
+ notes.sort((a2, b2) => b2.ts - a2.ts);
189214
+ return notes;
189179
189215
  },
189180
189216
  async getMetrics() {
189181
189217
  const db = getDb();
@@ -189419,6 +189455,16 @@ function createDashboardServer(dataProvider, config5) {
189419
189455
  if (req.method === "OPTIONS") {
189420
189456
  return new Response(null, { headers: corsHeaders });
189421
189457
  }
189458
+ if (config5.dashboardPassphrase) {
189459
+ const isHealth = path25 === "/api/health";
189460
+ const isGatewayToken = path25 === "/api/gateway/token";
189461
+ if (path25.startsWith("/api/") && !isHealth && !isGatewayToken) {
189462
+ const provided = req.headers.get("x-passphrase");
189463
+ if (!provided || provided !== config5.dashboardPassphrase) {
189464
+ return Response.json({ error: "Unauthorized \u2014 dashboard passphrase required" }, { status: 401, headers: corsHeaders });
189465
+ }
189466
+ }
189467
+ }
189422
189468
  if (path25.startsWith("/api/")) {
189423
189469
  if (path25 === "/api/logs/stream") {
189424
189470
  const headers = new Headers({
@@ -189546,6 +189592,8 @@ function createDashboardServer(dataProvider, config5) {
189546
189592
  const result = await dataProvider.decomposeGoal(body.id);
189547
189593
  return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
189548
189594
  }
189595
+ case "/api/notifications":
189596
+ return Response.json(await dataProvider.getNotifications(), { headers: jsonHeaders });
189549
189597
  case "/api/incidents": {
189550
189598
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
189551
189599
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -189696,7 +189744,7 @@ async function dashboardCli(options) {
189696
189744
  console.log(`[dashboard] frontend: ${frontendDir}`);
189697
189745
  try {
189698
189746
  const dataProvider = createDataProvider();
189699
- const server2 = createDashboardServer(dataProvider, { port: port3, host, frontendDir, gatewayPassphrase });
189747
+ const server2 = createDashboardServer(dataProvider, { port: port3, host, frontendDir, gatewayPassphrase, dashboardPassphrase: gatewayPassphrase });
189700
189748
  server2.start();
189701
189749
  console.log(`[dashboard] ready \u2014 http://${host}:${port3}`);
189702
189750
  if (!options.daemon) {
@@ -2163,7 +2163,7 @@ var package_default;
2163
2163
  var init_package = __esm(() => {
2164
2164
  package_default = {
2165
2165
  name: "@kl-c/matrixos",
2166
- version: "0.3.54",
2166
+ version: "0.3.56",
2167
2167
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2168
2168
  main: "./dist/index.js",
2169
2169
  types: "dist/index.d.ts",
@@ -189219,18 +189219,54 @@ ${row.description ? `Description: ${row.description}
189219
189219
  return { ok: false, error: e instanceof Error ? e.message : String(e) };
189220
189220
  }
189221
189221
  },
189222
- async getIncidents(limit = 50) {
189222
+ getIncidents(limit = 50) {
189223
189223
  const lines = readJsonl("anti-loop.jsonl");
189224
189224
  if (lines.length === 0) {
189225
- return [];
189225
+ return Promise.resolve([]);
189226
189226
  }
189227
- return lines.slice(0, limit).map((l2) => {
189227
+ return Promise.resolve(lines.slice(0, limit).map((l2) => {
189228
189228
  try {
189229
189229
  return JSON.parse(l2);
189230
189230
  } catch {
189231
189231
  return null;
189232
189232
  }
189233
- }).filter(Boolean);
189233
+ }).filter(Boolean));
189234
+ },
189235
+ async getNotifications() {
189236
+ const notes = [];
189237
+ try {
189238
+ const board = await this.getKanbanTasks();
189239
+ for (const col of Object.values(board.columns)) {
189240
+ for (const t2 of col) {
189241
+ if (t2.status === "failed") {
189242
+ notes.push({
189243
+ id: `kanban-${t2.id}`,
189244
+ kind: "kanban_failed",
189245
+ title: `Kanban task failed: ${t2.title}`,
189246
+ detail: t2.agent ? `agent: ${t2.agent}` : "",
189247
+ ts: Date.parse(t2.updatedAt) || Date.parse(t2.createdAt) || 0
189248
+ });
189249
+ }
189250
+ }
189251
+ }
189252
+ } catch {}
189253
+ try {
189254
+ const incidents = readJsonl("anti-loop.jsonl").slice(0, 10);
189255
+ for (const line of incidents) {
189256
+ try {
189257
+ const inc = JSON.parse(line);
189258
+ notes.push({
189259
+ id: `incident-${inc.timestamp}`,
189260
+ kind: "incident",
189261
+ title: inc.trigger ?? "Incident",
189262
+ detail: inc.detail ?? inc.actionTaken ?? "",
189263
+ ts: inc.timestamp ?? 0
189264
+ });
189265
+ } catch {}
189266
+ }
189267
+ } catch {}
189268
+ notes.sort((a2, b2) => b2.ts - a2.ts);
189269
+ return notes;
189234
189270
  },
189235
189271
  async getMetrics() {
189236
189272
  const db = getDb();
@@ -189474,6 +189510,16 @@ function createDashboardServer(dataProvider, config5) {
189474
189510
  if (req.method === "OPTIONS") {
189475
189511
  return new Response(null, { headers: corsHeaders });
189476
189512
  }
189513
+ if (config5.dashboardPassphrase) {
189514
+ const isHealth = path25 === "/api/health";
189515
+ const isGatewayToken = path25 === "/api/gateway/token";
189516
+ if (path25.startsWith("/api/") && !isHealth && !isGatewayToken) {
189517
+ const provided = req.headers.get("x-passphrase");
189518
+ if (!provided || provided !== config5.dashboardPassphrase) {
189519
+ return Response.json({ error: "Unauthorized \u2014 dashboard passphrase required" }, { status: 401, headers: corsHeaders });
189520
+ }
189521
+ }
189522
+ }
189477
189523
  if (path25.startsWith("/api/")) {
189478
189524
  if (path25 === "/api/logs/stream") {
189479
189525
  const headers = new Headers({
@@ -189601,6 +189647,8 @@ function createDashboardServer(dataProvider, config5) {
189601
189647
  const result = await dataProvider.decomposeGoal(body.id);
189602
189648
  return Response.json(result, { status: result.ok ? 200 : 500, headers: jsonHeaders });
189603
189649
  }
189650
+ case "/api/notifications":
189651
+ return Response.json(await dataProvider.getNotifications(), { headers: jsonHeaders });
189604
189652
  case "/api/incidents": {
189605
189653
  const limit = parseInt(url3.searchParams.get("limit") ?? "50", 10);
189606
189654
  return Response.json(await dataProvider.getIncidents(limit), { headers: jsonHeaders });
@@ -189751,7 +189799,7 @@ async function dashboardCli(options) {
189751
189799
  console.log(`[dashboard] frontend: ${frontendDir}`);
189752
189800
  try {
189753
189801
  const dataProvider = createDataProvider();
189754
- const server2 = createDashboardServer(dataProvider, { port: port3, host, frontendDir, gatewayPassphrase });
189802
+ const server2 = createDashboardServer(dataProvider, { port: port3, host, frontendDir, gatewayPassphrase, dashboardPassphrase: gatewayPassphrase });
189755
189803
  server2.start();
189756
189804
  console.log(`[dashboard] ready \u2014 http://${host}:${port3}`);
189757
189805
  if (!options.daemon) {
@@ -1,5 +1,5 @@
1
1
  import { type CronJob } from "../cron/store";
2
- import type { AgentStatus, SessionSummary, CostSummary, KanbanBoard, Goal, IncidentEntry, HealthResponse, MetricsSnapshot } from "./types";
2
+ import type { AgentStatus, SessionSummary, CostSummary, KanbanBoard, Goal, IncidentEntry, HealthResponse, MetricsSnapshot, NotificationEntry } from "./types";
3
3
  export interface DataProviderConfig {
4
4
  matrixosHome: string;
5
5
  }
@@ -89,6 +89,7 @@ export interface DataProvider {
89
89
  ok: boolean;
90
90
  error?: string;
91
91
  };
92
+ getNotifications(): Promise<NotificationEntry[]>;
92
93
  getIncidents(limit?: number): Promise<IncidentEntry[]>;
93
94
  getMetrics(range?: string): Promise<MetricsSnapshot[]>;
94
95
  getLogs(): LogEntry[];
@@ -419,3 +419,13 @@ body{font-family:var(--font-body);font-size:var(--text-base);color:var(--text-pr
419
419
  .schedule-table td{padding:10px 12px;border-bottom:1px solid var(--border-subtle);color:var(--text-primary)}
420
420
  .schedule-table code{background:var(--bg-code);padding:2px 6px;border-radius:4px;font-size:11px;color:var(--text-secondary)}
421
421
  .badge-green{background:rgba(16,185,129,.15);color:#10b981}
422
+
423
+ .notif-panel{position:absolute;top:52px;right:16px;width:320px;max-height:420px;overflow-y:auto;background:var(--bg-surface,#1a1d23);border:1px solid var(--border-default,#2a2e36);border-radius:10px;box-shadow:0 10px 30px rgba(0,0,0,.4);z-index:50;padding:8px}
424
+ .notif-empty{color:var(--text-tertiary);padding:16px;text-align:center;font-size:var(--text-sm)}
425
+ .notif-item{padding:10px 12px;border-bottom:1px solid var(--border-subtle,#222);border-left:3px solid var(--accent,#4a9eff)}
426
+ .notif-item:last-child{border-bottom:none}
427
+ .notif-kanban_failed{border-left-color:#ff6b6b}
428
+ .notif-incident{border-left-color:#ffa94d}
429
+ .notif-title{font-size:var(--text-sm);color:var(--text-primary);font-weight:600}
430
+ .notif-detail{font-size:var(--text-xs);color:var(--text-secondary);margin-top:2px}
431
+ .notif-time{font-size:var(--text-xs);color:var(--text-tertiary);margin-top:4px}
@@ -52,7 +52,8 @@
52
52
  </div>
53
53
  <div class="header-right">
54
54
  <div class="header-search"><svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="6" cy="6" r="4.5"/><path d="M9.5 9.5L13 13"/></svg>Search agents, sessions...<kbd>Ctrl+K</kbd></div>
55
- <button class="header-icon-btn" id="notifBtn"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M12 6a4 4 0 10-8 0c0 4-2 5-2 5h12s-2-1-2-5"/><path d="M6.5 14a1.5 1.5 0 003 0"/></svg><span class="notif-dot"></span></button>
55
+ <button class="header-icon-btn" id="notifBtn"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M12 6a4 4 0 10-8 0c0 4-2 5-2 5h12s-2-1-2-5"/><path d="M6.5 14a1.5 1.5 0 003 0"/></svg><span class="notif-dot" style="display:none"></span></button>
56
+ <div class="notif-panel" id="notifPanel" style="display:none"></div>
56
57
  <button class="header-icon-btn" id="refreshBtn"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><polyline points="14 3 14 8 9 8"/><path d="M12.5 11.5A6 6 0 104 4L2 6"/></svg></button>
57
58
  </div>
58
59
  </header>
@@ -20,10 +20,23 @@ const API={
20
20
  cronList:()=>fetchJSON('/cron'),
21
21
  cronAdd:(body)=>fetchJSON('/cron',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}),
22
22
  cronRemove:(id)=>fetchJSON(`/cron?id=${encodeURIComponent(id)}`,{method:'DELETE'}),
23
+ notifications:()=>fetchJSON('/notifications'),
23
24
  gatewayToken:(type,token,passphrase)=>fetch('/api/gateway/token',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({type,token,passphrase})}).then(r=>r.json())
24
25
  }
25
- async function fetchJSON(e,opts){
26
- const r=await fetch(`/api${e}`,opts)
27
- if(!r.ok)throw new Error(`API ${e}: ${r.status}`)
26
+ async function fetchJSON(e, opts) {
27
+ opts = opts || {}
28
+ opts.headers = Object.assign({ "x-passphrase": localStorage.getItem("matrixos_dashboard_passphrase") || "" }, opts.headers || {})
29
+ const r = await fetch(`/api${e}`, opts)
30
+ if (r.status === 401) {
31
+ const tried = localStorage.getItem("matrixos_dashboard_passphrase")
32
+ if (!tried) {
33
+ const p = prompt("Dashboard passphrase required:")
34
+ if (p) {
35
+ localStorage.setItem("matrixos_dashboard_passphrase", p)
36
+ return fetchJSON(e, opts)
37
+ }
38
+ }
39
+ }
40
+ if (!r.ok) throw new Error(`API ${e}: ${r.status}`)
28
41
  return r.json()
29
42
  }
@@ -715,7 +715,31 @@ document.getElementById('newSkillBtn').onclick=()=>openSkillModal()
715
715
  document.getElementById('newCronBtn').onclick=()=>openCronModal()
716
716
  document.getElementById('deployBtn').onclick=()=>alert('Deploy Agent — coming soon')
717
717
  document.getElementById('diagBtn').onclick=()=>alert('Diagnostics — coming soon')
718
- document.getElementById('notifBtn').onclick=()=>alert('3 notifications pending')
718
+ document.getElementById('notifBtn').onclick=(e)=>{e.stopPropagation();toggleNotifications()}
719
+ async function loadNotifications(){
720
+ try{
721
+ const notes=await API.notifications()
722
+ const dot=document.querySelector('#notifBtn .notif-dot')
723
+ if(dot)dot.style.display=notes.length?'block':'none'
724
+ const panel=document.getElementById('notifPanel')
725
+ if(!notes.length){panel.innerHTML='<div class="notif-empty">No notifications</div>';return}
726
+ panel.innerHTML=notes.map(n=>`<div class="notif-item notif-${n.kind}">
727
+ <div class="notif-title">${n.title}</div>
728
+ ${n.detail?`<div class="notif-detail">${n.detail}</div>`:''}
729
+ <div class="notif-time">${new Date(n.ts).toLocaleString()}</div>
730
+ </div>`).join('')
731
+ }catch(e){/* ignore */}
732
+ }
733
+ function toggleNotifications(){
734
+ const panel=document.getElementById('notifPanel')
735
+ if(panel.style.display==='block'){panel.style.display='none';return}
736
+ panel.style.display='block'
737
+ loadNotifications()
738
+ }
739
+ document.addEventListener('click',(e)=>{
740
+ const panel=document.getElementById('notifPanel')
741
+ if(panel&&!panel.contains(e.target)&&e.target.id!=='notifBtn')panel.style.display='none'
742
+ })
719
743
  const createGoalBtn=document.getElementById('createGoalBtn')
720
744
  if(createGoalBtn)createGoalBtn.onclick=createGoal
721
745
  const refreshGoalsBtn=document.getElementById('refreshGoalsBtn')
@@ -724,4 +748,5 @@ const newKanbanBtn=document.getElementById('newKanbanBtn')
724
748
  if(newKanbanBtn)newKanbanBtn.onclick=()=>openKanbanModal()
725
749
 
726
750
  loadTab('architect')
751
+ loadNotifications()
727
752
  setInterval(()=>{const t=document.querySelector('.header-tab.active');t&&loadTab(t.dataset.tab)},30000)
@@ -5,6 +5,8 @@ export interface DashboardServerConfig {
5
5
  frontendDir: string;
6
6
  /** Passphrase required to submit gateway tokens via POST /api/gateway/token */
7
7
  gatewayPassphrase?: string;
8
+ /** Global passphrase required for ALL /api/* routes (except /api/health) when set */
9
+ dashboardPassphrase?: string;
8
10
  }
9
11
  export declare function createDashboardServer(dataProvider: DataProvider, config: DashboardServerConfig): {
10
12
  start(): void;
@@ -69,6 +69,13 @@ export interface IncidentEntry {
69
69
  outcome: string;
70
70
  detail?: string;
71
71
  }
72
+ export interface NotificationEntry {
73
+ id: string;
74
+ kind: "kanban_failed" | "incident";
75
+ title: string;
76
+ detail: string;
77
+ ts: number;
78
+ }
72
79
  export interface HealthResponse {
73
80
  agentsOnline: number;
74
81
  totalAgents: number;
package/dist/index.js CHANGED
@@ -368086,7 +368086,7 @@ function getCachedVersion(options = {}) {
368086
368086
  // package.json
368087
368087
  var package_default = {
368088
368088
  name: "@kl-c/matrixos",
368089
- version: "0.3.54",
368089
+ version: "0.3.56",
368090
368090
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
368091
368091
  main: "./dist/index.js",
368092
368092
  types: "dist/index.d.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kl-c/matrixos",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "description": "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",