@massa-ai/opencode-plugin 1.45.0 → 1.47.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.
@@ -401,9 +401,16 @@ var init_xdg = () => {};
401
401
 
402
402
  // ../../packages/shared/dist/config/massa-ai-config.js
403
403
  import path2 from "path";
404
- var defaultMassaAiConfig;
404
+ var SCHEDULER_JOB_KINDS, defaultMassaAiConfig;
405
405
  var init_massa_ai_config = __esm(() => {
406
406
  init_xdg();
407
+ SCHEDULER_JOB_KINDS = [
408
+ "memory-consolidation",
409
+ "decay-sweep",
410
+ "auto-improve",
411
+ "observation-bridge",
412
+ "checkpoint-purge"
413
+ ];
407
414
  defaultMassaAiConfig = {
408
415
  database: {
409
416
  url: ""
@@ -734,11 +741,13 @@ try {
734
741
  // ../../packages/shared/dist/config/index.js
735
742
  init_config_loader();
736
743
  init_massa_ai_config();
744
+ init_massa_ai_config();
737
745
  init_config_loader();
738
746
  import path4 from "path";
739
747
 
740
748
  // ../../packages/shared/dist/config/config-writer.js
741
749
  init_config_loader();
750
+ init_massa_ai_config();
742
751
 
743
752
  // ../../packages/shared/dist/config/index.js
744
753
  init_xdg();
@@ -778,6 +787,49 @@ function envList(key, fallback) {
778
787
  const parsed = s.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
779
788
  return parsed.length > 0 ? parsed : fallback;
780
789
  }
790
+ function readSchedulerConfig(rawFileConfig) {
791
+ try {
792
+ const cfg = rawFileConfig;
793
+ return cfg?.scheduler ?? {};
794
+ } catch {
795
+ return {};
796
+ }
797
+ }
798
+ var SCHEDULER_JOB_ENV = {
799
+ "memory-consolidation": {
800
+ enabledVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_ENABLED",
801
+ intervalVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_INTERVAL_MS",
802
+ defaultIntervalMs: 30 * 60 * 1000
803
+ },
804
+ "decay-sweep": {
805
+ enabledVar: "MASSA_AI_SCHEDULER_DECAY_ENABLED",
806
+ intervalVar: "MASSA_AI_SCHEDULER_DECAY_INTERVAL_MS",
807
+ defaultIntervalMs: 60 * 60 * 1000
808
+ },
809
+ "auto-improve": {
810
+ enabledVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_ENABLED",
811
+ intervalVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_INTERVAL_MS",
812
+ defaultIntervalMs: 30 * 60 * 1000
813
+ },
814
+ "observation-bridge": {
815
+ enabledVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_ENABLED",
816
+ intervalVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_INTERVAL_MS",
817
+ defaultIntervalMs: 30 * 60 * 1000
818
+ },
819
+ "checkpoint-purge": {
820
+ enabledVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_ENABLED",
821
+ intervalVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_INTERVAL_MS",
822
+ defaultIntervalMs: 60 * 60 * 1000
823
+ }
824
+ };
825
+ function resolveSchedulerJob(kind, fileJobs) {
826
+ const meta = SCHEDULER_JOB_ENV[kind];
827
+ const fileJob = fileJobs?.[kind];
828
+ return {
829
+ enabled: envBool(meta.enabledVar, fileJob?.enabled ?? false),
830
+ intervalMs: envNum(meta.intervalVar, fileJob?.intervalMs ?? meta.defaultIntervalMs)
831
+ };
832
+ }
781
833
  var MAX_IGNORE_PATTERNS = 1024;
782
834
  function validateCapturePolicyConfig(raw) {
783
835
  if (!raw || typeof raw !== "object")
@@ -881,10 +933,11 @@ var DEFAULT_ALLOWED_EXTENSIONS = [
881
933
  var fileConfig = loadConfigSafe();
882
934
  var fileCacheL1Bytes = fileConfig.cache?.l1MaxSizeMB ? fileConfig.cache.l1MaxSizeMB * 1024 * 1024 : undefined;
883
935
  var fileCacheL2Bytes = fileConfig.cache?.l2MaxSizeMB ? fileConfig.cache.l2MaxSizeMB * 1024 * 1024 : undefined;
936
+ var resolvedDataDir = getGlobalDataDir();
884
937
  var defaultConfig = {
885
938
  name: "massa-ai-server",
886
939
  version: "1.0.0",
887
- dataDir: getGlobalDataDir(),
940
+ dataDir: resolvedDataDir,
888
941
  cache: {
889
942
  l1: {
890
943
  maxSize: envNum("L1_CACHE_MAX_SIZE", fileCacheL1Bytes ?? 100 * 1024 * 1024),
@@ -1008,8 +1061,24 @@ var defaultConfig = {
1008
1061
  logging: {
1009
1062
  level: process.env.LOG_LEVEL || fileConfig.logging?.level || "info",
1010
1063
  enableMetrics: process.env.ENABLE_METRICS === "true" || process.env.ENABLE_METRICS === undefined && !!fileConfig.logging?.enableMetrics,
1011
- file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || undefined
1064
+ file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || path4.join(resolvedDataDir, "logs", "massa-ai.log"),
1065
+ enableFileSink: envBool("MASSA_AI_LOG_ENABLE_FILE_SINK", fileConfig.logging?.enableFileSink ?? true),
1066
+ bufferSize: envNum("MASSA_AI_LOG_BUFFER_SIZE", fileConfig.logging?.bufferSize ?? 2000),
1067
+ maxFileSizeMb: envNum("MASSA_AI_LOG_MAX_FILE_SIZE_MB", fileConfig.logging?.maxFileSizeMb ?? 32),
1068
+ maxFiles: envNum("MASSA_AI_LOG_MAX_FILES", fileConfig.logging?.maxFiles ?? 5)
1012
1069
  },
1070
+ scheduler: (() => {
1071
+ const fileScheduler = readSchedulerConfig(fileConfig);
1072
+ return {
1073
+ enabled: envBool("MASSA_AI_SCHEDULER_ENABLED", fileScheduler.enabled ?? false),
1074
+ tickMs: envNum("MASSA_AI_SCHEDULER_TICK_MS", fileScheduler.tickMs ?? 60000),
1075
+ maxConcurrent: envNum("MASSA_AI_SCHEDULER_MAX_CONCURRENT", fileScheduler.maxConcurrent ?? 2),
1076
+ jobs: Object.fromEntries(SCHEDULER_JOB_KINDS.map((kind) => [
1077
+ kind,
1078
+ resolveSchedulerJob(kind, fileScheduler.jobs)
1079
+ ]))
1080
+ };
1081
+ })(),
1013
1082
  synapse: {
1014
1083
  enabled: process.env.SYNAPSE_ENABLED !== "false",
1015
1084
  inhibition: {
@@ -1124,6 +1193,11 @@ class Config {
1124
1193
  rateLimit: { ...defaults.rateLimit, ...overrides.rateLimit },
1125
1194
  security: { ...defaults.security, ...overrides.security },
1126
1195
  logging: { ...defaults.logging, ...overrides.logging },
1196
+ scheduler: {
1197
+ ...defaults.scheduler,
1198
+ ...overrides.scheduler,
1199
+ jobs: { ...defaults.scheduler.jobs, ...overrides.scheduler?.jobs }
1200
+ },
1127
1201
  synapse: {
1128
1202
  ...defaults.synapse,
1129
1203
  ...overrides.synapse,
@@ -1288,8 +1362,165 @@ var TaskStatus;
1288
1362
  TaskStatus2["FAILED"] = "failed";
1289
1363
  TaskStatus2["PAUSED"] = "paused";
1290
1364
  })(TaskStatus || (TaskStatus = {}));
1291
- // ../../packages/shared/dist/utils/logger.js
1365
+ // ../../packages/shared/dist/utils/log-sink.js
1292
1366
  import fs2 from "fs";
1367
+ import path5 from "path";
1368
+ var STAT_DELTA_THRESHOLD_BYTES = 1024 * 1024;
1369
+ var ensuredDirs = new Set;
1370
+ var pathState = new Map;
1371
+ var lastError;
1372
+ function getState(filePath) {
1373
+ let state = pathState.get(filePath);
1374
+ if (!state) {
1375
+ state = { trackedSize: 0, deltaSinceStat: 0, primed: false };
1376
+ pathState.set(filePath, state);
1377
+ }
1378
+ return state;
1379
+ }
1380
+ function ensureDir(filePath) {
1381
+ const dir = path5.dirname(filePath);
1382
+ if (ensuredDirs.has(dir))
1383
+ return;
1384
+ fs2.mkdirSync(dir, { recursive: true });
1385
+ ensuredDirs.add(dir);
1386
+ }
1387
+ function restat(filePath, state) {
1388
+ try {
1389
+ state.trackedSize = fs2.statSync(filePath).size;
1390
+ } catch {
1391
+ state.trackedSize = 0;
1392
+ }
1393
+ state.deltaSinceStat = 0;
1394
+ state.primed = true;
1395
+ }
1396
+ function rotate(filePath, maxFiles) {
1397
+ if (maxFiles <= 0) {
1398
+ if (fs2.existsSync(filePath))
1399
+ fs2.unlinkSync(filePath);
1400
+ return;
1401
+ }
1402
+ const oldest = `${filePath}.${maxFiles}`;
1403
+ if (fs2.existsSync(oldest))
1404
+ fs2.unlinkSync(oldest);
1405
+ for (let n = maxFiles - 1;n >= 1; n--) {
1406
+ const src = `${filePath}.${n}`;
1407
+ const dest = `${filePath}.${n + 1}`;
1408
+ if (fs2.existsSync(src))
1409
+ fs2.renameSync(src, dest);
1410
+ }
1411
+ if (fs2.existsSync(filePath))
1412
+ fs2.renameSync(filePath, `${filePath}.1`);
1413
+ }
1414
+ function appendLine(opts, line) {
1415
+ const { filePath, maxFileSizeBytes, maxFiles } = opts;
1416
+ try {
1417
+ ensureDir(filePath);
1418
+ const state = getState(filePath);
1419
+ const lineBytes = Buffer.byteLength(line + `
1420
+ `, "utf8");
1421
+ if (!state.primed || state.deltaSinceStat >= STAT_DELTA_THRESHOLD_BYTES || state.trackedSize + state.deltaSinceStat >= maxFileSizeBytes) {
1422
+ restat(filePath, state);
1423
+ }
1424
+ if (maxFileSizeBytes > 0 && state.trackedSize >= maxFileSizeBytes) {
1425
+ rotate(filePath, maxFiles);
1426
+ state.trackedSize = 0;
1427
+ state.deltaSinceStat = 0;
1428
+ state.primed = true;
1429
+ }
1430
+ fs2.appendFileSync(filePath, line + `
1431
+ `);
1432
+ state.trackedSize += lineBytes;
1433
+ state.deltaSinceStat += lineBytes;
1434
+ } catch (err) {
1435
+ lastError = err;
1436
+ }
1437
+ }
1438
+
1439
+ // ../../packages/shared/dist/utils/log-buffer.js
1440
+ var DEFAULT_CAPACITY = 2000;
1441
+
1442
+ class LogBufferImpl {
1443
+ capacity = DEFAULT_CAPACITY;
1444
+ entries = [];
1445
+ subscribers = new Set;
1446
+ nextSeq = 0;
1447
+ dispatching = false;
1448
+ pending = [];
1449
+ push(entry) {
1450
+ if (this.dispatching) {
1451
+ this.pending.push(entry);
1452
+ return;
1453
+ }
1454
+ this.dispatching = true;
1455
+ try {
1456
+ this.pushOne(entry);
1457
+ while (this.pending.length > 0) {
1458
+ const next = this.pending.shift();
1459
+ this.pushOne(next);
1460
+ }
1461
+ } finally {
1462
+ this.dispatching = false;
1463
+ }
1464
+ }
1465
+ pushOne(entry) {
1466
+ const full = { ...entry, seq: this.nextSeq++ };
1467
+ this.entries.push(full);
1468
+ while (this.entries.length > this.capacity) {
1469
+ this.entries.shift();
1470
+ }
1471
+ for (const fn of this.subscribers) {
1472
+ try {
1473
+ fn(full);
1474
+ } catch {}
1475
+ }
1476
+ }
1477
+ snapshot(opts) {
1478
+ const from = opts?.from;
1479
+ const to = opts?.to;
1480
+ const level = opts?.level;
1481
+ const q = opts?.q?.toLowerCase();
1482
+ const result = [];
1483
+ for (let i = this.entries.length - 1;i >= 0; i--) {
1484
+ const e = this.entries[i];
1485
+ if (from !== undefined && e.seq < from)
1486
+ continue;
1487
+ if (to !== undefined && e.seq > to)
1488
+ continue;
1489
+ if (level !== undefined && e.level !== level)
1490
+ continue;
1491
+ if (q !== undefined && !e.message.toLowerCase().includes(q))
1492
+ continue;
1493
+ result.push(e);
1494
+ }
1495
+ return result;
1496
+ }
1497
+ subscribe(fn) {
1498
+ this.subscribers.add(fn);
1499
+ return () => {
1500
+ this.subscribers.delete(fn);
1501
+ };
1502
+ }
1503
+ setCapacity(n) {
1504
+ this.capacity = n > 0 ? n : 0;
1505
+ while (this.entries.length > this.capacity) {
1506
+ this.entries.shift();
1507
+ }
1508
+ }
1509
+ size() {
1510
+ return this.entries.length;
1511
+ }
1512
+ _resetForTesting() {
1513
+ this.entries = [];
1514
+ this.subscribers.clear();
1515
+ this.nextSeq = 0;
1516
+ this.dispatching = false;
1517
+ this.pending = [];
1518
+ this.capacity = DEFAULT_CAPACITY;
1519
+ }
1520
+ }
1521
+ var logBuffer = new LogBufferImpl;
1522
+
1523
+ // ../../packages/shared/dist/utils/logger.js
1293
1524
  var LogLevel;
1294
1525
  (function(LogLevel2) {
1295
1526
  LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
@@ -1297,11 +1528,26 @@ var LogLevel;
1297
1528
  LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
1298
1529
  LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
1299
1530
  })(LogLevel || (LogLevel = {}));
1531
+ var LOG_LEVEL_LABELS = {
1532
+ [LogLevel.DEBUG]: "DEBUG",
1533
+ [LogLevel.INFO]: "INFO",
1534
+ [LogLevel.WARN]: "WARN",
1535
+ [LogLevel.ERROR]: "ERROR"
1536
+ };
1537
+ var LOG_LEVEL_BUFFER_TAGS = {
1538
+ [LogLevel.DEBUG]: "debug",
1539
+ [LogLevel.INFO]: "info",
1540
+ [LogLevel.WARN]: "warn",
1541
+ [LogLevel.ERROR]: "error"
1542
+ };
1300
1543
 
1301
1544
  class Logger {
1302
1545
  _level;
1303
1546
  _enableMetrics;
1304
1547
  _logFilePath;
1548
+ _enableFileSink;
1549
+ _maxFileSizeBytes;
1550
+ _maxFiles;
1305
1551
  _initialized = false;
1306
1552
  constructor() {}
1307
1553
  ensureInitialized() {
@@ -1311,10 +1557,18 @@ class Logger {
1311
1557
  this._level = this.parseLogLevel(loggingConfig.level);
1312
1558
  this._enableMetrics = loggingConfig.enableMetrics;
1313
1559
  this._logFilePath = loggingConfig.file;
1560
+ this._enableFileSink = loggingConfig.enableFileSink;
1561
+ this._maxFileSizeBytes = loggingConfig.maxFileSizeMb * 1024 * 1024;
1562
+ this._maxFiles = loggingConfig.maxFiles;
1563
+ logBuffer.setCapacity(loggingConfig.bufferSize);
1314
1564
  } catch {
1315
1565
  this._level = LogLevel.INFO;
1316
1566
  this._enableMetrics = false;
1317
1567
  this._logFilePath = undefined;
1568
+ this._enableFileSink = false;
1569
+ this._maxFileSizeBytes = 32 * 1024 * 1024;
1570
+ this._maxFiles = 5;
1571
+ logBuffer.setCapacity(2000);
1318
1572
  }
1319
1573
  this._initialized = true;
1320
1574
  }
@@ -1331,6 +1585,18 @@ class Logger {
1331
1585
  this.ensureInitialized();
1332
1586
  return this._logFilePath;
1333
1587
  }
1588
+ get enableFileSink() {
1589
+ this.ensureInitialized();
1590
+ return this._enableFileSink;
1591
+ }
1592
+ get maxFileSizeBytes() {
1593
+ this.ensureInitialized();
1594
+ return this._maxFileSizeBytes;
1595
+ }
1596
+ get maxFiles() {
1597
+ this.ensureInitialized();
1598
+ return this._maxFiles;
1599
+ }
1334
1600
  parseLogLevel(level) {
1335
1601
  const levels = {
1336
1602
  debug: LogLevel.DEBUG,
@@ -1343,34 +1609,40 @@ class Logger {
1343
1609
  shouldLog(level) {
1344
1610
  return level >= this.level;
1345
1611
  }
1346
- formatMessage(level, message, meta) {
1347
- const timestamp = new Date().toISOString();
1612
+ formatMessage(level, message, meta, timestamp = new Date().toISOString()) {
1348
1613
  const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
1349
1614
  return `[${timestamp}] [${level}] ${message}${metaStr}`;
1350
1615
  }
1351
- write(message, _level) {
1352
- console.error(message);
1353
- const filePath = this.logFilePath;
1354
- if (filePath) {
1355
- try {
1356
- fs2.appendFileSync(filePath, message + `
1357
- `);
1358
- } catch {}
1616
+ emit(level, message, meta) {
1617
+ const ts = new Date().toISOString();
1618
+ const line = this.formatMessage(LOG_LEVEL_LABELS[level], message, meta, ts);
1619
+ console.error(line);
1620
+ if (this.enableFileSink) {
1621
+ const filePath = this.logFilePath;
1622
+ if (filePath) {
1623
+ appendLine({ filePath, maxFileSizeBytes: this.maxFileSizeBytes, maxFiles: this.maxFiles }, line);
1624
+ }
1359
1625
  }
1626
+ logBuffer.push({
1627
+ ts,
1628
+ level: LOG_LEVEL_BUFFER_TAGS[level],
1629
+ message,
1630
+ ...meta ? { meta } : {}
1631
+ });
1360
1632
  }
1361
1633
  debug(message, meta) {
1362
1634
  if (this.shouldLog(LogLevel.DEBUG)) {
1363
- this.write(this.formatMessage("DEBUG", message, meta), LogLevel.DEBUG);
1635
+ this.emit(LogLevel.DEBUG, message, meta);
1364
1636
  }
1365
1637
  }
1366
1638
  info(message, meta) {
1367
1639
  if (this.shouldLog(LogLevel.INFO)) {
1368
- this.write(this.formatMessage("INFO", message, meta), LogLevel.INFO);
1640
+ this.emit(LogLevel.INFO, message, meta);
1369
1641
  }
1370
1642
  }
1371
1643
  warn(message, meta) {
1372
1644
  if (this.shouldLog(LogLevel.WARN)) {
1373
- this.write(this.formatMessage("WARN", message, meta), LogLevel.WARN);
1645
+ this.emit(LogLevel.WARN, message, meta);
1374
1646
  }
1375
1647
  }
1376
1648
  error(message, error, meta) {
@@ -1383,7 +1655,7 @@ class Logger {
1383
1655
  stack: error.stack
1384
1656
  }
1385
1657
  } : meta;
1386
- this.write(this.formatMessage("ERROR", message, errorMeta), LogLevel.ERROR);
1658
+ this.emit(LogLevel.ERROR, message, errorMeta);
1387
1659
  }
1388
1660
  }
1389
1661
  metric(name, value, unit) {
@@ -1507,7 +1779,7 @@ class SmartRateLimiter {
1507
1779
  var rateLimiter = new SmartRateLimiter;
1508
1780
  // ../../packages/shared/dist/profile-switch/hosts.js
1509
1781
  import os3 from "os";
1510
- import path5 from "path";
1782
+ import path6 from "path";
1511
1783
  var HOSTS = ["claude", "codex", "cursor", "opencode"];
1512
1784
  function isHost(v) {
1513
1785
  return typeof v === "string" && HOSTS.includes(v);
@@ -1520,7 +1792,7 @@ function fileLayout(host, activeDir, activeGlob, variantsRoot) {
1520
1792
  activeDir,
1521
1793
  activeGlob,
1522
1794
  variantsRoot,
1523
- variantDir: (profile) => path5.join(variantsRoot, profile)
1795
+ variantDir: (profile) => path6.join(variantsRoot, profile)
1524
1796
  };
1525
1797
  }
1526
1798
  function resolveHostLayout(host, opts = {}) {
@@ -1530,25 +1802,37 @@ function resolveHostLayout(host, opts = {}) {
1530
1802
  case "cursor":
1531
1803
  return { host, route: "skip", reason: CURSOR_SKIP_REASON };
1532
1804
  case "claude": {
1533
- const root = override ?? path5.join(targetHome, ".claude");
1534
- return fileLayout(host, path5.join(root, "agents"), "massa-ai-*.md", path5.join(root, "massa-ai", "agent-profiles"));
1805
+ const marketplaceRoot = opts.marketplaceRoot?.claude;
1806
+ if (override === undefined && marketplaceRoot !== undefined) {
1807
+ return fileLayout(host, path6.join(marketplaceRoot, "agents"), "massa-ai-*.md", path6.join(marketplaceRoot, "agent-profiles"));
1808
+ }
1809
+ const root = override ?? path6.join(targetHome, ".claude");
1810
+ return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.md", path6.join(root, "massa-ai", "agent-profiles"));
1535
1811
  }
1536
1812
  case "codex": {
1537
- const root = override ?? path5.join(targetHome, ".codex");
1538
- return fileLayout(host, path5.join(root, "agents"), "massa-ai-*.toml", path5.join(root, "massa-ai", "agent-profiles"));
1813
+ const root = override ?? path6.join(targetHome, ".codex");
1814
+ return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.toml", path6.join(root, "massa-ai", "agent-profiles"));
1539
1815
  }
1540
1816
  case "opencode": {
1541
- const root = override ?? path5.join(targetHome, ".config", "opencode");
1542
- const pluginsDir = path5.join(root, "plugins", "massa-ai");
1543
- return fileLayout(host, path5.join(root, "agents"), "massa-ai-*.md", path5.join(pluginsDir, "agent-profiles"));
1817
+ const root = override ?? path6.join(targetHome, ".config", "opencode");
1818
+ const pluginsDir = path6.join(root, "plugins", "massa-ai");
1819
+ return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.md", path6.join(pluginsDir, "agent-profiles"));
1544
1820
  }
1545
1821
  }
1546
1822
  }
1547
- function detectRoute(platform) {
1823
+ function detectRoute(platform, host) {
1548
1824
  const route = platform?.installRoute;
1549
1825
  if (route === "file")
1550
1826
  return { kind: "proceed" };
1551
1827
  if (route === "marketplace") {
1828
+ if (host === "claude")
1829
+ return { kind: "proceed" };
1830
+ if (host === "codex") {
1831
+ return {
1832
+ kind: "refuse",
1833
+ reason: "codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
1834
+ };
1835
+ }
1552
1836
  return {
1553
1837
  kind: "refuse",
1554
1838
  reason: "claude/codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
@@ -1561,7 +1845,7 @@ function detectRoute(platform) {
1561
1845
  }
1562
1846
  // ../../packages/shared/dist/profile-switch/state.js
1563
1847
  import fs3 from "fs";
1564
- import path6 from "path";
1848
+ import path7 from "path";
1565
1849
 
1566
1850
  class InstallStateError extends Error {
1567
1851
  constructor(message) {
@@ -1617,7 +1901,7 @@ function writeInstallState(filePath, state) {
1617
1901
  const text = `${JSON.stringify(validated, null, 2)}
1618
1902
  `;
1619
1903
  try {
1620
- fs3.mkdirSync(path6.dirname(filePath), { recursive: true });
1904
+ fs3.mkdirSync(path7.dirname(filePath), { recursive: true });
1621
1905
  fs3.writeFileSync(filePath, text);
1622
1906
  } catch (err) {
1623
1907
  throw UnwritableInstallStateError(filePath, err.message);
@@ -1636,7 +1920,7 @@ function updatePlatform(filePath, host, patch) {
1636
1920
  }
1637
1921
  // ../../packages/shared/dist/profile-switch/lock.js
1638
1922
  import fs4 from "fs";
1639
- import path7 from "path";
1923
+ import path8 from "path";
1640
1924
  import os4 from "os";
1641
1925
  import crypto2 from "crypto";
1642
1926
  import { execFileSync } from "child_process";
@@ -1693,7 +1977,7 @@ function releaseIfOwned(lockDir, ownerPath, token) {
1693
1977
  }
1694
1978
  function acquireLock(stateFilePath, options = {}) {
1695
1979
  const lockDir = `${stateFilePath}.switch.lock`;
1696
- const ownerPath = path7.join(lockDir, "owner.json");
1980
+ const ownerPath = path8.join(lockDir, "owner.json");
1697
1981
  const clock = options.clock ?? DEFAULT_CLOCK;
1698
1982
  const identity = options.identity ?? DEFAULT_IDENTITY;
1699
1983
  const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
@@ -1713,7 +1997,7 @@ function acquireLock(stateFilePath, options = {}) {
1713
1997
  token,
1714
1998
  timestamp: clock.now()
1715
1999
  };
1716
- fs4.mkdirSync(path7.dirname(ownerPath), { recursive: true });
2000
+ fs4.mkdirSync(path8.dirname(ownerPath), { recursive: true });
1717
2001
  fs4.writeFileSync(ownerPath, JSON.stringify(record));
1718
2002
  return { lockDir, release: () => releaseIfOwned(lockDir, ownerPath, token) };
1719
2003
  };
@@ -1741,10 +2025,60 @@ function acquireLock(stateFilePath, options = {}) {
1741
2025
  }
1742
2026
  }
1743
2027
  // ../../packages/shared/dist/profile-switch/engine.js
2028
+ import fs6 from "fs";
2029
+ import path10 from "path";
2030
+ import os6 from "os";
2031
+ import crypto3 from "crypto";
2032
+
2033
+ // ../../packages/shared/dist/profile-switch/claude-marketplace.js
1744
2034
  import fs5 from "fs";
1745
- import path8 from "path";
1746
2035
  import os5 from "os";
1747
- import crypto3 from "crypto";
2036
+ import path9 from "path";
2037
+ var DEFAULT_PLUGIN_KEY = "massa-ai@massa-ai";
2038
+ function selectRecord(records) {
2039
+ if (records.length === 0)
2040
+ return;
2041
+ const userScoped = records.filter((r) => r.scope === "user");
2042
+ const pool = userScoped.length > 0 ? userScoped : records;
2043
+ let best;
2044
+ let bestTime = -Infinity;
2045
+ for (const record of pool) {
2046
+ const parsed = record.lastUpdated ? Date.parse(record.lastUpdated) : NaN;
2047
+ if (Number.isFinite(parsed) && parsed >= bestTime) {
2048
+ best = record;
2049
+ bestTime = parsed;
2050
+ }
2051
+ }
2052
+ return best ?? pool[pool.length - 1];
2053
+ }
2054
+ function resolveClaudeMarketplaceRoot(opts = {}) {
2055
+ const targetHome = opts.targetHome ?? os5.homedir();
2056
+ const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
2057
+ const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
2058
+ let records;
2059
+ try {
2060
+ const raw = fs5.readFileSync(registryPath, "utf8");
2061
+ const parsed = JSON.parse(raw);
2062
+ records = parsed?.plugins?.[pluginKey];
2063
+ } catch {
2064
+ return null;
2065
+ }
2066
+ if (!Array.isArray(records) || records.length === 0)
2067
+ return null;
2068
+ const selected = selectRecord(records);
2069
+ const installPath = selected?.installPath;
2070
+ if (!installPath)
2071
+ return null;
2072
+ try {
2073
+ if (!fs5.existsSync(installPath))
2074
+ return null;
2075
+ } catch {
2076
+ return null;
2077
+ }
2078
+ return installPath;
2079
+ }
2080
+
2081
+ // ../../packages/shared/dist/profile-switch/engine.js
1748
2082
  class SwitchEngineError extends Error {
1749
2083
  constructor(message) {
1750
2084
  super(message);
@@ -1759,19 +2093,39 @@ function namedError3(name, message) {
1759
2093
  var UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`);
1760
2094
  var NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found");
1761
2095
  function defaultStatePath(targetHome) {
1762
- return path8.join(targetHome, ".config", "massa-ai", "install-state.json");
2096
+ return path10.join(targetHome, ".config", "massa-ai", "install-state.json");
1763
2097
  }
1764
2098
  function resolveCommon(opts) {
1765
- const targetHome = opts.targetHome ?? os5.homedir();
2099
+ const targetHome = opts.targetHome ?? os6.homedir();
1766
2100
  const stateFilePath = opts.stateFilePath ?? defaultStatePath(targetHome);
1767
2101
  return { targetHome, stateFilePath };
1768
2102
  }
2103
+ function marketplaceRoots(targetHome, state) {
2104
+ return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
2105
+ }
2106
+ function claudeMarketplaceUnresolvedReason(targetHome) {
2107
+ const registryPath = path10.join(targetHome, ".claude", "plugins", "installed_plugins.json");
2108
+ return `claude installRoute is "marketplace" but no install root could be resolved from ${registryPath} ` + "\u2014 re-run the Claude plugin installer, or verify the plugin registry file";
2109
+ }
1769
2110
  function listProfiles(opts = {}) {
1770
2111
  const { targetHome, stateFilePath } = resolveCommon(opts);
1771
2112
  const state = readInstallState(stateFilePath);
2113
+ const roots = marketplaceRoots(targetHome, state);
1772
2114
  const universe = opts.hosts ?? HOSTS;
1773
2115
  const hosts = universe.map((host) => {
1774
- const layout = resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot });
2116
+ if (host === "claude" && state.platforms.claude?.installRoute === "marketplace" && roots.claude === undefined) {
2117
+ const platform2 = state.platforms.claude;
2118
+ return {
2119
+ host,
2120
+ installed: false,
2121
+ skipped: false,
2122
+ skipReason: null,
2123
+ activeProfile: platform2.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
2124
+ bundleVersion: platform2.plugin?.version ?? null,
2125
+ availableProfiles: []
2126
+ };
2127
+ }
2128
+ const layout = resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots });
1775
2129
  if (layout.route === "skip") {
1776
2130
  return {
1777
2131
  host,
@@ -1783,7 +2137,7 @@ function listProfiles(opts = {}) {
1783
2137
  availableProfiles: []
1784
2138
  };
1785
2139
  }
1786
- const installed = fs5.existsSync(layout.activeDir);
2140
+ const installed = fs6.existsSync(layout.activeDir);
1787
2141
  const availableProfiles = listVariantProfiles(layout);
1788
2142
  const platform = state.platforms[host];
1789
2143
  return {
@@ -1799,9 +2153,9 @@ function listProfiles(opts = {}) {
1799
2153
  return { hosts };
1800
2154
  }
1801
2155
  function listVariantProfiles(layout) {
1802
- if (!fs5.existsSync(layout.variantsRoot))
2156
+ if (!fs6.existsSync(layout.variantsRoot))
1803
2157
  return [];
1804
- return fs5.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
2158
+ return fs6.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
1805
2159
  }
1806
2160
  function matchesGlob(filename, glob) {
1807
2161
  const starIdx = glob.indexOf("*");
@@ -1812,50 +2166,50 @@ function matchesGlob(filename, glob) {
1812
2166
  return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
1813
2167
  }
1814
2168
  function assertStateWritable(stateFilePath) {
1815
- const dir = path8.dirname(stateFilePath);
2169
+ const dir = path10.dirname(stateFilePath);
1816
2170
  try {
1817
- fs5.mkdirSync(dir, { recursive: true });
2171
+ fs6.mkdirSync(dir, { recursive: true });
1818
2172
  } catch (err) {
1819
2173
  throw UnwritableInstallStateError(stateFilePath, err.message);
1820
2174
  }
1821
- const checkPath = fs5.existsSync(stateFilePath) ? stateFilePath : dir;
2175
+ const checkPath = fs6.existsSync(stateFilePath) ? stateFilePath : dir;
1822
2176
  try {
1823
- fs5.accessSync(checkPath, fs5.constants.W_OK);
2177
+ fs6.accessSync(checkPath, fs6.constants.W_OK);
1824
2178
  } catch (err) {
1825
2179
  throw UnwritableInstallStateError(stateFilePath, err.message);
1826
2180
  }
1827
2181
  }
1828
2182
  function copyFileRouteVariant(layout, variantDir) {
1829
- fs5.mkdirSync(layout.activeDir, { recursive: true });
2183
+ fs6.mkdirSync(layout.activeDir, { recursive: true });
1830
2184
  let changed = 0;
1831
- for (const entry of fs5.readdirSync(variantDir, { withFileTypes: true })) {
2185
+ for (const entry of fs6.readdirSync(variantDir, { withFileTypes: true })) {
1832
2186
  if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
1833
2187
  continue;
1834
- fs5.copyFileSync(path8.join(variantDir, entry.name), path8.join(layout.activeDir, entry.name));
2188
+ fs6.copyFileSync(path10.join(variantDir, entry.name), path10.join(layout.activeDir, entry.name));
1835
2189
  changed++;
1836
2190
  }
1837
2191
  return changed;
1838
2192
  }
1839
2193
  function repointOpencodeVariant(layout, variantDir) {
1840
- fs5.mkdirSync(layout.activeDir, { recursive: true });
2194
+ fs6.mkdirSync(layout.activeDir, { recursive: true });
1841
2195
  let changed = 0;
1842
- for (const entry of fs5.readdirSync(variantDir, { withFileTypes: true })) {
2196
+ for (const entry of fs6.readdirSync(variantDir, { withFileTypes: true })) {
1843
2197
  if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
1844
2198
  continue;
1845
- const dest = path8.join(layout.activeDir, entry.name);
1846
- const target = path8.resolve(path8.join(variantDir, entry.name));
2199
+ const dest = path10.join(layout.activeDir, entry.name);
2200
+ const target = path10.resolve(path10.join(variantDir, entry.name));
1847
2201
  let destExists = true;
1848
2202
  let destIsSymlink = false;
1849
2203
  try {
1850
- destIsSymlink = fs5.lstatSync(dest).isSymbolicLink();
2204
+ destIsSymlink = fs6.lstatSync(dest).isSymbolicLink();
1851
2205
  } catch {
1852
2206
  destExists = false;
1853
2207
  }
1854
2208
  if (destExists && !destIsSymlink)
1855
2209
  continue;
1856
2210
  const tmp = `${dest}.massa-ai-switch.${crypto3.randomUUID()}`;
1857
- fs5.symlinkSync(target, tmp);
1858
- fs5.renameSync(tmp, dest);
2211
+ fs6.symlinkSync(target, tmp);
2212
+ fs6.renameSync(tmp, dest);
1859
2213
  changed++;
1860
2214
  }
1861
2215
  return changed;
@@ -1871,19 +2225,37 @@ function switchProfile(opts) {
1871
2225
  const state = readInstallState(stateFilePath);
1872
2226
  if (!dryRun)
1873
2227
  assertStateWritable(stateFilePath);
1874
- const layouts = universe.map((host) => ({ host, layout: resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot }) }));
1875
- const skipRows = layouts.filter((l) => l.layout.route === "skip").map((l) => ({ host: l.host, status: "skipped", reason: l.layout.reason }));
2228
+ const roots = marketplaceRoots(targetHome, state);
2229
+ const unresolvedRows = [];
2230
+ const resolvableUniverse = universe.filter((host) => {
2231
+ if (host !== "claude")
2232
+ return true;
2233
+ if (state.platforms.claude?.installRoute !== "marketplace")
2234
+ return true;
2235
+ if (roots.claude !== undefined)
2236
+ return true;
2237
+ unresolvedRows.push({ host, status: "failed", reason: claudeMarketplaceUnresolvedReason(targetHome) });
2238
+ return false;
2239
+ });
2240
+ const layouts = resolvableUniverse.map((host) => ({
2241
+ host,
2242
+ layout: resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots })
2243
+ }));
2244
+ const skipRows = [
2245
+ ...unresolvedRows,
2246
+ ...layouts.filter((l) => l.layout.route === "skip").map((l) => ({ host: l.host, status: "skipped", reason: l.layout.reason }))
2247
+ ];
1876
2248
  const fileHosts = layouts.filter((l) => l.layout.route === "files");
1877
2249
  if (fileHosts.length === 0) {
1878
2250
  return { profile: opts.profile, dryRun, hosts: orderRows(universe, skipRows), restartRequired: false };
1879
2251
  }
1880
- const installedFileHosts = fileHosts.filter((h) => fs5.existsSync(h.layout.activeDir));
2252
+ const installedFileHosts = fileHosts.filter((h) => fs6.existsSync(h.layout.activeDir));
1881
2253
  if (installedFileHosts.length === 0)
1882
2254
  throw NoHostsDetectedError();
1883
2255
  const withAvailability = fileHosts.map((h) => {
1884
- const variantsRootExists = fs5.existsSync(h.layout.variantsRoot);
2256
+ const variantsRootExists = fs6.existsSync(h.layout.variantsRoot);
1885
2257
  const variantDir = h.layout.variantDir(opts.profile);
1886
- const available = variantsRootExists && fs5.existsSync(variantDir) && fs5.statSync(variantDir).isDirectory();
2258
+ const available = variantsRootExists && fs6.existsSync(variantDir) && fs6.statSync(variantDir).isDirectory();
1887
2259
  return { ...h, variantsRootExists, variantDir, available };
1888
2260
  });
1889
2261
  if (!withAvailability.some((h) => h.available)) {
@@ -1913,7 +2285,7 @@ function switchProfile(opts) {
1913
2285
  });
1914
2286
  continue;
1915
2287
  }
1916
- const route = detectRoute(state.platforms[h.host]);
2288
+ const route = detectRoute(state.platforms[h.host], h.host);
1917
2289
  if (route.kind === "refuse") {
1918
2290
  rows.push({ host: h.host, status: "failed", reason: route.reason });
1919
2291
  continue;
@@ -1948,19 +2320,26 @@ function reportSucceeded(report) {
1948
2320
  return report.hosts.every((h) => h.status === "switched" || h.status === "skipped");
1949
2321
  }
1950
2322
  // ../../packages/shared/dist/profile-switch/variant-sync.js
1951
- import fs6 from "fs";
1952
- import path9 from "path";
2323
+ import fs7 from "fs";
2324
+ import path11 from "path";
2325
+ import os7 from "os";
1953
2326
  import crypto4 from "crypto";
2327
+ function defaultStatePath2(targetHome) {
2328
+ return path11.join(targetHome, ".config", "massa-ai", "install-state.json");
2329
+ }
2330
+ function marketplaceRoots2(targetHome, state) {
2331
+ return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
2332
+ }
1954
2333
  var tempFileCounter2 = 0;
1955
2334
  function writeFileIntoDirAtomically(destDir, destName, content) {
1956
2335
  const unique = `${process.pid}.${++tempFileCounter2}.${crypto4.randomBytes(6).toString("hex")}`;
1957
- const tempFile = path9.join(destDir, `.${destName}.${unique}.tmp`);
2336
+ const tempFile = path11.join(destDir, `.${destName}.${unique}.tmp`);
1958
2337
  try {
1959
- fs6.writeFileSync(tempFile, content);
1960
- fs6.renameSync(tempFile, path9.join(destDir, destName));
2338
+ fs7.writeFileSync(tempFile, content);
2339
+ fs7.renameSync(tempFile, path11.join(destDir, destName));
1961
2340
  } catch (error) {
1962
2341
  try {
1963
- fs6.unlinkSync(tempFile);
2342
+ fs7.unlinkSync(tempFile);
1964
2343
  } catch {}
1965
2344
  throw error;
1966
2345
  }
@@ -1968,20 +2347,20 @@ function writeFileIntoDirAtomically(destDir, destName, content) {
1968
2347
  function isSafeDirName(name) {
1969
2348
  if (name === "." || name === "..")
1970
2349
  return false;
1971
- if (name.includes("/") || name.includes("\\") || name.includes(path9.sep))
2350
+ if (name.includes("/") || name.includes("\\") || name.includes(path11.sep))
1972
2351
  return false;
1973
- return path9.basename(name) === name;
2352
+ return path11.basename(name) === name;
1974
2353
  }
1975
- function syncHost(host, sourceRoot, targetHome) {
1976
- const layout = resolveHostLayout(host, { targetHome });
2354
+ function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
2355
+ const layout = resolveHostLayout(host, { targetHome, marketplaceRoot });
1977
2356
  if (layout.route === "skip") {
1978
2357
  return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: layout.reason };
1979
2358
  }
1980
- const srcDir = path9.join(sourceRoot, "apps", `${host}-plugin`, "agent-profiles");
1981
- if (!fs6.existsSync(srcDir) || !fs6.statSync(srcDir).isDirectory()) {
2359
+ const srcDir = path11.join(sourceRoot, "apps", `${host}-plugin`, "agent-profiles");
2360
+ if (!fs7.existsSync(srcDir) || !fs7.statSync(srcDir).isDirectory()) {
1982
2361
  return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: "no generated variants for this host" };
1983
2362
  }
1984
- if (!fs6.existsSync(layout.variantsRoot)) {
2363
+ if (!fs7.existsSync(layout.variantsRoot)) {
1985
2364
  return {
1986
2365
  host,
1987
2366
  status: "skipped",
@@ -1993,24 +2372,24 @@ function syncHost(host, sourceRoot, targetHome) {
1993
2372
  }
1994
2373
  const profiles = [];
1995
2374
  let files = 0;
1996
- for (const entry of fs6.readdirSync(srcDir, { withFileTypes: true })) {
2375
+ for (const entry of fs7.readdirSync(srcDir, { withFileTypes: true })) {
1997
2376
  if (!entry.isDirectory())
1998
2377
  continue;
1999
2378
  if (!isSafeDirName(entry.name))
2000
2379
  continue;
2001
- const srcProfileDir = path9.join(srcDir, entry.name);
2002
- const destProfileDir = path9.join(layout.variantsRoot, entry.name);
2003
- fs6.mkdirSync(destProfileDir, { recursive: true });
2004
- for (const fileEntry of fs6.readdirSync(srcProfileDir, { withFileTypes: true })) {
2380
+ const srcProfileDir = path11.join(srcDir, entry.name);
2381
+ const destProfileDir = path11.join(layout.variantsRoot, entry.name);
2382
+ fs7.mkdirSync(destProfileDir, { recursive: true });
2383
+ for (const fileEntry of fs7.readdirSync(srcProfileDir, { withFileTypes: true })) {
2005
2384
  if (!fileEntry.isFile())
2006
2385
  continue;
2007
- const content = fs6.readFileSync(path9.join(srcProfileDir, fileEntry.name));
2386
+ const content = fs7.readFileSync(path11.join(srcProfileDir, fileEntry.name));
2008
2387
  writeFileIntoDirAtomically(destProfileDir, fileEntry.name, content);
2009
2388
  files++;
2010
2389
  }
2011
2390
  profiles.push(entry.name);
2012
2391
  }
2013
- const retained = fs6.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && !profiles.includes(e.name)).map((e) => e.name).sort();
2392
+ const retained = fs7.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && !profiles.includes(e.name)).map((e) => e.name).sort();
2014
2393
  return { host, status: "synced", profiles: profiles.sort(), retained, files };
2015
2394
  }
2016
2395
  function syncGeneratedVariants(opts) {
@@ -2026,23 +2405,26 @@ function syncGeneratedVariants(opts) {
2026
2405
  }));
2027
2406
  }
2028
2407
  const sourceRoot = opts.sourceRoot;
2408
+ const targetHome = opts.targetHome ?? os7.homedir();
2409
+ const state = readInstallState(defaultStatePath2(targetHome));
2410
+ const roots = marketplaceRoots2(targetHome, state);
2029
2411
  return hosts.map((host) => {
2030
2412
  try {
2031
- return syncHost(host, sourceRoot, opts.targetHome);
2413
+ return syncHost(host, sourceRoot, opts.targetHome, roots);
2032
2414
  } catch (err) {
2033
2415
  return { host, status: "failed", profiles: [], retained: [], files: 0, error: err.message };
2034
2416
  }
2035
2417
  });
2036
2418
  }
2037
2419
  // ../../packages/shared/dist/profile-switch/repo-root.js
2038
- import fs7 from "fs";
2039
- import path10 from "path";
2420
+ import fs8 from "fs";
2421
+ import path12 from "path";
2040
2422
  function findRepoRootWithMarker(startDir, marker, maxLevels) {
2041
2423
  let dir = startDir;
2042
2424
  for (let i = 0;i <= maxLevels; i++) {
2043
- if (fs7.existsSync(path10.join(dir, marker)))
2425
+ if (fs8.existsSync(path12.join(dir, marker)))
2044
2426
  return dir;
2045
- const parent = path10.dirname(dir);
2427
+ const parent = path12.dirname(dir);
2046
2428
  if (parent === dir)
2047
2429
  break;
2048
2430
  dir = parent;
@@ -2050,11 +2432,11 @@ function findRepoRootWithMarker(startDir, marker, maxLevels) {
2050
2432
  return null;
2051
2433
  }
2052
2434
  // src/config-cli.ts
2053
- import { promises as fs8 } from "fs";
2054
- import path11 from "path";
2055
- import os6 from "os";
2435
+ import { promises as fs9 } from "fs";
2436
+ import path13 from "path";
2437
+ import os8 from "os";
2056
2438
  import { fileURLToPath } from "url";
2057
- var __dirname2 = path11.dirname(fileURLToPath(import.meta.url));
2439
+ var __dirname2 = path13.dirname(fileURLToPath(import.meta.url));
2058
2440
  var GENERATOR_MARKER = "scripts/generate-subagent-artifacts.ts";
2059
2441
  var GENERATOR_MARKER_MAX_LEVELS = 6;
2060
2442
  function formatVariantSync(results) {
@@ -2265,18 +2647,18 @@ Using defaults:`);
2265
2647
  return 1;
2266
2648
  }
2267
2649
  const scope = typeof options.project === "boolean" ? "project" : "user";
2268
- const agentsDir = scope === "project" ? path11.join(process.cwd(), ".opencode/agents") : path11.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path11.join(os6.homedir(), ".config"), "opencode", "agents");
2269
- const sourceAgentsDir = path11.resolve(__dirname2, "..", "agents");
2650
+ const agentsDir = scope === "project" ? path13.join(process.cwd(), ".opencode/agents") : path13.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path13.join(os8.homedir(), ".config"), "opencode", "agents");
2651
+ const sourceAgentsDir = path13.resolve(__dirname2, "..", "agents");
2270
2652
  if (subcommand === "install") {
2271
- await fs8.mkdir(agentsDir, { recursive: true });
2653
+ await fs9.mkdir(agentsDir, { recursive: true });
2272
2654
  let count = 0;
2273
- const entries = await fs8.readdir(sourceAgentsDir);
2655
+ const entries = await fs9.readdir(sourceAgentsDir);
2274
2656
  for (const entry of entries) {
2275
2657
  if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
2276
2658
  continue;
2277
- const src = path11.join(sourceAgentsDir, entry);
2278
- const dest = path11.join(agentsDir, entry);
2279
- await fs8.copyFile(src, dest);
2659
+ const src = path13.join(sourceAgentsDir, entry);
2660
+ const dest = path13.join(agentsDir, entry);
2661
+ await fs9.copyFile(src, dest);
2280
2662
  count++;
2281
2663
  }
2282
2664
  console.log(`+ ${count} subagent specialists (generated from skills/agents/*/SKILL.md)`);
@@ -2284,14 +2666,14 @@ Using defaults:`);
2284
2666
  } else {
2285
2667
  let removed = 0;
2286
2668
  try {
2287
- const entries = await fs8.readdir(agentsDir);
2669
+ const entries = await fs9.readdir(agentsDir);
2288
2670
  for (const entry of entries) {
2289
2671
  if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
2290
2672
  continue;
2291
- const filePath = path11.join(agentsDir, entry);
2292
- const content = await fs8.readFile(filePath, "utf8");
2673
+ const filePath = path13.join(agentsDir, entry);
2674
+ const content = await fs9.readFile(filePath, "utf8");
2293
2675
  if (content.includes("massa-ai-owned: true")) {
2294
- await fs8.unlink(filePath);
2676
+ await fs9.unlink(filePath);
2295
2677
  removed++;
2296
2678
  }
2297
2679
  }
package/dist/index.js CHANGED
@@ -400,9 +400,16 @@ var init_xdg = () => {};
400
400
 
401
401
  // ../../packages/shared/dist/config/massa-ai-config.js
402
402
  import path2 from "path";
403
- var defaultMassaAiConfig;
403
+ var SCHEDULER_JOB_KINDS, defaultMassaAiConfig;
404
404
  var init_massa_ai_config = __esm(() => {
405
405
  init_xdg();
406
+ SCHEDULER_JOB_KINDS = [
407
+ "memory-consolidation",
408
+ "decay-sweep",
409
+ "auto-improve",
410
+ "observation-bridge",
411
+ "checkpoint-purge"
412
+ ];
406
413
  defaultMassaAiConfig = {
407
414
  database: {
408
415
  url: ""
@@ -733,11 +740,13 @@ try {
733
740
  // ../../packages/shared/dist/config/index.js
734
741
  init_config_loader();
735
742
  init_massa_ai_config();
743
+ init_massa_ai_config();
736
744
  init_config_loader();
737
745
  import path4 from "path";
738
746
 
739
747
  // ../../packages/shared/dist/config/config-writer.js
740
748
  init_config_loader();
749
+ init_massa_ai_config();
741
750
 
742
751
  // ../../packages/shared/dist/config/index.js
743
752
  init_xdg();
@@ -777,6 +786,49 @@ function envList(key, fallback) {
777
786
  const parsed = s.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
778
787
  return parsed.length > 0 ? parsed : fallback;
779
788
  }
789
+ function readSchedulerConfig(rawFileConfig) {
790
+ try {
791
+ const cfg = rawFileConfig;
792
+ return cfg?.scheduler ?? {};
793
+ } catch {
794
+ return {};
795
+ }
796
+ }
797
+ var SCHEDULER_JOB_ENV = {
798
+ "memory-consolidation": {
799
+ enabledVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_ENABLED",
800
+ intervalVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_INTERVAL_MS",
801
+ defaultIntervalMs: 30 * 60 * 1000
802
+ },
803
+ "decay-sweep": {
804
+ enabledVar: "MASSA_AI_SCHEDULER_DECAY_ENABLED",
805
+ intervalVar: "MASSA_AI_SCHEDULER_DECAY_INTERVAL_MS",
806
+ defaultIntervalMs: 60 * 60 * 1000
807
+ },
808
+ "auto-improve": {
809
+ enabledVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_ENABLED",
810
+ intervalVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_INTERVAL_MS",
811
+ defaultIntervalMs: 30 * 60 * 1000
812
+ },
813
+ "observation-bridge": {
814
+ enabledVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_ENABLED",
815
+ intervalVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_INTERVAL_MS",
816
+ defaultIntervalMs: 30 * 60 * 1000
817
+ },
818
+ "checkpoint-purge": {
819
+ enabledVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_ENABLED",
820
+ intervalVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_INTERVAL_MS",
821
+ defaultIntervalMs: 60 * 60 * 1000
822
+ }
823
+ };
824
+ function resolveSchedulerJob(kind, fileJobs) {
825
+ const meta = SCHEDULER_JOB_ENV[kind];
826
+ const fileJob = fileJobs?.[kind];
827
+ return {
828
+ enabled: envBool(meta.enabledVar, fileJob?.enabled ?? false),
829
+ intervalMs: envNum(meta.intervalVar, fileJob?.intervalMs ?? meta.defaultIntervalMs)
830
+ };
831
+ }
780
832
  var MAX_IGNORE_PATTERNS = 1024;
781
833
  function validateCapturePolicyConfig(raw) {
782
834
  if (!raw || typeof raw !== "object")
@@ -880,10 +932,11 @@ var DEFAULT_ALLOWED_EXTENSIONS = [
880
932
  var fileConfig = loadConfigSafe();
881
933
  var fileCacheL1Bytes = fileConfig.cache?.l1MaxSizeMB ? fileConfig.cache.l1MaxSizeMB * 1024 * 1024 : undefined;
882
934
  var fileCacheL2Bytes = fileConfig.cache?.l2MaxSizeMB ? fileConfig.cache.l2MaxSizeMB * 1024 * 1024 : undefined;
935
+ var resolvedDataDir = getGlobalDataDir();
883
936
  var defaultConfig = {
884
937
  name: "massa-ai-server",
885
938
  version: "1.0.0",
886
- dataDir: getGlobalDataDir(),
939
+ dataDir: resolvedDataDir,
887
940
  cache: {
888
941
  l1: {
889
942
  maxSize: envNum("L1_CACHE_MAX_SIZE", fileCacheL1Bytes ?? 100 * 1024 * 1024),
@@ -1007,8 +1060,24 @@ var defaultConfig = {
1007
1060
  logging: {
1008
1061
  level: process.env.LOG_LEVEL || fileConfig.logging?.level || "info",
1009
1062
  enableMetrics: process.env.ENABLE_METRICS === "true" || process.env.ENABLE_METRICS === undefined && !!fileConfig.logging?.enableMetrics,
1010
- file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || undefined
1063
+ file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || path4.join(resolvedDataDir, "logs", "massa-ai.log"),
1064
+ enableFileSink: envBool("MASSA_AI_LOG_ENABLE_FILE_SINK", fileConfig.logging?.enableFileSink ?? true),
1065
+ bufferSize: envNum("MASSA_AI_LOG_BUFFER_SIZE", fileConfig.logging?.bufferSize ?? 2000),
1066
+ maxFileSizeMb: envNum("MASSA_AI_LOG_MAX_FILE_SIZE_MB", fileConfig.logging?.maxFileSizeMb ?? 32),
1067
+ maxFiles: envNum("MASSA_AI_LOG_MAX_FILES", fileConfig.logging?.maxFiles ?? 5)
1011
1068
  },
1069
+ scheduler: (() => {
1070
+ const fileScheduler = readSchedulerConfig(fileConfig);
1071
+ return {
1072
+ enabled: envBool("MASSA_AI_SCHEDULER_ENABLED", fileScheduler.enabled ?? false),
1073
+ tickMs: envNum("MASSA_AI_SCHEDULER_TICK_MS", fileScheduler.tickMs ?? 60000),
1074
+ maxConcurrent: envNum("MASSA_AI_SCHEDULER_MAX_CONCURRENT", fileScheduler.maxConcurrent ?? 2),
1075
+ jobs: Object.fromEntries(SCHEDULER_JOB_KINDS.map((kind) => [
1076
+ kind,
1077
+ resolveSchedulerJob(kind, fileScheduler.jobs)
1078
+ ]))
1079
+ };
1080
+ })(),
1012
1081
  synapse: {
1013
1082
  enabled: process.env.SYNAPSE_ENABLED !== "false",
1014
1083
  inhibition: {
@@ -1123,6 +1192,11 @@ class Config {
1123
1192
  rateLimit: { ...defaults.rateLimit, ...overrides.rateLimit },
1124
1193
  security: { ...defaults.security, ...overrides.security },
1125
1194
  logging: { ...defaults.logging, ...overrides.logging },
1195
+ scheduler: {
1196
+ ...defaults.scheduler,
1197
+ ...overrides.scheduler,
1198
+ jobs: { ...defaults.scheduler.jobs, ...overrides.scheduler?.jobs }
1199
+ },
1126
1200
  synapse: {
1127
1201
  ...defaults.synapse,
1128
1202
  ...overrides.synapse,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/opencode-plugin",
3
- "version": "1.45.0",
3
+ "version": "1.47.0",
4
4
  "description": "massa-ai plugin for OpenCode - Semantic code search, memory, and context compression",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -24,8 +24,8 @@
24
24
  "dependencies": {
25
25
  "@opencode-ai/plugin": "^1.2.15",
26
26
  "@opencode-ai/sdk": "^1.2.15",
27
- "@massa-ai/core": "^1.45.0",
28
- "@massa-ai/shared": "^1.45.0"
27
+ "@massa-ai/core": "^1.47.0",
28
+ "@massa-ai/shared": "^1.47.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.5",