@kl-c/matrixos 0.1.16 → 0.1.18

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.
@@ -16,7 +16,15 @@ Check whether `/root/MOS_Vault/04-Areas/user-context.md` exists and is filled.
16
16
  - If filled: tell the user "Tu as déjà un contexte d'adoption. Je relance le rituel pour le mettre à jour." and proceed to step 2 (re-adoption mode — EDIT sections, do NOT rewrite from scratch).
17
17
  - If missing/template: adoption mode — you will CREATE the file.
18
18
 
19
- ### 2. Ask the adoption questions (in conversation, French, interactive `question` tool)
19
+ ### 2. Inform the user of base capabilities (DO THIS EARLY)
20
+ Before or right after the questions, tell the user what MaTrixOS already gives them out of the box — so they know what they have at their disposal without having to ask:
21
+ - **Mission Control (Dashboard)** — real-time web UI (agents, sessions, tasks, cost, incidents). Launch anytime with `matrixos dashboard --daemon` (http://127.0.0.1:9123; `--host <tailscale-ip>` for remote).
22
+ - **16-agent team** — Morpheus + specialists (Neo, Tank, Oracle, Trinity, Cypher, Sentinel, etc.). Delegate, don't do it all yourself.
23
+ - **Messaging Gateway** (opt-in) — drive Morpheus from Telegram/Discord/WhatsApp.
24
+ - **Learning Loop** + **Team Mode** (opt-in).
25
+ Keep it to one short paragraph — this is awareness, not the opt-in checklist (that's step 3).
26
+
27
+ ### 3. Ask the adoption questions (in conversation, French, interactive `question` tool)
20
28
  Pose ~5 targeted questions:
21
29
  1. **Qui es-tu ?** (nom/alias, rôle, ce que tu fais)
22
30
  2. **Pour quel projet / besoin m'as-tu déployé ?** (objectif, stack, périmètre)
@@ -16,7 +16,15 @@ Check whether `/root/MOS_Vault/04-Areas/user-context.md` exists and is filled.
16
16
  - If filled: tell the user "Tu as déjà un contexte d'adoption. Je relance le rituel pour le mettre à jour." and proceed to step 2 (re-adoption mode — EDIT sections, do NOT rewrite from scratch).
17
17
  - If missing/template: adoption mode — you will CREATE the file.
18
18
 
19
- ### 2. Ask the adoption questions (in conversation, French, interactive `question` tool)
19
+ ### 2. Inform the user of base capabilities (DO THIS EARLY)
20
+ Before or right after the questions, tell the user what MaTrixOS already gives them out of the box — so they know what they have at their disposal without having to ask:
21
+ - **Mission Control (Dashboard)** — real-time web UI (agents, sessions, tasks, cost, incidents). Launch anytime with `matrixos dashboard --daemon` (http://127.0.0.1:9123; `--host <tailscale-ip>` for remote).
22
+ - **16-agent team** — Morpheus + specialists (Neo, Tank, Oracle, Trinity, Cypher, Sentinel, etc.). Delegate, don't do it all yourself.
23
+ - **Messaging Gateway** (opt-in) — drive Morpheus from Telegram/Discord/WhatsApp.
24
+ - **Learning Loop** + **Team Mode** (opt-in).
25
+ Keep it to one short paragraph — this is awareness, not the opt-in checklist (that's step 3).
26
+
27
+ ### 3. Ask the adoption questions (in conversation, French, interactive `question` tool)
20
28
  Pose ~5 targeted questions:
21
29
  1. **Qui es-tu ?** (nom/alias, rôle, ce que tu fais)
22
30
  2. **Pour quel projet / besoin m'as-tu déployé ?** (objectif, stack, périmètre)
@@ -22,3 +22,12 @@ export declare function buildOracleSection(agents: AvailableAgent[]): string;
22
22
  export declare function buildFrontendGuidanceSection(categories: AvailableCategory[]): string;
23
23
  export declare function buildNonClaudePlannerSection(model: string): string;
24
24
  export declare function buildParallelDelegationSection(model: string, categories: AvailableCategory[]): string;
25
+ /**
26
+ * Base capabilities awareness — what Morpheus knows about out of the box,
27
+ * without needing to read features.ts or run /features. The Dashboard
28
+ * (Mission Control) backend is always bundled; the user can launch it
29
+ * immediately. Listed here so Morpheus is never "lost" when the user
30
+ * mentions these features in conversation (VPS cantabo bug: Morpheus had
31
+ * to grep the codebase to discover the dashboard existed).
32
+ */
33
+ export declare function buildBaseCapabilitiesSection(): string;
@@ -2,6 +2,7 @@ import type { AvailableAgent, AvailableCategory, AvailableSkill, AvailableTool }
2
2
  export interface MorpheusDynamicPromptSections {
3
3
  readonly agentIdentity: string;
4
4
  readonly agentTeam: string;
5
+ readonly baseCapabilities: string;
5
6
  readonly antiPatterns: string;
6
7
  readonly categorySkillsGuide: string;
7
8
  readonly delegationTable: string;
package/dist/cli/index.js CHANGED
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.16",
2148
+ version: "0.1.18",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -141031,101 +141031,55 @@ var AGENT_ROLES = {
141031
141031
  Neo: "Executor",
141032
141032
  Tank: "Deep Work"
141033
141033
  };
141034
+ var OPENCODE_DB_PATH = path24.resolve(os12.homedir(), ".local", "share", "opencode", "opencode.db");
141035
+ var THIRTY_MIN_MS = 30 * 60 * 1000;
141036
+ var FIVE_MIN_MS = 5 * 60 * 1000;
141037
+ var BUDGET_TOTAL = 5000;
141034
141038
  function randomBetween(min2, max2) {
141035
141039
  return Math.floor(Math.random() * (max2 - min2 + 1)) + min2;
141036
141040
  }
141037
141041
  function randomChoice(arr) {
141038
141042
  return arr[Math.floor(Math.random() * arr.length)];
141039
141043
  }
141040
- function generateMockAgents() {
141041
- return AGENT_NAMES.map((name2) => ({
141042
- name: name2,
141043
- role: AGENT_ROLES[name2] ?? "Agent",
141044
- status: randomChoice(["online", "online", "online", "idle", "idle", "offline"]),
141045
- sessions: randomBetween(0, 25),
141046
- uptime: randomBetween(100, 86400),
141047
- cpuPercent: randomBetween(2, 85),
141048
- memoryPercent: randomBetween(10, 75)
141049
- }));
141044
+ function parseModel(modelRaw) {
141045
+ if (!modelRaw)
141046
+ return "unknown";
141047
+ try {
141048
+ const parsed = JSON.parse(modelRaw);
141049
+ if (parsed.id)
141050
+ return parsed.id;
141051
+ } catch {}
141052
+ return modelRaw;
141053
+ }
141054
+ function getDb() {
141055
+ if (!fs20.existsSync(OPENCODE_DB_PATH))
141056
+ return;
141057
+ try {
141058
+ return new Database5(OPENCODE_DB_PATH, { readonly: true });
141059
+ } catch {
141060
+ return;
141061
+ }
141050
141062
  }
141051
- function generateMockSessions(count) {
141052
- const models2 = ["deepseek-v4-flash-free", "gpt-5.5-pro", "claude-opus-4-7", "gemini-3.1-pro", "glm-5.2"];
141053
- return Array.from({ length: count }, (_2, i3) => ({
141054
- id: `ses_${randomBetween(1e5, 999999).toString(16)}`,
141055
- agent: randomChoice(AGENT_NAMES),
141056
- model: randomChoice(models2),
141057
- duration: randomBetween(5, 600),
141058
- cost: Math.round(randomBetween(1, 500) * 100) / 100,
141059
- status: randomChoice(["active", "completed", "completed", "completed", "failed", "timeout"]),
141060
- startedAt: new Date(Date.now() - randomBetween(0, 86400000)).toISOString()
141061
- }));
141063
+ function midnightMs() {
141064
+ const d = new Date;
141065
+ d.setHours(0, 0, 0, 0);
141066
+ return d.getTime();
141062
141067
  }
141063
- function generateMockCost() {
141064
- const agents = AGENT_NAMES.map((name2) => ({
141065
- agent: name2,
141066
- cost: Math.round(randomBetween(50, 2000) * 100) / 100,
141067
- percentage: 0
141068
- }));
141069
- const total = agents.reduce((s, a2) => s + a2.cost, 0);
141070
- agents.forEach((a2) => {
141071
- a2.percentage = Math.round(a2.cost / total * 100);
141072
- });
141073
- return {
141074
- total: Math.round(total * 100) / 100,
141075
- daily: Math.round(randomBetween(50, 300) * 100) / 100,
141076
- monthly: Math.round(randomBetween(1500, 9000) * 100) / 100,
141077
- budgetRemaining: Math.round(randomBetween(500, 5000) * 100) / 100,
141078
- byAgent: agents,
141079
- byDay: Array.from({ length: 30 }, (_2, i3) => ({
141080
- date: new Date(Date.now() - (29 - i3) * 86400000).toISOString().slice(0, 10),
141081
- cost: Math.round(randomBetween(20, 300) * 100) / 100
141082
- })),
141083
- alerts: [
141084
- { severity: "warning", message: "Morpheus approaching budget threshold (85%)" },
141085
- { severity: "critical", message: "Tank exceeded daily budget cap ($500)" }
141086
- ]
141087
- };
141068
+ function thirtyDaysAgoMs() {
141069
+ return Date.now() - 30 * 24 * 60 * 60 * 1000;
141088
141070
  }
141089
- function generateMockKanban() {
141090
- const statuses = ["pending", "in_progress", "completed", "failed"];
141091
- const columns = {};
141092
- for (const s of statuses) {
141093
- columns[s] = Array.from({ length: randomBetween(2, 6) }, (_2, i3) => ({
141094
- id: `task_${randomBetween(1000, 9999)}`,
141095
- title: randomChoice([
141096
- "Implement Watchdog daemon",
141097
- "Design System tokens",
141098
- "API rate limiting",
141099
- "Session persistence",
141100
- "User auth flow",
141101
- "Database migration",
141102
- "Cache invalidation",
141103
- "Webhook integration",
141104
- "Telegram notifications",
141105
- "Cost optimization",
141106
- "Security audit",
141107
- "Performance profiling"
141108
- ]),
141109
- agent: randomChoice(AGENT_NAMES),
141110
- priority: randomChoice(["low", "medium", "high", "critical"]),
141111
- status: s,
141112
- createdAt: new Date(Date.now() - randomBetween(0, 604800000)).toISOString(),
141113
- updatedAt: new Date(Date.now() - randomBetween(0, 86400000)).toISOString()
141114
- }));
141071
+ function readPackageVersion2() {
141072
+ try {
141073
+ const pkgPath = new URL("../../../../package.json", import.meta.url).pathname;
141074
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
141075
+ return pkg.version ?? "0.0.0";
141076
+ } catch {
141077
+ return "0.0.0";
141115
141078
  }
141116
- return { columns };
141117
- }
141118
- function generateMockMetrics() {
141119
- return Array.from({ length: 60 }, (_2, i3) => ({
141120
- timestamp: Date.now() - (59 - i3) * 60000,
141121
- cpuPercent: randomBetween(10, 95),
141122
- memoryPercent: randomBetween(30, 85),
141123
- diskPercent: randomBetween(40, 70),
141124
- sessionsActive: randomBetween(1, 8)
141125
- }));
141126
141079
  }
141127
141080
  function createDataProvider(config4) {
141128
141081
  const matrixosHome = config4?.matrixosHome ?? ".matrixos";
141082
+ const version3 = readPackageVersion2();
141129
141083
  function readJsonl(filePath) {
141130
141084
  try {
141131
141085
  const fullPath = path24.resolve(matrixosHome, "logs", filePath);
@@ -141141,13 +141095,33 @@ function createDataProvider(config4) {
141141
141095
  async getHealth() {
141142
141096
  const mem = process.memoryUsage();
141143
141097
  const totalMem = os12.totalmem();
141098
+ const db = getDb();
141099
+ let agentsOnline = AGENT_NAMES.length;
141100
+ let activeSessions = 0;
141101
+ let tasksQueued = 0;
141102
+ if (db) {
141103
+ try {
141104
+ const agentRow = db.query("SELECT COUNT(DISTINCT agent) as count FROM session").get();
141105
+ if (agentRow)
141106
+ agentsOnline = agentRow.count;
141107
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141108
+ const sessionRows = db.query("SELECT COUNT(*) as count FROM session WHERE time_archived IS NULL AND time_updated > ?").all(cutoff);
141109
+ if (sessionRows.length > 0)
141110
+ activeSessions = sessionRows[0].count;
141111
+ const todoRows = db.query("SELECT COUNT(*) as count FROM todo WHERE status = 'pending'").all();
141112
+ if (todoRows.length > 0)
141113
+ tasksQueued = todoRows[0].count;
141114
+ } catch {} finally {
141115
+ db.close();
141116
+ }
141117
+ }
141144
141118
  return {
141145
- agentsOnline: AGENT_NAMES.length - randomBetween(1, 3),
141119
+ agentsOnline,
141146
141120
  totalAgents: AGENT_NAMES.length,
141147
- activeSessions: randomBetween(2, 8),
141148
- tasksQueued: randomBetween(5, 20),
141121
+ activeSessions,
141122
+ tasksQueued,
141149
141123
  uptime: process.uptime(),
141150
- version: "0.1.0",
141124
+ version: version3,
141151
141125
  memoryUsage: {
141152
141126
  used: Math.round(mem.rss / 1024 / 1024),
141153
141127
  total: Math.round(totalMem / 1024 / 1024),
@@ -141157,16 +141131,181 @@ function createDataProvider(config4) {
141157
141131
  };
141158
141132
  },
141159
141133
  async getAgents() {
141160
- return generateMockAgents();
141134
+ const db = getDb();
141135
+ if (db) {
141136
+ try {
141137
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141138
+ const rows = db.query(`SELECT agent, COUNT(*) as sessions
141139
+ FROM session
141140
+ WHERE agent IS NOT NULL AND agent != ''
141141
+ GROUP BY agent`).all();
141142
+ if (rows.length > 0) {
141143
+ const result = [];
141144
+ for (const row of rows) {
141145
+ const name2 = row.agent;
141146
+ const onlineRows = db.query("SELECT COUNT(*) as count FROM session WHERE agent = ? AND time_updated > ?").all(name2, cutoff);
141147
+ const status2 = onlineRows.length > 0 && onlineRows[0].count > 0 ? "online" : "idle";
141148
+ result.push({
141149
+ name: name2,
141150
+ role: AGENT_ROLES[name2] ?? "Agent",
141151
+ status: status2,
141152
+ sessions: row.sessions,
141153
+ uptime: 0,
141154
+ cpuPercent: 0,
141155
+ memoryPercent: 0
141156
+ });
141157
+ }
141158
+ db.close();
141159
+ return result;
141160
+ }
141161
+ } catch {} finally {
141162
+ db.close();
141163
+ }
141164
+ }
141165
+ return AGENT_NAMES.map((name2) => ({
141166
+ name: name2,
141167
+ role: AGENT_ROLES[name2] ?? "Agent",
141168
+ status: "idle",
141169
+ sessions: 0,
141170
+ uptime: 0,
141171
+ cpuPercent: 0,
141172
+ memoryPercent: 0
141173
+ }));
141161
141174
  },
141162
141175
  async getSessions(limit = 20) {
141163
- return generateMockSessions(limit).slice(0, limit);
141176
+ const db = getDb();
141177
+ if (!db)
141178
+ return [];
141179
+ try {
141180
+ const now = Date.now();
141181
+ const rows = db.query(`SELECT id, title, agent, model, cost, time_created, time_updated, time_archived
141182
+ FROM session
141183
+ ORDER BY time_updated DESC
141184
+ LIMIT ?`).all(limit);
141185
+ const result = [];
141186
+ for (const row of rows) {
141187
+ const duration3 = Math.max(0, Math.round((row.time_updated - row.time_created) / 1000));
141188
+ let status2 = "completed";
141189
+ if (row.time_archived !== null) {
141190
+ status2 = "completed";
141191
+ } else if (row.time_updated > now - FIVE_MIN_MS) {
141192
+ status2 = "active";
141193
+ } else {
141194
+ status2 = "completed";
141195
+ }
141196
+ result.push({
141197
+ id: row.id,
141198
+ agent: row.agent ?? "unknown",
141199
+ model: parseModel(row.model),
141200
+ duration: duration3,
141201
+ cost: row.cost,
141202
+ status: status2,
141203
+ startedAt: new Date(row.time_created).toISOString()
141204
+ });
141205
+ }
141206
+ return result;
141207
+ } catch {
141208
+ return [];
141209
+ } finally {
141210
+ db.close();
141211
+ }
141164
141212
  },
141165
141213
  async getCost() {
141166
- return generateMockCost();
141214
+ const db = getDb();
141215
+ if (!db) {
141216
+ return {
141217
+ total: 0,
141218
+ daily: 0,
141219
+ monthly: 0,
141220
+ budgetRemaining: BUDGET_TOTAL,
141221
+ byAgent: [],
141222
+ byDay: [],
141223
+ alerts: []
141224
+ };
141225
+ }
141226
+ try {
141227
+ const totalRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session").all();
141228
+ const total = totalRows.length > 0 ? totalRows[0].sum ?? 0 : 0;
141229
+ const dailyRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session WHERE time_created > ?").all(midnightMs());
141230
+ const daily = dailyRows.length > 0 ? dailyRows[0].sum ?? 0 : 0;
141231
+ const monthlyRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session WHERE time_created > ?").all(thirtyDaysAgoMs());
141232
+ const monthly = monthlyRows.length > 0 ? monthlyRows[0].sum ?? 0 : 0;
141233
+ const agentRows = db.query(`SELECT agent, COALESCE(SUM(cost), 0) as cost
141234
+ FROM session
141235
+ WHERE agent IS NOT NULL AND agent != ''
141236
+ GROUP BY agent`).all();
141237
+ const byAgent = agentRows.map((r2) => ({
141238
+ agent: r2.agent,
141239
+ cost: r2.cost,
141240
+ percentage: total > 0 ? Math.round(r2.cost / total * 100) : 0
141241
+ }));
141242
+ const byDayRows = db.query(`SELECT strftime('%Y-%m-%d', time_created / 1000, 'unixepoch') as day,
141243
+ COALESCE(SUM(cost), 0) as cost
141244
+ FROM session
141245
+ WHERE time_created > ?
141246
+ GROUP BY day
141247
+ ORDER BY day ASC`).all(thirtyDaysAgoMs());
141248
+ const byDayMap = new Map;
141249
+ for (const r2 of byDayRows) {
141250
+ byDayMap.set(r2.day, r2.cost);
141251
+ }
141252
+ const byDay = [];
141253
+ const today = new Date;
141254
+ for (let i3 = 29;i3 >= 0; i3--) {
141255
+ const d = new Date(today);
141256
+ d.setDate(d.getDate() - i3);
141257
+ const key = d.toISOString().slice(0, 10);
141258
+ byDay.push({ date: key, cost: byDayMap.get(key) ?? 0 });
141259
+ }
141260
+ const budgetRemaining = Math.max(0, BUDGET_TOTAL - total);
141261
+ const alerts = [];
141262
+ if (total > BUDGET_TOTAL) {
141263
+ alerts.push({ severity: "critical", message: `Total cost $${total.toFixed(2)} exceeds budget $${BUDGET_TOTAL}` });
141264
+ } else if (total > BUDGET_TOTAL * 0.85) {
141265
+ alerts.push({ severity: "warning", message: `Cost $${total.toFixed(2)} approaching budget threshold (85%)` });
141266
+ }
141267
+ return { total, daily, monthly, budgetRemaining, byAgent, byDay, alerts };
141268
+ } catch {
141269
+ return {
141270
+ total: 0,
141271
+ daily: 0,
141272
+ monthly: 0,
141273
+ budgetRemaining: BUDGET_TOTAL,
141274
+ byAgent: [],
141275
+ byDay: [],
141276
+ alerts: []
141277
+ };
141278
+ } finally {
141279
+ db.close();
141280
+ }
141167
141281
  },
141168
141282
  async getKanbanTasks() {
141169
- return generateMockKanban();
141283
+ const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
141284
+ const db = getDb();
141285
+ if (!db)
141286
+ return board;
141287
+ try {
141288
+ const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
141289
+ let idx = 0;
141290
+ for (const row of rows) {
141291
+ const status2 = row.status;
141292
+ if (!board.columns[status2])
141293
+ continue;
141294
+ const task2 = {
141295
+ id: `todo_${idx++}`,
141296
+ title: row.content,
141297
+ agent: "Unknown",
141298
+ priority: row.priority ?? "medium",
141299
+ status: status2,
141300
+ createdAt: new Date(row.time_created).toISOString(),
141301
+ updatedAt: new Date(row.time_updated).toISOString()
141302
+ };
141303
+ board.columns[status2].push(task2);
141304
+ }
141305
+ } catch {} finally {
141306
+ db.close();
141307
+ }
141308
+ return board;
141170
141309
  },
141171
141310
  async getIncidents(limit = 50) {
141172
141311
  const lines = readJsonl("anti-loop.jsonl");
@@ -141190,7 +141329,33 @@ function createDataProvider(config4) {
141190
141329
  }).filter(Boolean);
141191
141330
  },
141192
141331
  async getMetrics() {
141193
- return generateMockMetrics();
141332
+ const db = getDb();
141333
+ let activeSessions = 0;
141334
+ if (db) {
141335
+ try {
141336
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141337
+ const rows = db.query("SELECT COUNT(*) as count FROM session WHERE time_archived IS NULL AND time_updated > ?").all(cutoff);
141338
+ if (rows.length > 0)
141339
+ activeSessions = rows[0].count;
141340
+ } catch {} finally {
141341
+ db.close();
141342
+ }
141343
+ }
141344
+ const totalMem = os12.totalmem();
141345
+ const mem = process.memoryUsage();
141346
+ const memPercent = Math.round(mem.rss / totalMem * 100);
141347
+ const cpuPercent = Math.round(os12.loadavg()[0] * 100);
141348
+ const snapshots = [];
141349
+ for (let i3 = 0;i3 < 60; i3++) {
141350
+ snapshots.push({
141351
+ timestamp: Date.now() - (59 - i3) * 60000,
141352
+ cpuPercent,
141353
+ memoryPercent: memPercent,
141354
+ diskPercent: 0,
141355
+ sessionsActive: activeSessions
141356
+ });
141357
+ }
141358
+ return snapshots;
141194
141359
  },
141195
141360
  getLogs() {
141196
141361
  const logFiles = [
@@ -141537,7 +141702,7 @@ function createDashboardServer(dataProvider, config4) {
141537
141702
  // packages/omo-opencode/src/cli/dashboard.ts
141538
141703
  async function dashboardCli(options) {
141539
141704
  const port = options.port ?? 9123;
141540
- const host = options.host ?? "127.0.0.1";
141705
+ const host = options.host ?? "0.0.0.0";
141541
141706
  const frontendDir = path25.resolve(import.meta.dir, "../features/dashboard/frontend");
141542
141707
  console.log(`[dashboard] starting MaTrixOS dashboard on http://${host}:${port}`);
141543
141708
  console.log(`[dashboard] frontend: ${frontendDir}`);
@@ -141859,7 +142024,7 @@ function configureRuntimeCommands(program2) {
141859
142024
  const exitCode = await architectRollback({ ...options, id });
141860
142025
  process.exit(exitCode);
141861
142026
  });
141862
- program2.command("dashboard").description("Start the MaTrixOS web dashboard (port 9123)").option("-p, --port <number>", "HTTP port", "9123").option("--host <addr>", "Bind address", "127.0.0.1").option("--daemon", "Run in background (no Ctrl+C)").action(async (options) => {
142027
+ program2.command("dashboard").description("Start the MaTrixOS web dashboard (port 9123)").option("-p, --port <number>", "HTTP port", "9123").option("--host <addr>", "Bind address", "0.0.0.0").option("--daemon", "Run in background (no Ctrl+C)").action(async (options) => {
141863
142028
  const exitCode = await dashboardCli({
141864
142029
  port: parseInt(options.port ?? "9123", 10),
141865
142030
  host: options.host,
@@ -2145,7 +2145,7 @@ var package_default;
2145
2145
  var init_package = __esm(() => {
2146
2146
  package_default = {
2147
2147
  name: "@kl-c/matrixos",
2148
- version: "0.1.16",
2148
+ version: "0.1.18",
2149
2149
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
2150
2150
  main: "./dist/index.js",
2151
2151
  types: "dist/index.d.ts",
@@ -141086,101 +141086,55 @@ var AGENT_ROLES = {
141086
141086
  Neo: "Executor",
141087
141087
  Tank: "Deep Work"
141088
141088
  };
141089
+ var OPENCODE_DB_PATH = path24.resolve(os12.homedir(), ".local", "share", "opencode", "opencode.db");
141090
+ var THIRTY_MIN_MS = 30 * 60 * 1000;
141091
+ var FIVE_MIN_MS = 5 * 60 * 1000;
141092
+ var BUDGET_TOTAL = 5000;
141089
141093
  function randomBetween(min2, max2) {
141090
141094
  return Math.floor(Math.random() * (max2 - min2 + 1)) + min2;
141091
141095
  }
141092
141096
  function randomChoice(arr) {
141093
141097
  return arr[Math.floor(Math.random() * arr.length)];
141094
141098
  }
141095
- function generateMockAgents() {
141096
- return AGENT_NAMES.map((name2) => ({
141097
- name: name2,
141098
- role: AGENT_ROLES[name2] ?? "Agent",
141099
- status: randomChoice(["online", "online", "online", "idle", "idle", "offline"]),
141100
- sessions: randomBetween(0, 25),
141101
- uptime: randomBetween(100, 86400),
141102
- cpuPercent: randomBetween(2, 85),
141103
- memoryPercent: randomBetween(10, 75)
141104
- }));
141099
+ function parseModel(modelRaw) {
141100
+ if (!modelRaw)
141101
+ return "unknown";
141102
+ try {
141103
+ const parsed = JSON.parse(modelRaw);
141104
+ if (parsed.id)
141105
+ return parsed.id;
141106
+ } catch {}
141107
+ return modelRaw;
141108
+ }
141109
+ function getDb() {
141110
+ if (!fs20.existsSync(OPENCODE_DB_PATH))
141111
+ return;
141112
+ try {
141113
+ return new Database5(OPENCODE_DB_PATH, { readonly: true });
141114
+ } catch {
141115
+ return;
141116
+ }
141105
141117
  }
141106
- function generateMockSessions(count) {
141107
- const models2 = ["deepseek-v4-flash-free", "gpt-5.5-pro", "claude-opus-4-7", "gemini-3.1-pro", "glm-5.2"];
141108
- return Array.from({ length: count }, (_2, i3) => ({
141109
- id: `ses_${randomBetween(1e5, 999999).toString(16)}`,
141110
- agent: randomChoice(AGENT_NAMES),
141111
- model: randomChoice(models2),
141112
- duration: randomBetween(5, 600),
141113
- cost: Math.round(randomBetween(1, 500) * 100) / 100,
141114
- status: randomChoice(["active", "completed", "completed", "completed", "failed", "timeout"]),
141115
- startedAt: new Date(Date.now() - randomBetween(0, 86400000)).toISOString()
141116
- }));
141118
+ function midnightMs() {
141119
+ const d = new Date;
141120
+ d.setHours(0, 0, 0, 0);
141121
+ return d.getTime();
141117
141122
  }
141118
- function generateMockCost() {
141119
- const agents = AGENT_NAMES.map((name2) => ({
141120
- agent: name2,
141121
- cost: Math.round(randomBetween(50, 2000) * 100) / 100,
141122
- percentage: 0
141123
- }));
141124
- const total = agents.reduce((s, a2) => s + a2.cost, 0);
141125
- agents.forEach((a2) => {
141126
- a2.percentage = Math.round(a2.cost / total * 100);
141127
- });
141128
- return {
141129
- total: Math.round(total * 100) / 100,
141130
- daily: Math.round(randomBetween(50, 300) * 100) / 100,
141131
- monthly: Math.round(randomBetween(1500, 9000) * 100) / 100,
141132
- budgetRemaining: Math.round(randomBetween(500, 5000) * 100) / 100,
141133
- byAgent: agents,
141134
- byDay: Array.from({ length: 30 }, (_2, i3) => ({
141135
- date: new Date(Date.now() - (29 - i3) * 86400000).toISOString().slice(0, 10),
141136
- cost: Math.round(randomBetween(20, 300) * 100) / 100
141137
- })),
141138
- alerts: [
141139
- { severity: "warning", message: "Morpheus approaching budget threshold (85%)" },
141140
- { severity: "critical", message: "Tank exceeded daily budget cap ($500)" }
141141
- ]
141142
- };
141123
+ function thirtyDaysAgoMs() {
141124
+ return Date.now() - 30 * 24 * 60 * 60 * 1000;
141143
141125
  }
141144
- function generateMockKanban() {
141145
- const statuses = ["pending", "in_progress", "completed", "failed"];
141146
- const columns = {};
141147
- for (const s of statuses) {
141148
- columns[s] = Array.from({ length: randomBetween(2, 6) }, (_2, i3) => ({
141149
- id: `task_${randomBetween(1000, 9999)}`,
141150
- title: randomChoice([
141151
- "Implement Watchdog daemon",
141152
- "Design System tokens",
141153
- "API rate limiting",
141154
- "Session persistence",
141155
- "User auth flow",
141156
- "Database migration",
141157
- "Cache invalidation",
141158
- "Webhook integration",
141159
- "Telegram notifications",
141160
- "Cost optimization",
141161
- "Security audit",
141162
- "Performance profiling"
141163
- ]),
141164
- agent: randomChoice(AGENT_NAMES),
141165
- priority: randomChoice(["low", "medium", "high", "critical"]),
141166
- status: s,
141167
- createdAt: new Date(Date.now() - randomBetween(0, 604800000)).toISOString(),
141168
- updatedAt: new Date(Date.now() - randomBetween(0, 86400000)).toISOString()
141169
- }));
141126
+ function readPackageVersion2() {
141127
+ try {
141128
+ const pkgPath = new URL("../../../../package.json", import.meta.url).pathname;
141129
+ const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
141130
+ return pkg.version ?? "0.0.0";
141131
+ } catch {
141132
+ return "0.0.0";
141170
141133
  }
141171
- return { columns };
141172
- }
141173
- function generateMockMetrics() {
141174
- return Array.from({ length: 60 }, (_2, i3) => ({
141175
- timestamp: Date.now() - (59 - i3) * 60000,
141176
- cpuPercent: randomBetween(10, 95),
141177
- memoryPercent: randomBetween(30, 85),
141178
- diskPercent: randomBetween(40, 70),
141179
- sessionsActive: randomBetween(1, 8)
141180
- }));
141181
141134
  }
141182
141135
  function createDataProvider(config4) {
141183
141136
  const matrixosHome = config4?.matrixosHome ?? ".matrixos";
141137
+ const version3 = readPackageVersion2();
141184
141138
  function readJsonl(filePath) {
141185
141139
  try {
141186
141140
  const fullPath = path24.resolve(matrixosHome, "logs", filePath);
@@ -141196,13 +141150,33 @@ function createDataProvider(config4) {
141196
141150
  async getHealth() {
141197
141151
  const mem = process.memoryUsage();
141198
141152
  const totalMem = os12.totalmem();
141153
+ const db = getDb();
141154
+ let agentsOnline = AGENT_NAMES.length;
141155
+ let activeSessions = 0;
141156
+ let tasksQueued = 0;
141157
+ if (db) {
141158
+ try {
141159
+ const agentRow = db.query("SELECT COUNT(DISTINCT agent) as count FROM session").get();
141160
+ if (agentRow)
141161
+ agentsOnline = agentRow.count;
141162
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141163
+ const sessionRows = db.query("SELECT COUNT(*) as count FROM session WHERE time_archived IS NULL AND time_updated > ?").all(cutoff);
141164
+ if (sessionRows.length > 0)
141165
+ activeSessions = sessionRows[0].count;
141166
+ const todoRows = db.query("SELECT COUNT(*) as count FROM todo WHERE status = 'pending'").all();
141167
+ if (todoRows.length > 0)
141168
+ tasksQueued = todoRows[0].count;
141169
+ } catch {} finally {
141170
+ db.close();
141171
+ }
141172
+ }
141199
141173
  return {
141200
- agentsOnline: AGENT_NAMES.length - randomBetween(1, 3),
141174
+ agentsOnline,
141201
141175
  totalAgents: AGENT_NAMES.length,
141202
- activeSessions: randomBetween(2, 8),
141203
- tasksQueued: randomBetween(5, 20),
141176
+ activeSessions,
141177
+ tasksQueued,
141204
141178
  uptime: process.uptime(),
141205
- version: "0.1.0",
141179
+ version: version3,
141206
141180
  memoryUsage: {
141207
141181
  used: Math.round(mem.rss / 1024 / 1024),
141208
141182
  total: Math.round(totalMem / 1024 / 1024),
@@ -141212,16 +141186,181 @@ function createDataProvider(config4) {
141212
141186
  };
141213
141187
  },
141214
141188
  async getAgents() {
141215
- return generateMockAgents();
141189
+ const db = getDb();
141190
+ if (db) {
141191
+ try {
141192
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141193
+ const rows = db.query(`SELECT agent, COUNT(*) as sessions
141194
+ FROM session
141195
+ WHERE agent IS NOT NULL AND agent != ''
141196
+ GROUP BY agent`).all();
141197
+ if (rows.length > 0) {
141198
+ const result = [];
141199
+ for (const row of rows) {
141200
+ const name2 = row.agent;
141201
+ const onlineRows = db.query("SELECT COUNT(*) as count FROM session WHERE agent = ? AND time_updated > ?").all(name2, cutoff);
141202
+ const status2 = onlineRows.length > 0 && onlineRows[0].count > 0 ? "online" : "idle";
141203
+ result.push({
141204
+ name: name2,
141205
+ role: AGENT_ROLES[name2] ?? "Agent",
141206
+ status: status2,
141207
+ sessions: row.sessions,
141208
+ uptime: 0,
141209
+ cpuPercent: 0,
141210
+ memoryPercent: 0
141211
+ });
141212
+ }
141213
+ db.close();
141214
+ return result;
141215
+ }
141216
+ } catch {} finally {
141217
+ db.close();
141218
+ }
141219
+ }
141220
+ return AGENT_NAMES.map((name2) => ({
141221
+ name: name2,
141222
+ role: AGENT_ROLES[name2] ?? "Agent",
141223
+ status: "idle",
141224
+ sessions: 0,
141225
+ uptime: 0,
141226
+ cpuPercent: 0,
141227
+ memoryPercent: 0
141228
+ }));
141216
141229
  },
141217
141230
  async getSessions(limit = 20) {
141218
- return generateMockSessions(limit).slice(0, limit);
141231
+ const db = getDb();
141232
+ if (!db)
141233
+ return [];
141234
+ try {
141235
+ const now = Date.now();
141236
+ const rows = db.query(`SELECT id, title, agent, model, cost, time_created, time_updated, time_archived
141237
+ FROM session
141238
+ ORDER BY time_updated DESC
141239
+ LIMIT ?`).all(limit);
141240
+ const result = [];
141241
+ for (const row of rows) {
141242
+ const duration3 = Math.max(0, Math.round((row.time_updated - row.time_created) / 1000));
141243
+ let status2 = "completed";
141244
+ if (row.time_archived !== null) {
141245
+ status2 = "completed";
141246
+ } else if (row.time_updated > now - FIVE_MIN_MS) {
141247
+ status2 = "active";
141248
+ } else {
141249
+ status2 = "completed";
141250
+ }
141251
+ result.push({
141252
+ id: row.id,
141253
+ agent: row.agent ?? "unknown",
141254
+ model: parseModel(row.model),
141255
+ duration: duration3,
141256
+ cost: row.cost,
141257
+ status: status2,
141258
+ startedAt: new Date(row.time_created).toISOString()
141259
+ });
141260
+ }
141261
+ return result;
141262
+ } catch {
141263
+ return [];
141264
+ } finally {
141265
+ db.close();
141266
+ }
141219
141267
  },
141220
141268
  async getCost() {
141221
- return generateMockCost();
141269
+ const db = getDb();
141270
+ if (!db) {
141271
+ return {
141272
+ total: 0,
141273
+ daily: 0,
141274
+ monthly: 0,
141275
+ budgetRemaining: BUDGET_TOTAL,
141276
+ byAgent: [],
141277
+ byDay: [],
141278
+ alerts: []
141279
+ };
141280
+ }
141281
+ try {
141282
+ const totalRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session").all();
141283
+ const total = totalRows.length > 0 ? totalRows[0].sum ?? 0 : 0;
141284
+ const dailyRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session WHERE time_created > ?").all(midnightMs());
141285
+ const daily = dailyRows.length > 0 ? dailyRows[0].sum ?? 0 : 0;
141286
+ const monthlyRows = db.query("SELECT COALESCE(SUM(cost), 0) as sum FROM session WHERE time_created > ?").all(thirtyDaysAgoMs());
141287
+ const monthly = monthlyRows.length > 0 ? monthlyRows[0].sum ?? 0 : 0;
141288
+ const agentRows = db.query(`SELECT agent, COALESCE(SUM(cost), 0) as cost
141289
+ FROM session
141290
+ WHERE agent IS NOT NULL AND agent != ''
141291
+ GROUP BY agent`).all();
141292
+ const byAgent = agentRows.map((r2) => ({
141293
+ agent: r2.agent,
141294
+ cost: r2.cost,
141295
+ percentage: total > 0 ? Math.round(r2.cost / total * 100) : 0
141296
+ }));
141297
+ const byDayRows = db.query(`SELECT strftime('%Y-%m-%d', time_created / 1000, 'unixepoch') as day,
141298
+ COALESCE(SUM(cost), 0) as cost
141299
+ FROM session
141300
+ WHERE time_created > ?
141301
+ GROUP BY day
141302
+ ORDER BY day ASC`).all(thirtyDaysAgoMs());
141303
+ const byDayMap = new Map;
141304
+ for (const r2 of byDayRows) {
141305
+ byDayMap.set(r2.day, r2.cost);
141306
+ }
141307
+ const byDay = [];
141308
+ const today = new Date;
141309
+ for (let i3 = 29;i3 >= 0; i3--) {
141310
+ const d = new Date(today);
141311
+ d.setDate(d.getDate() - i3);
141312
+ const key = d.toISOString().slice(0, 10);
141313
+ byDay.push({ date: key, cost: byDayMap.get(key) ?? 0 });
141314
+ }
141315
+ const budgetRemaining = Math.max(0, BUDGET_TOTAL - total);
141316
+ const alerts = [];
141317
+ if (total > BUDGET_TOTAL) {
141318
+ alerts.push({ severity: "critical", message: `Total cost $${total.toFixed(2)} exceeds budget $${BUDGET_TOTAL}` });
141319
+ } else if (total > BUDGET_TOTAL * 0.85) {
141320
+ alerts.push({ severity: "warning", message: `Cost $${total.toFixed(2)} approaching budget threshold (85%)` });
141321
+ }
141322
+ return { total, daily, monthly, budgetRemaining, byAgent, byDay, alerts };
141323
+ } catch {
141324
+ return {
141325
+ total: 0,
141326
+ daily: 0,
141327
+ monthly: 0,
141328
+ budgetRemaining: BUDGET_TOTAL,
141329
+ byAgent: [],
141330
+ byDay: [],
141331
+ alerts: []
141332
+ };
141333
+ } finally {
141334
+ db.close();
141335
+ }
141222
141336
  },
141223
141337
  async getKanbanTasks() {
141224
- return generateMockKanban();
141338
+ const board = { columns: { pending: [], in_progress: [], completed: [], failed: [] } };
141339
+ const db = getDb();
141340
+ if (!db)
141341
+ return board;
141342
+ try {
141343
+ const rows = db.query("SELECT content, status, priority, time_created, time_updated FROM todo ORDER BY time_updated DESC").all();
141344
+ let idx = 0;
141345
+ for (const row of rows) {
141346
+ const status2 = row.status;
141347
+ if (!board.columns[status2])
141348
+ continue;
141349
+ const task2 = {
141350
+ id: `todo_${idx++}`,
141351
+ title: row.content,
141352
+ agent: "Unknown",
141353
+ priority: row.priority ?? "medium",
141354
+ status: status2,
141355
+ createdAt: new Date(row.time_created).toISOString(),
141356
+ updatedAt: new Date(row.time_updated).toISOString()
141357
+ };
141358
+ board.columns[status2].push(task2);
141359
+ }
141360
+ } catch {} finally {
141361
+ db.close();
141362
+ }
141363
+ return board;
141225
141364
  },
141226
141365
  async getIncidents(limit = 50) {
141227
141366
  const lines = readJsonl("anti-loop.jsonl");
@@ -141245,7 +141384,33 @@ function createDataProvider(config4) {
141245
141384
  }).filter(Boolean);
141246
141385
  },
141247
141386
  async getMetrics() {
141248
- return generateMockMetrics();
141387
+ const db = getDb();
141388
+ let activeSessions = 0;
141389
+ if (db) {
141390
+ try {
141391
+ const cutoff = Date.now() - THIRTY_MIN_MS;
141392
+ const rows = db.query("SELECT COUNT(*) as count FROM session WHERE time_archived IS NULL AND time_updated > ?").all(cutoff);
141393
+ if (rows.length > 0)
141394
+ activeSessions = rows[0].count;
141395
+ } catch {} finally {
141396
+ db.close();
141397
+ }
141398
+ }
141399
+ const totalMem = os12.totalmem();
141400
+ const mem = process.memoryUsage();
141401
+ const memPercent = Math.round(mem.rss / totalMem * 100);
141402
+ const cpuPercent = Math.round(os12.loadavg()[0] * 100);
141403
+ const snapshots = [];
141404
+ for (let i3 = 0;i3 < 60; i3++) {
141405
+ snapshots.push({
141406
+ timestamp: Date.now() - (59 - i3) * 60000,
141407
+ cpuPercent,
141408
+ memoryPercent: memPercent,
141409
+ diskPercent: 0,
141410
+ sessionsActive: activeSessions
141411
+ });
141412
+ }
141413
+ return snapshots;
141249
141414
  },
141250
141415
  getLogs() {
141251
141416
  const logFiles = [
@@ -141592,7 +141757,7 @@ function createDashboardServer(dataProvider, config4) {
141592
141757
  // packages/omo-opencode/src/cli/dashboard.ts
141593
141758
  async function dashboardCli(options) {
141594
141759
  const port = options.port ?? 9123;
141595
- const host = options.host ?? "127.0.0.1";
141760
+ const host = options.host ?? "0.0.0.0";
141596
141761
  const frontendDir = path25.resolve(import.meta.dir, "../features/dashboard/frontend");
141597
141762
  console.log(`[dashboard] starting MaTrixOS dashboard on http://${host}:${port}`);
141598
141763
  console.log(`[dashboard] frontend: ${frontendDir}`);
@@ -141914,7 +142079,7 @@ function configureRuntimeCommands(program2) {
141914
142079
  const exitCode = await architectRollback({ ...options, id });
141915
142080
  process.exit(exitCode);
141916
142081
  });
141917
- program2.command("dashboard").description("Start the MaTrixOS web dashboard (port 9123)").option("-p, --port <number>", "HTTP port", "9123").option("--host <addr>", "Bind address", "127.0.0.1").option("--daemon", "Run in background (no Ctrl+C)").action(async (options) => {
142082
+ program2.command("dashboard").description("Start the MaTrixOS web dashboard (port 9123)").option("-p, --port <number>", "HTTP port", "9123").option("--host <addr>", "Bind address", "0.0.0.0").option("--daemon", "Run in background (no Ctrl+C)").action(async (options) => {
141918
142083
  const exitCode = await dashboardCli({
141919
142084
  port: parseInt(options.port ?? "9123", 10),
141920
142085
  host: options.host,
@@ -23,11 +23,13 @@
23
23
  <div class="sidebar-section">Operations</div>
24
24
  <a href="#" class="sidebar-item" data-tab="health"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 2L1.5 13h13L8 2z"/><path d="M8 7v2.5M8 11.5v.5"/></svg>Incidents<span class="sidebar-badge danger" id="badgeIncidents">3</span></a>
25
25
  <a href="#" class="sidebar-item" data-tab="logs"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M14 2H2v12h12V2z"/><path d="M5 8h6M5 11h4"/></svg>Logs</a>
26
- <a href="#" class="sidebar-item"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 14A6 6 0 108 2a6 6 0 000 12z"/><path d="M8 5v3l2 2"/></svg>Schedule</a>
26
+ <a href="#" class="sidebar-item" data-tab="schedule"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M8 14A6 6 0 108 2a6 6 0 000 12z"/><path d="M8 5v3l2 2"/></svg>Schedule</a>
27
+ <a href="#" class="sidebar-item" data-tab="memory"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="2" y="3" width="12" height="11" rx="1"/><path d="M2 6h12"/><circle cx="8" cy="10" r="1.5"/></svg>Memory</a>
28
+ <a href="#" class="sidebar-item" data-tab="skills"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="6" r="3"/><path d="M3 14c0-3 2.5-5 5-5s5 2 5 5"/></svg>Skills</a>
27
29
  <div class="sidebar-section">System</div>
28
30
  <a href="#" class="sidebar-item" data-tab="config"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M6 6h4M6 8h4M6 10h2"/></svg>Config</a>
29
- <a href="#" class="sidebar-item"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M12 8A4 4 0 114 8a4 4 0 018 0z"/><path d="M8 2v1M8 13v1M2 8h1M13 8h1"/></svg>Settings</a>
30
- <a href="#" class="sidebar-item"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V7l-5-5z"/><path d="M9 2v5h5"/></svg>Audit Trail</a>
31
+ <a href="#" class="sidebar-item" data-tab="config"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M12 8A4 4 0 114 8a4 4 0 018 0z"/><path d="M8 2v1M8 13v1M2 8h1M13 8h1"/></svg>Settings</a>
32
+ <a href="#" class="sidebar-item" data-tab="logs"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><path d="M9 2H4a1 1 0 00-1 1v10a1 1 0 001 1h8a1 1 0 001-1V7l-5-5z"/><path d="M9 2v5h5"/></svg>Audit Trail</a>
31
33
  </nav>
32
34
  <div class="sidebar-footer">
33
35
  <div class="sidebar-avatar">SM</div>
@@ -165,6 +167,13 @@
165
167
  </div>
166
168
  <div class="card"><div class="card-header"><span class="card-title">matrixos.jsonc</span><span id="configStatus" style="font-size:var(--text-xs);color:var(--text-tertiary)"></span></div><div class="card-body"><textarea id="configEditor" class="config-editor" spellcheck="false"></textarea></div></div>
167
169
  </div>
170
+
171
+ <div id="tab-schedule" class="tab-panel">
172
+ <div class="page-header">
173
+ <div><h1 class="page-title">Schedule</h1><p class="page-subtitle">Cron jobs and scheduled tasks for MaTrixOS agents</p></div>
174
+ </div>
175
+ <div class="card"><div class="card-body" id="scheduleBody"></div></div>
176
+ </div>
168
177
  </div>
169
178
  </div>
170
179
  <script src="js/api.js"></script>
@@ -10,7 +10,7 @@ document.querySelectorAll('[data-tab]').forEach(el=>{
10
10
  el.addEventListener('click',e=>{
11
11
  e.preventDefault()
12
12
  const tab=el.dataset.tab
13
- if(!tab||!['architect','health','cost','kanban','agents','sessions','logs','config'].includes(tab))return
13
+ if(!tab||!['architect','health','cost','kanban','agents','sessions','logs','config','memory','skills','schedule'].includes(tab))return
14
14
  const realTab=tab==='agents'||tab==='sessions'?'architect':tab
15
15
  document.querySelectorAll('.sidebar-item.active,.header-tab.active,.tab-panel.active').forEach(n=>n.classList.remove('active'))
16
16
  document.querySelectorAll(`.sidebar-item[data-tab="${tab}"],.header-tab[data-tab="${realTab}"]`).forEach(n=>n.classList.add('active'))
@@ -20,7 +20,7 @@ document.querySelectorAll('[data-tab]').forEach(el=>{
20
20
  })
21
21
 
22
22
  // Tab loading dispatch
23
- function loadTab(tab){const f={architect:loadArchitect,health:loadHealth,cost:loadCost,kanban:loadKanban,logs:loadLogs,config:loadConfig,memory:loadMemory,skills:loadSkills}[tab];f&&f()}
23
+ function loadTab(tab){const f={architect:loadArchitect,health:loadHealth,cost:loadCost,kanban:loadKanban,logs:loadLogs,config:loadConfig,memory:loadMemory,skills:loadSkills,schedule:loadSchedule}[tab];f&&f()}
24
24
 
25
25
  // Skeleton helpers
26
26
  function showSkeleton(container,type,count=4){
@@ -338,6 +338,16 @@ async function loadSkills(){
338
338
  }catch(e){grid.innerHTML=`<div class="error"><p>Failed to load skills: ${e.message}</p></div>`}
339
339
  }
340
340
 
341
+ // ===== SCHEDULE =====
342
+ function loadSchedule(){
343
+ const body=document.getElementById('scheduleBody')
344
+ if(!body)return
345
+ body.innerHTML=`<div class="empty" style="padding:40px">
346
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
347
+ <p>Schedule feature not configured — enable cron in matrixos.jsonc to start scheduling agent tasks.</p>
348
+ </div>`
349
+ }
350
+
341
351
  // ===== INIT =====
342
352
  document.getElementById('refreshBtn').onclick=()=>{const t=document.querySelector('.header-tab.active');t&&loadTab(t.dataset.tab)}
343
353
  document.getElementById('archRefresh').onclick=()=>loadArchitect()
@@ -5,8 +5,8 @@ var MATRIX_FEATURES = [
5
5
  id: "dashboard",
6
6
  name: "Mission Control (Dashboard)",
7
7
  description: "Web UI to supervise agents, sessions, tasks, cost and incidents in real time.",
8
- enabledByDefault: false,
9
- status: "cli-only",
8
+ enabledByDefault: true,
9
+ status: "native",
10
10
  userBenefit: "Open a browser page (default http://127.0.0.1:9123) showing live agent/session/task state, cost, kanban and incidents.",
11
11
  provisioning: {
12
12
  activation: "Launch the bundled server: `matrixos dashboard --daemon` (listens on 127.0.0.1:9123 by default). For remote access over Tailscale, rebind with `--host <tailscale-ip>` (e.g. 100.96.138.13).",
package/dist/index.js CHANGED
@@ -367932,7 +367932,7 @@ function getCachedVersion(options = {}) {
367932
367932
  // package.json
367933
367933
  var package_default = {
367934
367934
  name: "@kl-c/matrixos",
367935
- version: "0.1.16",
367935
+ version: "0.1.18",
367936
367936
  description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
367937
367937
  main: "./dist/index.js",
367938
367938
  types: "dist/index.d.ts",
@@ -421414,6 +421414,21 @@ function buildParallelDelegationSection(model, categories) {
421414
421414
 
421415
421415
  **Your value is orchestration, decomposition, and quality control. Delegating with crystal-clear prompts IS your work.**`;
421416
421416
  }
421417
+ function buildBaseCapabilitiesSection() {
421418
+ return `### Base Capabilities (you already have these)
421419
+
421420
+ These ship with MaTrixOS and are part of your baseline awareness \u2014 you do
421421
+ NOT need to discover them by searching the code. When the user mentions one,
421422
+ you already know what it is and how to activate it:
421423
+
421424
+ - **Mission Control (Dashboard)** \u2014 real-time web UI to supervise agents, sessions, tasks, cost and incidents. Backend is always bundled. Launch with \`matrixos dashboard --daemon\` (serves on http://127.0.0.1:9123). For remote access over Tailscale, rebind with \`--host <tailscale-ip>\`.
421425
+ - **Messaging Gateway** \u2014 drive Morpheus from Telegram / Discord / WhatsApp. Opt-in: set \`gateway.enabled: true\` in matrixos.json + a channel entry with the bot token via ENV (never inline). See \`/features gateway\` for the full recipe.
421426
+ - **Learning Loop** \u2014 L1\u2192L2 self-improvement; MaTrixOS proposes learnings from your session history.
421427
+ - **Team Mode** \u2014 spawn parallel sub-agent teams (up to 8 members). Opt-in via \`team_mode.enabled\`.
421428
+ - **16-agent team** \u2014 listed above. Delegate, do not implement yourself when a specialist exists.
421429
+
421430
+ For the full, on-demand inventory (including opt-in features like Kanban, Cron, Goal Mode), use \`/features\`. To (re)run the adoption ritual and choose opt-in features, use \`/adopt\`.`;
421431
+ }
421417
421432
  // packages/omo-opencode/src/agents/dynamic-agent-category-skills-guide.ts
421418
421433
  function buildSkillsSection(skills) {
421419
421434
  const builtinSkills = skills.filter((skill2) => skill2.location === "plugin");
@@ -421905,6 +421920,8 @@ You are "Morpheus" - the orchestrator of MaTrixOS, an agentic OS built as a fork
421905
421920
 
421906
421921
  ${sections.agentTeam}
421907
421922
 
421923
+ ${sections.baseCapabilities}
421924
+
421908
421925
  </Role>
421909
421926
  <Behavior_Instructions>
421910
421927
 
@@ -422135,6 +422152,7 @@ function buildMorpheusDynamicPromptSections(model, availableAgents, availableToo
422135
422152
  return {
422136
422153
  agentIdentity: buildAgentIdentitySection("Morpheus", "Powerful AI Agent with orchestration capabilities from MaTrixOS \u2014 a fork of OMO improved with Hermes, OpenClaw and Goose strengths, model-agnostic (open/standard models, never locked to a proprietary provider)"),
422137
422154
  agentTeam: buildMatrixOSAgentTeamSection(),
422155
+ baseCapabilities: buildBaseCapabilitiesSection(),
422138
422156
  antiPatterns: buildAntiPatternsSection(),
422139
422157
  categorySkillsGuide: buildCategorySkillsDelegationGuide(availableCategories, availableSkills),
422140
422158
  delegationTable: buildDelegationTable(availableAgents),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kl-c/matrixos",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "MaTrixOS — Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",