@massa-ai/opencode-plugin 1.46.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config-cli.js +571 -108
- package/dist/index.js +162 -7
- package/package.json +3 -3
package/dist/config-cli.js
CHANGED
|
@@ -401,9 +401,64 @@ 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, MAX_MATCH_WORK = 1e5, MAX_IGNORE_PATTERNS = 1024, DEFAULT_CAPTURE_POLICY, DEFAULT_SCHEDULER_CONFIG, 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
|
+
];
|
|
414
|
+
DEFAULT_CAPTURE_POLICY = {
|
|
415
|
+
rules: [
|
|
416
|
+
{ pattern: "**/node_modules/**", disposition: "Drop" },
|
|
417
|
+
{ pattern: "**/.git/**", disposition: "Drop" },
|
|
418
|
+
{ pattern: "**/dist/**", disposition: "Drop" },
|
|
419
|
+
{ pattern: "**/build/**", disposition: "Drop" },
|
|
420
|
+
{ pattern: "**/coverage/**", disposition: "Drop" },
|
|
421
|
+
{ pattern: ".env", disposition: "Drop" },
|
|
422
|
+
{ pattern: ".env.*", disposition: "Drop" },
|
|
423
|
+
{ pattern: "**/generated/**", disposition: "Drop" },
|
|
424
|
+
{ pattern: "**/*.generated.*", disposition: "Drop" },
|
|
425
|
+
{ pattern: "**/*.d.ts", disposition: "Drop" },
|
|
426
|
+
{ pattern: "**/__tests__/**", disposition: "Drop" },
|
|
427
|
+
{ pattern: "**/tests/**", disposition: "Drop" },
|
|
428
|
+
{ pattern: "**/*.test.ts", disposition: "Drop" },
|
|
429
|
+
{ pattern: "**/*.test.tsx", disposition: "Drop" },
|
|
430
|
+
{ pattern: "**/*.test.js", disposition: "Drop" },
|
|
431
|
+
{ pattern: "**/*.test.jsx", disposition: "Drop" },
|
|
432
|
+
{ pattern: "**/*.spec.ts", disposition: "Drop" },
|
|
433
|
+
{ pattern: "**/*.spec.tsx", disposition: "Drop" },
|
|
434
|
+
{ pattern: "**/*.spec.js", disposition: "Drop" },
|
|
435
|
+
{ pattern: "**/*.spec.jsx", disposition: "Drop" },
|
|
436
|
+
{ pattern: "**/benchmarks/**", disposition: "Drop" },
|
|
437
|
+
{ pattern: "**/fixtures/**", disposition: "Drop" },
|
|
438
|
+
{ pattern: "**/*.wasm*", disposition: "Drop" },
|
|
439
|
+
{ pattern: "**/*.min.*", disposition: "Drop" },
|
|
440
|
+
{ pattern: "**/*.map", disposition: "Drop" },
|
|
441
|
+
{ pattern: "**/lock.yaml", disposition: "Drop" },
|
|
442
|
+
{ pattern: "**/pnpm-lock.yaml", disposition: "Drop" },
|
|
443
|
+
{ pattern: "**/package-lock.json", disposition: "Drop" },
|
|
444
|
+
{ pattern: "**/bun.lockb", disposition: "Drop" },
|
|
445
|
+
{ pattern: "**/yarn.lock", disposition: "Drop" }
|
|
446
|
+
],
|
|
447
|
+
maxMatchWork: MAX_MATCH_WORK,
|
|
448
|
+
maxIgnorePatterns: MAX_IGNORE_PATTERNS
|
|
449
|
+
};
|
|
450
|
+
DEFAULT_SCHEDULER_CONFIG = {
|
|
451
|
+
enabled: false,
|
|
452
|
+
tickMs: 60000,
|
|
453
|
+
maxConcurrent: 2,
|
|
454
|
+
jobs: {
|
|
455
|
+
"memory-consolidation": { enabled: false, intervalMs: 30 * 60 * 1000 },
|
|
456
|
+
"decay-sweep": { enabled: false, intervalMs: 60 * 60 * 1000 },
|
|
457
|
+
"auto-improve": { enabled: false, intervalMs: 30 * 60 * 1000 },
|
|
458
|
+
"observation-bridge": { enabled: false, intervalMs: 30 * 60 * 1000 },
|
|
459
|
+
"checkpoint-purge": { enabled: false, intervalMs: 60 * 60 * 1000 }
|
|
460
|
+
}
|
|
461
|
+
};
|
|
407
462
|
defaultMassaAiConfig = {
|
|
408
463
|
database: {
|
|
409
464
|
url: ""
|
|
@@ -422,7 +477,7 @@ var init_massa_ai_config = __esm(() => {
|
|
|
422
477
|
impact: {
|
|
423
478
|
bfsCteEnabled: false
|
|
424
479
|
},
|
|
425
|
-
capturePolicy:
|
|
480
|
+
capturePolicy: DEFAULT_CAPTURE_POLICY,
|
|
426
481
|
cache: {
|
|
427
482
|
enabled: true,
|
|
428
483
|
l1MaxSizeMB: 100,
|
|
@@ -545,7 +600,8 @@ var init_massa_ai_config = __esm(() => {
|
|
|
545
600
|
},
|
|
546
601
|
security: {
|
|
547
602
|
corsOrigins: []
|
|
548
|
-
}
|
|
603
|
+
},
|
|
604
|
+
scheduler: DEFAULT_SCHEDULER_CONFIG
|
|
549
605
|
};
|
|
550
606
|
});
|
|
551
607
|
|
|
@@ -555,6 +611,8 @@ __export(exports_config_loader, {
|
|
|
555
611
|
writeFileAtomically: () => writeFileAtomically,
|
|
556
612
|
saveConfig: () => saveConfig,
|
|
557
613
|
migrateDataDirOnce: () => migrateDataDirOnce,
|
|
614
|
+
mergeSchedulerSection: () => mergeSchedulerSection,
|
|
615
|
+
loadRawUserConfig: () => loadRawUserConfig,
|
|
558
616
|
loadConfigSafe: () => loadConfigSafe,
|
|
559
617
|
loadConfig: () => loadConfig,
|
|
560
618
|
initConfig: () => initConfig,
|
|
@@ -577,6 +635,22 @@ function getConfigPath() {
|
|
|
577
635
|
function configExists() {
|
|
578
636
|
return fs.existsSync(CONFIG_FILE);
|
|
579
637
|
}
|
|
638
|
+
function mergeSchedulerJobs(base, incoming) {
|
|
639
|
+
const defaults = defaultMassaAiConfig.scheduler?.jobs ?? {};
|
|
640
|
+
const merged = {};
|
|
641
|
+
for (const kind of Object.keys(defaults)) {
|
|
642
|
+
merged[kind] = { ...defaults[kind], ...base?.[kind], ...incoming?.[kind] };
|
|
643
|
+
}
|
|
644
|
+
return merged;
|
|
645
|
+
}
|
|
646
|
+
function mergeSchedulerSection(base, incoming) {
|
|
647
|
+
return {
|
|
648
|
+
...defaultMassaAiConfig.scheduler,
|
|
649
|
+
...base,
|
|
650
|
+
...incoming,
|
|
651
|
+
jobs: mergeSchedulerJobs(base?.jobs, incoming?.jobs)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
580
654
|
function loadConfig() {
|
|
581
655
|
if (!fs.existsSync(CONFIG_FILE)) {
|
|
582
656
|
return defaultMassaAiConfig;
|
|
@@ -597,13 +671,27 @@ function loadConfig() {
|
|
|
597
671
|
memory: { ...defaultMassaAiConfig.memory, ...userConfig.memory },
|
|
598
672
|
hooks: { ...defaultMassaAiConfig.hooks, ...userConfig.hooks },
|
|
599
673
|
handoffs: { ...defaultMassaAiConfig.handoffs, ...userConfig.handoffs },
|
|
600
|
-
security: { ...defaultMassaAiConfig.security, ...userConfig.security }
|
|
674
|
+
security: { ...defaultMassaAiConfig.security, ...userConfig.security },
|
|
675
|
+
scheduler: mergeSchedulerSection(undefined, userConfig.scheduler),
|
|
676
|
+
capturePolicy: userConfig.capturePolicy ?? defaultMassaAiConfig.capturePolicy
|
|
601
677
|
};
|
|
602
678
|
} catch (error) {
|
|
603
679
|
console.error(`Error loading config from ${CONFIG_FILE}:`, error);
|
|
604
680
|
return defaultMassaAiConfig;
|
|
605
681
|
}
|
|
606
682
|
}
|
|
683
|
+
function loadRawUserConfig() {
|
|
684
|
+
try {
|
|
685
|
+
if (!fs.existsSync(CONFIG_FILE))
|
|
686
|
+
return {};
|
|
687
|
+
const parsed = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
688
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
689
|
+
return {};
|
|
690
|
+
return parsed;
|
|
691
|
+
} catch {
|
|
692
|
+
return {};
|
|
693
|
+
}
|
|
694
|
+
}
|
|
607
695
|
function loadConfigSafe() {
|
|
608
696
|
try {
|
|
609
697
|
return loadConfig();
|
|
@@ -734,11 +822,14 @@ try {
|
|
|
734
822
|
// ../../packages/shared/dist/config/index.js
|
|
735
823
|
init_config_loader();
|
|
736
824
|
init_massa_ai_config();
|
|
825
|
+
init_massa_ai_config();
|
|
826
|
+
init_massa_ai_config();
|
|
737
827
|
init_config_loader();
|
|
738
828
|
import path4 from "path";
|
|
739
829
|
|
|
740
830
|
// ../../packages/shared/dist/config/config-writer.js
|
|
741
831
|
init_config_loader();
|
|
832
|
+
init_massa_ai_config();
|
|
742
833
|
|
|
743
834
|
// ../../packages/shared/dist/config/index.js
|
|
744
835
|
init_xdg();
|
|
@@ -778,7 +869,49 @@ function envList(key, fallback) {
|
|
|
778
869
|
const parsed = s.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
|
|
779
870
|
return parsed.length > 0 ? parsed : fallback;
|
|
780
871
|
}
|
|
781
|
-
|
|
872
|
+
function readSchedulerConfig(rawFileConfig) {
|
|
873
|
+
try {
|
|
874
|
+
const cfg = rawFileConfig;
|
|
875
|
+
return cfg?.scheduler ?? {};
|
|
876
|
+
} catch {
|
|
877
|
+
return {};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
var SCHEDULER_JOB_ENV = {
|
|
881
|
+
"memory-consolidation": {
|
|
882
|
+
enabledVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_ENABLED",
|
|
883
|
+
intervalVar: "MASSA_AI_SCHEDULER_CONSOLIDATION_INTERVAL_MS",
|
|
884
|
+
defaultIntervalMs: 30 * 60 * 1000
|
|
885
|
+
},
|
|
886
|
+
"decay-sweep": {
|
|
887
|
+
enabledVar: "MASSA_AI_SCHEDULER_DECAY_ENABLED",
|
|
888
|
+
intervalVar: "MASSA_AI_SCHEDULER_DECAY_INTERVAL_MS",
|
|
889
|
+
defaultIntervalMs: 60 * 60 * 1000
|
|
890
|
+
},
|
|
891
|
+
"auto-improve": {
|
|
892
|
+
enabledVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_ENABLED",
|
|
893
|
+
intervalVar: "MASSA_AI_SCHEDULER_AUTO_IMPROVE_INTERVAL_MS",
|
|
894
|
+
defaultIntervalMs: 30 * 60 * 1000
|
|
895
|
+
},
|
|
896
|
+
"observation-bridge": {
|
|
897
|
+
enabledVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_ENABLED",
|
|
898
|
+
intervalVar: "MASSA_AI_SCHEDULER_OBSERVATION_BRIDGE_INTERVAL_MS",
|
|
899
|
+
defaultIntervalMs: 30 * 60 * 1000
|
|
900
|
+
},
|
|
901
|
+
"checkpoint-purge": {
|
|
902
|
+
enabledVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_ENABLED",
|
|
903
|
+
intervalVar: "MASSA_AI_SCHEDULER_CHECKPOINT_PURGE_INTERVAL_MS",
|
|
904
|
+
defaultIntervalMs: 60 * 60 * 1000
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
function resolveSchedulerJob(kind, fileJobs) {
|
|
908
|
+
const meta = SCHEDULER_JOB_ENV[kind];
|
|
909
|
+
const fileJob = fileJobs?.[kind];
|
|
910
|
+
return {
|
|
911
|
+
enabled: envBool(meta.enabledVar, fileJob?.enabled ?? false),
|
|
912
|
+
intervalMs: envNum(meta.intervalVar, fileJob?.intervalMs ?? meta.defaultIntervalMs)
|
|
913
|
+
};
|
|
914
|
+
}
|
|
782
915
|
function validateCapturePolicyConfig(raw) {
|
|
783
916
|
if (!raw || typeof raw !== "object")
|
|
784
917
|
throw new TypeError("capturePolicy must be an object");
|
|
@@ -881,10 +1014,11 @@ var DEFAULT_ALLOWED_EXTENSIONS = [
|
|
|
881
1014
|
var fileConfig = loadConfigSafe();
|
|
882
1015
|
var fileCacheL1Bytes = fileConfig.cache?.l1MaxSizeMB ? fileConfig.cache.l1MaxSizeMB * 1024 * 1024 : undefined;
|
|
883
1016
|
var fileCacheL2Bytes = fileConfig.cache?.l2MaxSizeMB ? fileConfig.cache.l2MaxSizeMB * 1024 * 1024 : undefined;
|
|
1017
|
+
var resolvedDataDir = getGlobalDataDir();
|
|
884
1018
|
var defaultConfig = {
|
|
885
1019
|
name: "massa-ai-server",
|
|
886
1020
|
version: "1.0.0",
|
|
887
|
-
dataDir:
|
|
1021
|
+
dataDir: resolvedDataDir,
|
|
888
1022
|
cache: {
|
|
889
1023
|
l1: {
|
|
890
1024
|
maxSize: envNum("L1_CACHE_MAX_SIZE", fileCacheL1Bytes ?? 100 * 1024 * 1024),
|
|
@@ -1008,8 +1142,24 @@ var defaultConfig = {
|
|
|
1008
1142
|
logging: {
|
|
1009
1143
|
level: process.env.LOG_LEVEL || fileConfig.logging?.level || "info",
|
|
1010
1144
|
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 ||
|
|
1145
|
+
file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || path4.join(resolvedDataDir, "logs", "massa-ai.log"),
|
|
1146
|
+
enableFileSink: envBool("MASSA_AI_LOG_ENABLE_FILE_SINK", fileConfig.logging?.enableFileSink ?? true),
|
|
1147
|
+
bufferSize: envNum("MASSA_AI_LOG_BUFFER_SIZE", fileConfig.logging?.bufferSize ?? 2000),
|
|
1148
|
+
maxFileSizeMb: envNum("MASSA_AI_LOG_MAX_FILE_SIZE_MB", fileConfig.logging?.maxFileSizeMb ?? 32),
|
|
1149
|
+
maxFiles: envNum("MASSA_AI_LOG_MAX_FILES", fileConfig.logging?.maxFiles ?? 5)
|
|
1012
1150
|
},
|
|
1151
|
+
scheduler: (() => {
|
|
1152
|
+
const fileScheduler = readSchedulerConfig(fileConfig);
|
|
1153
|
+
return {
|
|
1154
|
+
enabled: envBool("MASSA_AI_SCHEDULER_ENABLED", fileScheduler.enabled ?? false),
|
|
1155
|
+
tickMs: envNum("MASSA_AI_SCHEDULER_TICK_MS", fileScheduler.tickMs ?? 60000),
|
|
1156
|
+
maxConcurrent: envNum("MASSA_AI_SCHEDULER_MAX_CONCURRENT", fileScheduler.maxConcurrent ?? 2),
|
|
1157
|
+
jobs: Object.fromEntries(SCHEDULER_JOB_KINDS.map((kind) => [
|
|
1158
|
+
kind,
|
|
1159
|
+
resolveSchedulerJob(kind, fileScheduler.jobs)
|
|
1160
|
+
]))
|
|
1161
|
+
};
|
|
1162
|
+
})(),
|
|
1013
1163
|
synapse: {
|
|
1014
1164
|
enabled: process.env.SYNAPSE_ENABLED !== "false",
|
|
1015
1165
|
inhibition: {
|
|
@@ -1124,6 +1274,11 @@ class Config {
|
|
|
1124
1274
|
rateLimit: { ...defaults.rateLimit, ...overrides.rateLimit },
|
|
1125
1275
|
security: { ...defaults.security, ...overrides.security },
|
|
1126
1276
|
logging: { ...defaults.logging, ...overrides.logging },
|
|
1277
|
+
scheduler: {
|
|
1278
|
+
...defaults.scheduler,
|
|
1279
|
+
...overrides.scheduler,
|
|
1280
|
+
jobs: { ...defaults.scheduler.jobs, ...overrides.scheduler?.jobs }
|
|
1281
|
+
},
|
|
1127
1282
|
synapse: {
|
|
1128
1283
|
...defaults.synapse,
|
|
1129
1284
|
...overrides.synapse,
|
|
@@ -1288,8 +1443,165 @@ var TaskStatus;
|
|
|
1288
1443
|
TaskStatus2["FAILED"] = "failed";
|
|
1289
1444
|
TaskStatus2["PAUSED"] = "paused";
|
|
1290
1445
|
})(TaskStatus || (TaskStatus = {}));
|
|
1291
|
-
// ../../packages/shared/dist/utils/
|
|
1446
|
+
// ../../packages/shared/dist/utils/log-sink.js
|
|
1292
1447
|
import fs2 from "fs";
|
|
1448
|
+
import path5 from "path";
|
|
1449
|
+
var STAT_DELTA_THRESHOLD_BYTES = 1024 * 1024;
|
|
1450
|
+
var ensuredDirs = new Set;
|
|
1451
|
+
var pathState = new Map;
|
|
1452
|
+
var lastError;
|
|
1453
|
+
function getState(filePath) {
|
|
1454
|
+
let state = pathState.get(filePath);
|
|
1455
|
+
if (!state) {
|
|
1456
|
+
state = { trackedSize: 0, deltaSinceStat: 0, primed: false };
|
|
1457
|
+
pathState.set(filePath, state);
|
|
1458
|
+
}
|
|
1459
|
+
return state;
|
|
1460
|
+
}
|
|
1461
|
+
function ensureDir(filePath) {
|
|
1462
|
+
const dir = path5.dirname(filePath);
|
|
1463
|
+
if (ensuredDirs.has(dir))
|
|
1464
|
+
return;
|
|
1465
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
1466
|
+
ensuredDirs.add(dir);
|
|
1467
|
+
}
|
|
1468
|
+
function restat(filePath, state) {
|
|
1469
|
+
try {
|
|
1470
|
+
state.trackedSize = fs2.statSync(filePath).size;
|
|
1471
|
+
} catch {
|
|
1472
|
+
state.trackedSize = 0;
|
|
1473
|
+
}
|
|
1474
|
+
state.deltaSinceStat = 0;
|
|
1475
|
+
state.primed = true;
|
|
1476
|
+
}
|
|
1477
|
+
function rotate(filePath, maxFiles) {
|
|
1478
|
+
if (maxFiles <= 0) {
|
|
1479
|
+
if (fs2.existsSync(filePath))
|
|
1480
|
+
fs2.unlinkSync(filePath);
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
const oldest = `${filePath}.${maxFiles}`;
|
|
1484
|
+
if (fs2.existsSync(oldest))
|
|
1485
|
+
fs2.unlinkSync(oldest);
|
|
1486
|
+
for (let n = maxFiles - 1;n >= 1; n--) {
|
|
1487
|
+
const src = `${filePath}.${n}`;
|
|
1488
|
+
const dest = `${filePath}.${n + 1}`;
|
|
1489
|
+
if (fs2.existsSync(src))
|
|
1490
|
+
fs2.renameSync(src, dest);
|
|
1491
|
+
}
|
|
1492
|
+
if (fs2.existsSync(filePath))
|
|
1493
|
+
fs2.renameSync(filePath, `${filePath}.1`);
|
|
1494
|
+
}
|
|
1495
|
+
function appendLine(opts, line) {
|
|
1496
|
+
const { filePath, maxFileSizeBytes, maxFiles } = opts;
|
|
1497
|
+
try {
|
|
1498
|
+
ensureDir(filePath);
|
|
1499
|
+
const state = getState(filePath);
|
|
1500
|
+
const lineBytes = Buffer.byteLength(line + `
|
|
1501
|
+
`, "utf8");
|
|
1502
|
+
if (!state.primed || state.deltaSinceStat >= STAT_DELTA_THRESHOLD_BYTES || state.trackedSize + state.deltaSinceStat >= maxFileSizeBytes) {
|
|
1503
|
+
restat(filePath, state);
|
|
1504
|
+
}
|
|
1505
|
+
if (maxFileSizeBytes > 0 && state.trackedSize >= maxFileSizeBytes) {
|
|
1506
|
+
rotate(filePath, maxFiles);
|
|
1507
|
+
state.trackedSize = 0;
|
|
1508
|
+
state.deltaSinceStat = 0;
|
|
1509
|
+
state.primed = true;
|
|
1510
|
+
}
|
|
1511
|
+
fs2.appendFileSync(filePath, line + `
|
|
1512
|
+
`);
|
|
1513
|
+
state.trackedSize += lineBytes;
|
|
1514
|
+
state.deltaSinceStat += lineBytes;
|
|
1515
|
+
} catch (err) {
|
|
1516
|
+
lastError = err;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// ../../packages/shared/dist/utils/log-buffer.js
|
|
1521
|
+
var DEFAULT_CAPACITY = 2000;
|
|
1522
|
+
|
|
1523
|
+
class LogBufferImpl {
|
|
1524
|
+
capacity = DEFAULT_CAPACITY;
|
|
1525
|
+
entries = [];
|
|
1526
|
+
subscribers = new Set;
|
|
1527
|
+
nextSeq = 0;
|
|
1528
|
+
dispatching = false;
|
|
1529
|
+
pending = [];
|
|
1530
|
+
push(entry) {
|
|
1531
|
+
if (this.dispatching) {
|
|
1532
|
+
this.pending.push(entry);
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
this.dispatching = true;
|
|
1536
|
+
try {
|
|
1537
|
+
this.pushOne(entry);
|
|
1538
|
+
while (this.pending.length > 0) {
|
|
1539
|
+
const next = this.pending.shift();
|
|
1540
|
+
this.pushOne(next);
|
|
1541
|
+
}
|
|
1542
|
+
} finally {
|
|
1543
|
+
this.dispatching = false;
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
pushOne(entry) {
|
|
1547
|
+
const full = { ...entry, seq: this.nextSeq++ };
|
|
1548
|
+
this.entries.push(full);
|
|
1549
|
+
while (this.entries.length > this.capacity) {
|
|
1550
|
+
this.entries.shift();
|
|
1551
|
+
}
|
|
1552
|
+
for (const fn of this.subscribers) {
|
|
1553
|
+
try {
|
|
1554
|
+
fn(full);
|
|
1555
|
+
} catch {}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
snapshot(opts) {
|
|
1559
|
+
const from = opts?.from;
|
|
1560
|
+
const to = opts?.to;
|
|
1561
|
+
const level = opts?.level;
|
|
1562
|
+
const q = opts?.q?.toLowerCase();
|
|
1563
|
+
const result = [];
|
|
1564
|
+
for (let i = this.entries.length - 1;i >= 0; i--) {
|
|
1565
|
+
const e = this.entries[i];
|
|
1566
|
+
if (from !== undefined && e.seq < from)
|
|
1567
|
+
continue;
|
|
1568
|
+
if (to !== undefined && e.seq > to)
|
|
1569
|
+
continue;
|
|
1570
|
+
if (level !== undefined && e.level !== level)
|
|
1571
|
+
continue;
|
|
1572
|
+
if (q !== undefined && !e.message.toLowerCase().includes(q))
|
|
1573
|
+
continue;
|
|
1574
|
+
result.push(e);
|
|
1575
|
+
}
|
|
1576
|
+
return result;
|
|
1577
|
+
}
|
|
1578
|
+
subscribe(fn) {
|
|
1579
|
+
this.subscribers.add(fn);
|
|
1580
|
+
return () => {
|
|
1581
|
+
this.subscribers.delete(fn);
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
setCapacity(n) {
|
|
1585
|
+
this.capacity = n > 0 ? n : 0;
|
|
1586
|
+
while (this.entries.length > this.capacity) {
|
|
1587
|
+
this.entries.shift();
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
size() {
|
|
1591
|
+
return this.entries.length;
|
|
1592
|
+
}
|
|
1593
|
+
_resetForTesting() {
|
|
1594
|
+
this.entries = [];
|
|
1595
|
+
this.subscribers.clear();
|
|
1596
|
+
this.nextSeq = 0;
|
|
1597
|
+
this.dispatching = false;
|
|
1598
|
+
this.pending = [];
|
|
1599
|
+
this.capacity = DEFAULT_CAPACITY;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
var logBuffer = new LogBufferImpl;
|
|
1603
|
+
|
|
1604
|
+
// ../../packages/shared/dist/utils/logger.js
|
|
1293
1605
|
var LogLevel;
|
|
1294
1606
|
(function(LogLevel2) {
|
|
1295
1607
|
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
@@ -1297,11 +1609,26 @@ var LogLevel;
|
|
|
1297
1609
|
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
1298
1610
|
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
1299
1611
|
})(LogLevel || (LogLevel = {}));
|
|
1612
|
+
var LOG_LEVEL_LABELS = {
|
|
1613
|
+
[LogLevel.DEBUG]: "DEBUG",
|
|
1614
|
+
[LogLevel.INFO]: "INFO",
|
|
1615
|
+
[LogLevel.WARN]: "WARN",
|
|
1616
|
+
[LogLevel.ERROR]: "ERROR"
|
|
1617
|
+
};
|
|
1618
|
+
var LOG_LEVEL_BUFFER_TAGS = {
|
|
1619
|
+
[LogLevel.DEBUG]: "debug",
|
|
1620
|
+
[LogLevel.INFO]: "info",
|
|
1621
|
+
[LogLevel.WARN]: "warn",
|
|
1622
|
+
[LogLevel.ERROR]: "error"
|
|
1623
|
+
};
|
|
1300
1624
|
|
|
1301
1625
|
class Logger {
|
|
1302
1626
|
_level;
|
|
1303
1627
|
_enableMetrics;
|
|
1304
1628
|
_logFilePath;
|
|
1629
|
+
_enableFileSink;
|
|
1630
|
+
_maxFileSizeBytes;
|
|
1631
|
+
_maxFiles;
|
|
1305
1632
|
_initialized = false;
|
|
1306
1633
|
constructor() {}
|
|
1307
1634
|
ensureInitialized() {
|
|
@@ -1311,10 +1638,18 @@ class Logger {
|
|
|
1311
1638
|
this._level = this.parseLogLevel(loggingConfig.level);
|
|
1312
1639
|
this._enableMetrics = loggingConfig.enableMetrics;
|
|
1313
1640
|
this._logFilePath = loggingConfig.file;
|
|
1641
|
+
this._enableFileSink = loggingConfig.enableFileSink;
|
|
1642
|
+
this._maxFileSizeBytes = loggingConfig.maxFileSizeMb * 1024 * 1024;
|
|
1643
|
+
this._maxFiles = loggingConfig.maxFiles;
|
|
1644
|
+
logBuffer.setCapacity(loggingConfig.bufferSize);
|
|
1314
1645
|
} catch {
|
|
1315
1646
|
this._level = LogLevel.INFO;
|
|
1316
1647
|
this._enableMetrics = false;
|
|
1317
1648
|
this._logFilePath = undefined;
|
|
1649
|
+
this._enableFileSink = false;
|
|
1650
|
+
this._maxFileSizeBytes = 32 * 1024 * 1024;
|
|
1651
|
+
this._maxFiles = 5;
|
|
1652
|
+
logBuffer.setCapacity(2000);
|
|
1318
1653
|
}
|
|
1319
1654
|
this._initialized = true;
|
|
1320
1655
|
}
|
|
@@ -1331,6 +1666,18 @@ class Logger {
|
|
|
1331
1666
|
this.ensureInitialized();
|
|
1332
1667
|
return this._logFilePath;
|
|
1333
1668
|
}
|
|
1669
|
+
get enableFileSink() {
|
|
1670
|
+
this.ensureInitialized();
|
|
1671
|
+
return this._enableFileSink;
|
|
1672
|
+
}
|
|
1673
|
+
get maxFileSizeBytes() {
|
|
1674
|
+
this.ensureInitialized();
|
|
1675
|
+
return this._maxFileSizeBytes;
|
|
1676
|
+
}
|
|
1677
|
+
get maxFiles() {
|
|
1678
|
+
this.ensureInitialized();
|
|
1679
|
+
return this._maxFiles;
|
|
1680
|
+
}
|
|
1334
1681
|
parseLogLevel(level) {
|
|
1335
1682
|
const levels = {
|
|
1336
1683
|
debug: LogLevel.DEBUG,
|
|
@@ -1343,34 +1690,40 @@ class Logger {
|
|
|
1343
1690
|
shouldLog(level) {
|
|
1344
1691
|
return level >= this.level;
|
|
1345
1692
|
}
|
|
1346
|
-
formatMessage(level, message, meta) {
|
|
1347
|
-
const timestamp = new Date().toISOString();
|
|
1693
|
+
formatMessage(level, message, meta, timestamp = new Date().toISOString()) {
|
|
1348
1694
|
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
1349
1695
|
return `[${timestamp}] [${level}] ${message}${metaStr}`;
|
|
1350
1696
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
const
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1697
|
+
emit(level, message, meta) {
|
|
1698
|
+
const ts = new Date().toISOString();
|
|
1699
|
+
const line = this.formatMessage(LOG_LEVEL_LABELS[level], message, meta, ts);
|
|
1700
|
+
console.error(line);
|
|
1701
|
+
if (this.enableFileSink) {
|
|
1702
|
+
const filePath = this.logFilePath;
|
|
1703
|
+
if (filePath) {
|
|
1704
|
+
appendLine({ filePath, maxFileSizeBytes: this.maxFileSizeBytes, maxFiles: this.maxFiles }, line);
|
|
1705
|
+
}
|
|
1359
1706
|
}
|
|
1707
|
+
logBuffer.push({
|
|
1708
|
+
ts,
|
|
1709
|
+
level: LOG_LEVEL_BUFFER_TAGS[level],
|
|
1710
|
+
message,
|
|
1711
|
+
...meta ? { meta } : {}
|
|
1712
|
+
});
|
|
1360
1713
|
}
|
|
1361
1714
|
debug(message, meta) {
|
|
1362
1715
|
if (this.shouldLog(LogLevel.DEBUG)) {
|
|
1363
|
-
this.
|
|
1716
|
+
this.emit(LogLevel.DEBUG, message, meta);
|
|
1364
1717
|
}
|
|
1365
1718
|
}
|
|
1366
1719
|
info(message, meta) {
|
|
1367
1720
|
if (this.shouldLog(LogLevel.INFO)) {
|
|
1368
|
-
this.
|
|
1721
|
+
this.emit(LogLevel.INFO, message, meta);
|
|
1369
1722
|
}
|
|
1370
1723
|
}
|
|
1371
1724
|
warn(message, meta) {
|
|
1372
1725
|
if (this.shouldLog(LogLevel.WARN)) {
|
|
1373
|
-
this.
|
|
1726
|
+
this.emit(LogLevel.WARN, message, meta);
|
|
1374
1727
|
}
|
|
1375
1728
|
}
|
|
1376
1729
|
error(message, error, meta) {
|
|
@@ -1383,7 +1736,7 @@ class Logger {
|
|
|
1383
1736
|
stack: error.stack
|
|
1384
1737
|
}
|
|
1385
1738
|
} : meta;
|
|
1386
|
-
this.
|
|
1739
|
+
this.emit(LogLevel.ERROR, message, errorMeta);
|
|
1387
1740
|
}
|
|
1388
1741
|
}
|
|
1389
1742
|
metric(name, value, unit) {
|
|
@@ -1507,7 +1860,7 @@ class SmartRateLimiter {
|
|
|
1507
1860
|
var rateLimiter = new SmartRateLimiter;
|
|
1508
1861
|
// ../../packages/shared/dist/profile-switch/hosts.js
|
|
1509
1862
|
import os3 from "os";
|
|
1510
|
-
import
|
|
1863
|
+
import path6 from "path";
|
|
1511
1864
|
var HOSTS = ["claude", "codex", "cursor", "opencode"];
|
|
1512
1865
|
function isHost(v) {
|
|
1513
1866
|
return typeof v === "string" && HOSTS.includes(v);
|
|
@@ -1520,7 +1873,7 @@ function fileLayout(host, activeDir, activeGlob, variantsRoot) {
|
|
|
1520
1873
|
activeDir,
|
|
1521
1874
|
activeGlob,
|
|
1522
1875
|
variantsRoot,
|
|
1523
|
-
variantDir: (profile) =>
|
|
1876
|
+
variantDir: (profile) => path6.join(variantsRoot, profile)
|
|
1524
1877
|
};
|
|
1525
1878
|
}
|
|
1526
1879
|
function resolveHostLayout(host, opts = {}) {
|
|
@@ -1530,25 +1883,37 @@ function resolveHostLayout(host, opts = {}) {
|
|
|
1530
1883
|
case "cursor":
|
|
1531
1884
|
return { host, route: "skip", reason: CURSOR_SKIP_REASON };
|
|
1532
1885
|
case "claude": {
|
|
1533
|
-
const
|
|
1534
|
-
|
|
1886
|
+
const marketplaceRoot = opts.marketplaceRoot?.claude;
|
|
1887
|
+
if (override === undefined && marketplaceRoot !== undefined) {
|
|
1888
|
+
return fileLayout(host, path6.join(marketplaceRoot, "agents"), "massa-ai-*.md", path6.join(marketplaceRoot, "agent-profiles"));
|
|
1889
|
+
}
|
|
1890
|
+
const root = override ?? path6.join(targetHome, ".claude");
|
|
1891
|
+
return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.md", path6.join(root, "massa-ai", "agent-profiles"));
|
|
1535
1892
|
}
|
|
1536
1893
|
case "codex": {
|
|
1537
|
-
const root = override ??
|
|
1538
|
-
return fileLayout(host,
|
|
1894
|
+
const root = override ?? path6.join(targetHome, ".codex");
|
|
1895
|
+
return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.toml", path6.join(root, "massa-ai", "agent-profiles"));
|
|
1539
1896
|
}
|
|
1540
1897
|
case "opencode": {
|
|
1541
|
-
const root = override ??
|
|
1542
|
-
const pluginsDir =
|
|
1543
|
-
return fileLayout(host,
|
|
1898
|
+
const root = override ?? path6.join(targetHome, ".config", "opencode");
|
|
1899
|
+
const pluginsDir = path6.join(root, "plugins", "massa-ai");
|
|
1900
|
+
return fileLayout(host, path6.join(root, "agents"), "massa-ai-*.md", path6.join(pluginsDir, "agent-profiles"));
|
|
1544
1901
|
}
|
|
1545
1902
|
}
|
|
1546
1903
|
}
|
|
1547
|
-
function detectRoute(platform) {
|
|
1904
|
+
function detectRoute(platform, host) {
|
|
1548
1905
|
const route = platform?.installRoute;
|
|
1549
1906
|
if (route === "file")
|
|
1550
1907
|
return { kind: "proceed" };
|
|
1551
1908
|
if (route === "marketplace") {
|
|
1909
|
+
if (host === "claude")
|
|
1910
|
+
return { kind: "proceed" };
|
|
1911
|
+
if (host === "codex") {
|
|
1912
|
+
return {
|
|
1913
|
+
kind: "refuse",
|
|
1914
|
+
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."
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1552
1917
|
return {
|
|
1553
1918
|
kind: "refuse",
|
|
1554
1919
|
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 +1926,7 @@ function detectRoute(platform) {
|
|
|
1561
1926
|
}
|
|
1562
1927
|
// ../../packages/shared/dist/profile-switch/state.js
|
|
1563
1928
|
import fs3 from "fs";
|
|
1564
|
-
import
|
|
1929
|
+
import path7 from "path";
|
|
1565
1930
|
|
|
1566
1931
|
class InstallStateError extends Error {
|
|
1567
1932
|
constructor(message) {
|
|
@@ -1617,7 +1982,7 @@ function writeInstallState(filePath, state) {
|
|
|
1617
1982
|
const text = `${JSON.stringify(validated, null, 2)}
|
|
1618
1983
|
`;
|
|
1619
1984
|
try {
|
|
1620
|
-
fs3.mkdirSync(
|
|
1985
|
+
fs3.mkdirSync(path7.dirname(filePath), { recursive: true });
|
|
1621
1986
|
fs3.writeFileSync(filePath, text);
|
|
1622
1987
|
} catch (err) {
|
|
1623
1988
|
throw UnwritableInstallStateError(filePath, err.message);
|
|
@@ -1636,7 +2001,7 @@ function updatePlatform(filePath, host, patch) {
|
|
|
1636
2001
|
}
|
|
1637
2002
|
// ../../packages/shared/dist/profile-switch/lock.js
|
|
1638
2003
|
import fs4 from "fs";
|
|
1639
|
-
import
|
|
2004
|
+
import path8 from "path";
|
|
1640
2005
|
import os4 from "os";
|
|
1641
2006
|
import crypto2 from "crypto";
|
|
1642
2007
|
import { execFileSync } from "child_process";
|
|
@@ -1693,7 +2058,7 @@ function releaseIfOwned(lockDir, ownerPath, token) {
|
|
|
1693
2058
|
}
|
|
1694
2059
|
function acquireLock(stateFilePath, options = {}) {
|
|
1695
2060
|
const lockDir = `${stateFilePath}.switch.lock`;
|
|
1696
|
-
const ownerPath =
|
|
2061
|
+
const ownerPath = path8.join(lockDir, "owner.json");
|
|
1697
2062
|
const clock = options.clock ?? DEFAULT_CLOCK;
|
|
1698
2063
|
const identity = options.identity ?? DEFAULT_IDENTITY;
|
|
1699
2064
|
const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
|
|
@@ -1713,7 +2078,7 @@ function acquireLock(stateFilePath, options = {}) {
|
|
|
1713
2078
|
token,
|
|
1714
2079
|
timestamp: clock.now()
|
|
1715
2080
|
};
|
|
1716
|
-
fs4.mkdirSync(
|
|
2081
|
+
fs4.mkdirSync(path8.dirname(ownerPath), { recursive: true });
|
|
1717
2082
|
fs4.writeFileSync(ownerPath, JSON.stringify(record));
|
|
1718
2083
|
return { lockDir, release: () => releaseIfOwned(lockDir, ownerPath, token) };
|
|
1719
2084
|
};
|
|
@@ -1741,10 +2106,60 @@ function acquireLock(stateFilePath, options = {}) {
|
|
|
1741
2106
|
}
|
|
1742
2107
|
}
|
|
1743
2108
|
// ../../packages/shared/dist/profile-switch/engine.js
|
|
2109
|
+
import fs6 from "fs";
|
|
2110
|
+
import path10 from "path";
|
|
2111
|
+
import os6 from "os";
|
|
2112
|
+
import crypto3 from "crypto";
|
|
2113
|
+
|
|
2114
|
+
// ../../packages/shared/dist/profile-switch/claude-marketplace.js
|
|
1744
2115
|
import fs5 from "fs";
|
|
1745
|
-
import path8 from "path";
|
|
1746
2116
|
import os5 from "os";
|
|
1747
|
-
import
|
|
2117
|
+
import path9 from "path";
|
|
2118
|
+
var DEFAULT_PLUGIN_KEY = "massa-ai@massa-ai";
|
|
2119
|
+
function selectRecord(records) {
|
|
2120
|
+
if (records.length === 0)
|
|
2121
|
+
return;
|
|
2122
|
+
const userScoped = records.filter((r) => r.scope === "user");
|
|
2123
|
+
const pool = userScoped.length > 0 ? userScoped : records;
|
|
2124
|
+
let best;
|
|
2125
|
+
let bestTime = -Infinity;
|
|
2126
|
+
for (const record of pool) {
|
|
2127
|
+
const parsed = record.lastUpdated ? Date.parse(record.lastUpdated) : NaN;
|
|
2128
|
+
if (Number.isFinite(parsed) && parsed >= bestTime) {
|
|
2129
|
+
best = record;
|
|
2130
|
+
bestTime = parsed;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return best ?? pool[pool.length - 1];
|
|
2134
|
+
}
|
|
2135
|
+
function resolveClaudeMarketplaceRoot(opts = {}) {
|
|
2136
|
+
const targetHome = opts.targetHome ?? os5.homedir();
|
|
2137
|
+
const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
|
|
2138
|
+
const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2139
|
+
let records;
|
|
2140
|
+
try {
|
|
2141
|
+
const raw = fs5.readFileSync(registryPath, "utf8");
|
|
2142
|
+
const parsed = JSON.parse(raw);
|
|
2143
|
+
records = parsed?.plugins?.[pluginKey];
|
|
2144
|
+
} catch {
|
|
2145
|
+
return null;
|
|
2146
|
+
}
|
|
2147
|
+
if (!Array.isArray(records) || records.length === 0)
|
|
2148
|
+
return null;
|
|
2149
|
+
const selected = selectRecord(records);
|
|
2150
|
+
const installPath = selected?.installPath;
|
|
2151
|
+
if (!installPath)
|
|
2152
|
+
return null;
|
|
2153
|
+
try {
|
|
2154
|
+
if (!fs5.existsSync(installPath))
|
|
2155
|
+
return null;
|
|
2156
|
+
} catch {
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
return installPath;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// ../../packages/shared/dist/profile-switch/engine.js
|
|
1748
2163
|
class SwitchEngineError extends Error {
|
|
1749
2164
|
constructor(message) {
|
|
1750
2165
|
super(message);
|
|
@@ -1759,19 +2174,39 @@ function namedError3(name, message) {
|
|
|
1759
2174
|
var UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`);
|
|
1760
2175
|
var NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found");
|
|
1761
2176
|
function defaultStatePath(targetHome) {
|
|
1762
|
-
return
|
|
2177
|
+
return path10.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
1763
2178
|
}
|
|
1764
2179
|
function resolveCommon(opts) {
|
|
1765
|
-
const targetHome = opts.targetHome ??
|
|
2180
|
+
const targetHome = opts.targetHome ?? os6.homedir();
|
|
1766
2181
|
const stateFilePath = opts.stateFilePath ?? defaultStatePath(targetHome);
|
|
1767
2182
|
return { targetHome, stateFilePath };
|
|
1768
2183
|
}
|
|
2184
|
+
function marketplaceRoots(targetHome, state) {
|
|
2185
|
+
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
2186
|
+
}
|
|
2187
|
+
function claudeMarketplaceUnresolvedReason(targetHome) {
|
|
2188
|
+
const registryPath = path10.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2189
|
+
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";
|
|
2190
|
+
}
|
|
1769
2191
|
function listProfiles(opts = {}) {
|
|
1770
2192
|
const { targetHome, stateFilePath } = resolveCommon(opts);
|
|
1771
2193
|
const state = readInstallState(stateFilePath);
|
|
2194
|
+
const roots = marketplaceRoots(targetHome, state);
|
|
1772
2195
|
const universe = opts.hosts ?? HOSTS;
|
|
1773
2196
|
const hosts = universe.map((host) => {
|
|
1774
|
-
|
|
2197
|
+
if (host === "claude" && state.platforms.claude?.installRoute === "marketplace" && roots.claude === undefined) {
|
|
2198
|
+
const platform2 = state.platforms.claude;
|
|
2199
|
+
return {
|
|
2200
|
+
host,
|
|
2201
|
+
installed: false,
|
|
2202
|
+
skipped: false,
|
|
2203
|
+
skipReason: null,
|
|
2204
|
+
activeProfile: platform2.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
|
|
2205
|
+
bundleVersion: platform2.plugin?.version ?? null,
|
|
2206
|
+
availableProfiles: []
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
const layout = resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots });
|
|
1775
2210
|
if (layout.route === "skip") {
|
|
1776
2211
|
return {
|
|
1777
2212
|
host,
|
|
@@ -1783,7 +2218,7 @@ function listProfiles(opts = {}) {
|
|
|
1783
2218
|
availableProfiles: []
|
|
1784
2219
|
};
|
|
1785
2220
|
}
|
|
1786
|
-
const installed =
|
|
2221
|
+
const installed = fs6.existsSync(layout.activeDir);
|
|
1787
2222
|
const availableProfiles = listVariantProfiles(layout);
|
|
1788
2223
|
const platform = state.platforms[host];
|
|
1789
2224
|
return {
|
|
@@ -1799,9 +2234,9 @@ function listProfiles(opts = {}) {
|
|
|
1799
2234
|
return { hosts };
|
|
1800
2235
|
}
|
|
1801
2236
|
function listVariantProfiles(layout) {
|
|
1802
|
-
if (!
|
|
2237
|
+
if (!fs6.existsSync(layout.variantsRoot))
|
|
1803
2238
|
return [];
|
|
1804
|
-
return
|
|
2239
|
+
return fs6.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
1805
2240
|
}
|
|
1806
2241
|
function matchesGlob(filename, glob) {
|
|
1807
2242
|
const starIdx = glob.indexOf("*");
|
|
@@ -1812,50 +2247,50 @@ function matchesGlob(filename, glob) {
|
|
|
1812
2247
|
return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
|
|
1813
2248
|
}
|
|
1814
2249
|
function assertStateWritable(stateFilePath) {
|
|
1815
|
-
const dir =
|
|
2250
|
+
const dir = path10.dirname(stateFilePath);
|
|
1816
2251
|
try {
|
|
1817
|
-
|
|
2252
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1818
2253
|
} catch (err) {
|
|
1819
2254
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
1820
2255
|
}
|
|
1821
|
-
const checkPath =
|
|
2256
|
+
const checkPath = fs6.existsSync(stateFilePath) ? stateFilePath : dir;
|
|
1822
2257
|
try {
|
|
1823
|
-
|
|
2258
|
+
fs6.accessSync(checkPath, fs6.constants.W_OK);
|
|
1824
2259
|
} catch (err) {
|
|
1825
2260
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
1826
2261
|
}
|
|
1827
2262
|
}
|
|
1828
2263
|
function copyFileRouteVariant(layout, variantDir) {
|
|
1829
|
-
|
|
2264
|
+
fs6.mkdirSync(layout.activeDir, { recursive: true });
|
|
1830
2265
|
let changed = 0;
|
|
1831
|
-
for (const entry of
|
|
2266
|
+
for (const entry of fs6.readdirSync(variantDir, { withFileTypes: true })) {
|
|
1832
2267
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
1833
2268
|
continue;
|
|
1834
|
-
|
|
2269
|
+
fs6.copyFileSync(path10.join(variantDir, entry.name), path10.join(layout.activeDir, entry.name));
|
|
1835
2270
|
changed++;
|
|
1836
2271
|
}
|
|
1837
2272
|
return changed;
|
|
1838
2273
|
}
|
|
1839
2274
|
function repointOpencodeVariant(layout, variantDir) {
|
|
1840
|
-
|
|
2275
|
+
fs6.mkdirSync(layout.activeDir, { recursive: true });
|
|
1841
2276
|
let changed = 0;
|
|
1842
|
-
for (const entry of
|
|
2277
|
+
for (const entry of fs6.readdirSync(variantDir, { withFileTypes: true })) {
|
|
1843
2278
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
1844
2279
|
continue;
|
|
1845
|
-
const dest =
|
|
1846
|
-
const target =
|
|
2280
|
+
const dest = path10.join(layout.activeDir, entry.name);
|
|
2281
|
+
const target = path10.resolve(path10.join(variantDir, entry.name));
|
|
1847
2282
|
let destExists = true;
|
|
1848
2283
|
let destIsSymlink = false;
|
|
1849
2284
|
try {
|
|
1850
|
-
destIsSymlink =
|
|
2285
|
+
destIsSymlink = fs6.lstatSync(dest).isSymbolicLink();
|
|
1851
2286
|
} catch {
|
|
1852
2287
|
destExists = false;
|
|
1853
2288
|
}
|
|
1854
2289
|
if (destExists && !destIsSymlink)
|
|
1855
2290
|
continue;
|
|
1856
2291
|
const tmp = `${dest}.massa-ai-switch.${crypto3.randomUUID()}`;
|
|
1857
|
-
|
|
1858
|
-
|
|
2292
|
+
fs6.symlinkSync(target, tmp);
|
|
2293
|
+
fs6.renameSync(tmp, dest);
|
|
1859
2294
|
changed++;
|
|
1860
2295
|
}
|
|
1861
2296
|
return changed;
|
|
@@ -1871,19 +2306,37 @@ function switchProfile(opts) {
|
|
|
1871
2306
|
const state = readInstallState(stateFilePath);
|
|
1872
2307
|
if (!dryRun)
|
|
1873
2308
|
assertStateWritable(stateFilePath);
|
|
1874
|
-
const
|
|
1875
|
-
const
|
|
2309
|
+
const roots = marketplaceRoots(targetHome, state);
|
|
2310
|
+
const unresolvedRows = [];
|
|
2311
|
+
const resolvableUniverse = universe.filter((host) => {
|
|
2312
|
+
if (host !== "claude")
|
|
2313
|
+
return true;
|
|
2314
|
+
if (state.platforms.claude?.installRoute !== "marketplace")
|
|
2315
|
+
return true;
|
|
2316
|
+
if (roots.claude !== undefined)
|
|
2317
|
+
return true;
|
|
2318
|
+
unresolvedRows.push({ host, status: "failed", reason: claudeMarketplaceUnresolvedReason(targetHome) });
|
|
2319
|
+
return false;
|
|
2320
|
+
});
|
|
2321
|
+
const layouts = resolvableUniverse.map((host) => ({
|
|
2322
|
+
host,
|
|
2323
|
+
layout: resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots })
|
|
2324
|
+
}));
|
|
2325
|
+
const skipRows = [
|
|
2326
|
+
...unresolvedRows,
|
|
2327
|
+
...layouts.filter((l) => l.layout.route === "skip").map((l) => ({ host: l.host, status: "skipped", reason: l.layout.reason }))
|
|
2328
|
+
];
|
|
1876
2329
|
const fileHosts = layouts.filter((l) => l.layout.route === "files");
|
|
1877
2330
|
if (fileHosts.length === 0) {
|
|
1878
2331
|
return { profile: opts.profile, dryRun, hosts: orderRows(universe, skipRows), restartRequired: false };
|
|
1879
2332
|
}
|
|
1880
|
-
const installedFileHosts = fileHosts.filter((h) =>
|
|
2333
|
+
const installedFileHosts = fileHosts.filter((h) => fs6.existsSync(h.layout.activeDir));
|
|
1881
2334
|
if (installedFileHosts.length === 0)
|
|
1882
2335
|
throw NoHostsDetectedError();
|
|
1883
2336
|
const withAvailability = fileHosts.map((h) => {
|
|
1884
|
-
const variantsRootExists =
|
|
2337
|
+
const variantsRootExists = fs6.existsSync(h.layout.variantsRoot);
|
|
1885
2338
|
const variantDir = h.layout.variantDir(opts.profile);
|
|
1886
|
-
const available = variantsRootExists &&
|
|
2339
|
+
const available = variantsRootExists && fs6.existsSync(variantDir) && fs6.statSync(variantDir).isDirectory();
|
|
1887
2340
|
return { ...h, variantsRootExists, variantDir, available };
|
|
1888
2341
|
});
|
|
1889
2342
|
if (!withAvailability.some((h) => h.available)) {
|
|
@@ -1913,7 +2366,7 @@ function switchProfile(opts) {
|
|
|
1913
2366
|
});
|
|
1914
2367
|
continue;
|
|
1915
2368
|
}
|
|
1916
|
-
const route = detectRoute(state.platforms[h.host]);
|
|
2369
|
+
const route = detectRoute(state.platforms[h.host], h.host);
|
|
1917
2370
|
if (route.kind === "refuse") {
|
|
1918
2371
|
rows.push({ host: h.host, status: "failed", reason: route.reason });
|
|
1919
2372
|
continue;
|
|
@@ -1948,19 +2401,26 @@ function reportSucceeded(report) {
|
|
|
1948
2401
|
return report.hosts.every((h) => h.status === "switched" || h.status === "skipped");
|
|
1949
2402
|
}
|
|
1950
2403
|
// ../../packages/shared/dist/profile-switch/variant-sync.js
|
|
1951
|
-
import
|
|
1952
|
-
import
|
|
2404
|
+
import fs7 from "fs";
|
|
2405
|
+
import path11 from "path";
|
|
2406
|
+
import os7 from "os";
|
|
1953
2407
|
import crypto4 from "crypto";
|
|
2408
|
+
function defaultStatePath2(targetHome) {
|
|
2409
|
+
return path11.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2410
|
+
}
|
|
2411
|
+
function marketplaceRoots2(targetHome, state) {
|
|
2412
|
+
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
2413
|
+
}
|
|
1954
2414
|
var tempFileCounter2 = 0;
|
|
1955
2415
|
function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
1956
2416
|
const unique = `${process.pid}.${++tempFileCounter2}.${crypto4.randomBytes(6).toString("hex")}`;
|
|
1957
|
-
const tempFile =
|
|
2417
|
+
const tempFile = path11.join(destDir, `.${destName}.${unique}.tmp`);
|
|
1958
2418
|
try {
|
|
1959
|
-
|
|
1960
|
-
|
|
2419
|
+
fs7.writeFileSync(tempFile, content);
|
|
2420
|
+
fs7.renameSync(tempFile, path11.join(destDir, destName));
|
|
1961
2421
|
} catch (error) {
|
|
1962
2422
|
try {
|
|
1963
|
-
|
|
2423
|
+
fs7.unlinkSync(tempFile);
|
|
1964
2424
|
} catch {}
|
|
1965
2425
|
throw error;
|
|
1966
2426
|
}
|
|
@@ -1968,20 +2428,20 @@ function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
|
1968
2428
|
function isSafeDirName(name) {
|
|
1969
2429
|
if (name === "." || name === "..")
|
|
1970
2430
|
return false;
|
|
1971
|
-
if (name.includes("/") || name.includes("\\") || name.includes(
|
|
2431
|
+
if (name.includes("/") || name.includes("\\") || name.includes(path11.sep))
|
|
1972
2432
|
return false;
|
|
1973
|
-
return
|
|
2433
|
+
return path11.basename(name) === name;
|
|
1974
2434
|
}
|
|
1975
|
-
function syncHost(host, sourceRoot, targetHome) {
|
|
1976
|
-
const layout = resolveHostLayout(host, { targetHome });
|
|
2435
|
+
function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
|
|
2436
|
+
const layout = resolveHostLayout(host, { targetHome, marketplaceRoot });
|
|
1977
2437
|
if (layout.route === "skip") {
|
|
1978
2438
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: layout.reason };
|
|
1979
2439
|
}
|
|
1980
|
-
const srcDir =
|
|
1981
|
-
if (!
|
|
2440
|
+
const srcDir = path11.join(sourceRoot, "apps", `${host}-plugin`, "agent-profiles");
|
|
2441
|
+
if (!fs7.existsSync(srcDir) || !fs7.statSync(srcDir).isDirectory()) {
|
|
1982
2442
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: "no generated variants for this host" };
|
|
1983
2443
|
}
|
|
1984
|
-
if (!
|
|
2444
|
+
if (!fs7.existsSync(layout.variantsRoot)) {
|
|
1985
2445
|
return {
|
|
1986
2446
|
host,
|
|
1987
2447
|
status: "skipped",
|
|
@@ -1993,24 +2453,24 @@ function syncHost(host, sourceRoot, targetHome) {
|
|
|
1993
2453
|
}
|
|
1994
2454
|
const profiles = [];
|
|
1995
2455
|
let files = 0;
|
|
1996
|
-
for (const entry of
|
|
2456
|
+
for (const entry of fs7.readdirSync(srcDir, { withFileTypes: true })) {
|
|
1997
2457
|
if (!entry.isDirectory())
|
|
1998
2458
|
continue;
|
|
1999
2459
|
if (!isSafeDirName(entry.name))
|
|
2000
2460
|
continue;
|
|
2001
|
-
const srcProfileDir =
|
|
2002
|
-
const destProfileDir =
|
|
2003
|
-
|
|
2004
|
-
for (const fileEntry of
|
|
2461
|
+
const srcProfileDir = path11.join(srcDir, entry.name);
|
|
2462
|
+
const destProfileDir = path11.join(layout.variantsRoot, entry.name);
|
|
2463
|
+
fs7.mkdirSync(destProfileDir, { recursive: true });
|
|
2464
|
+
for (const fileEntry of fs7.readdirSync(srcProfileDir, { withFileTypes: true })) {
|
|
2005
2465
|
if (!fileEntry.isFile())
|
|
2006
2466
|
continue;
|
|
2007
|
-
const content =
|
|
2467
|
+
const content = fs7.readFileSync(path11.join(srcProfileDir, fileEntry.name));
|
|
2008
2468
|
writeFileIntoDirAtomically(destProfileDir, fileEntry.name, content);
|
|
2009
2469
|
files++;
|
|
2010
2470
|
}
|
|
2011
2471
|
profiles.push(entry.name);
|
|
2012
2472
|
}
|
|
2013
|
-
const retained =
|
|
2473
|
+
const retained = fs7.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && !profiles.includes(e.name)).map((e) => e.name).sort();
|
|
2014
2474
|
return { host, status: "synced", profiles: profiles.sort(), retained, files };
|
|
2015
2475
|
}
|
|
2016
2476
|
function syncGeneratedVariants(opts) {
|
|
@@ -2026,23 +2486,26 @@ function syncGeneratedVariants(opts) {
|
|
|
2026
2486
|
}));
|
|
2027
2487
|
}
|
|
2028
2488
|
const sourceRoot = opts.sourceRoot;
|
|
2489
|
+
const targetHome = opts.targetHome ?? os7.homedir();
|
|
2490
|
+
const state = readInstallState(defaultStatePath2(targetHome));
|
|
2491
|
+
const roots = marketplaceRoots2(targetHome, state);
|
|
2029
2492
|
return hosts.map((host) => {
|
|
2030
2493
|
try {
|
|
2031
|
-
return syncHost(host, sourceRoot, opts.targetHome);
|
|
2494
|
+
return syncHost(host, sourceRoot, opts.targetHome, roots);
|
|
2032
2495
|
} catch (err) {
|
|
2033
2496
|
return { host, status: "failed", profiles: [], retained: [], files: 0, error: err.message };
|
|
2034
2497
|
}
|
|
2035
2498
|
});
|
|
2036
2499
|
}
|
|
2037
2500
|
// ../../packages/shared/dist/profile-switch/repo-root.js
|
|
2038
|
-
import
|
|
2039
|
-
import
|
|
2501
|
+
import fs8 from "fs";
|
|
2502
|
+
import path12 from "path";
|
|
2040
2503
|
function findRepoRootWithMarker(startDir, marker, maxLevels) {
|
|
2041
2504
|
let dir = startDir;
|
|
2042
2505
|
for (let i = 0;i <= maxLevels; i++) {
|
|
2043
|
-
if (
|
|
2506
|
+
if (fs8.existsSync(path12.join(dir, marker)))
|
|
2044
2507
|
return dir;
|
|
2045
|
-
const parent =
|
|
2508
|
+
const parent = path12.dirname(dir);
|
|
2046
2509
|
if (parent === dir)
|
|
2047
2510
|
break;
|
|
2048
2511
|
dir = parent;
|
|
@@ -2050,11 +2513,11 @@ function findRepoRootWithMarker(startDir, marker, maxLevels) {
|
|
|
2050
2513
|
return null;
|
|
2051
2514
|
}
|
|
2052
2515
|
// src/config-cli.ts
|
|
2053
|
-
import { promises as
|
|
2054
|
-
import
|
|
2055
|
-
import
|
|
2516
|
+
import { promises as fs9 } from "fs";
|
|
2517
|
+
import path13 from "path";
|
|
2518
|
+
import os8 from "os";
|
|
2056
2519
|
import { fileURLToPath } from "url";
|
|
2057
|
-
var __dirname2 =
|
|
2520
|
+
var __dirname2 = path13.dirname(fileURLToPath(import.meta.url));
|
|
2058
2521
|
var GENERATOR_MARKER = "scripts/generate-subagent-artifacts.ts";
|
|
2059
2522
|
var GENERATOR_MARKER_MAX_LEVELS = 6;
|
|
2060
2523
|
function formatVariantSync(results) {
|
|
@@ -2097,7 +2560,7 @@ Commands:
|
|
|
2097
2560
|
Examples:
|
|
2098
2561
|
massa-ai-config init
|
|
2099
2562
|
massa-ai-config init --mistral your-api-key
|
|
2100
|
-
massa-ai-config use ollama --model
|
|
2563
|
+
massa-ai-config use ollama --model qwen3-embedding:4b
|
|
2101
2564
|
massa-ai-config use mistral --api-key your-key
|
|
2102
2565
|
massa-ai-config set embedding.dimensions 1024
|
|
2103
2566
|
massa-ai-config agents install --user
|
|
@@ -2226,9 +2689,9 @@ Using defaults:`);
|
|
|
2226
2689
|
if (provider === "ollama") {
|
|
2227
2690
|
config2.embedding = {
|
|
2228
2691
|
provider: "ollama",
|
|
2229
|
-
model: options.model || "
|
|
2692
|
+
model: options.model || "qwen3-embedding:4b",
|
|
2230
2693
|
baseURL: options["base-url"] || "http://localhost:11434",
|
|
2231
|
-
dimensions:
|
|
2694
|
+
dimensions: 2560
|
|
2232
2695
|
};
|
|
2233
2696
|
} else if (provider === "mistral") {
|
|
2234
2697
|
if (!options["api-key"]) {
|
|
@@ -2265,18 +2728,18 @@ Using defaults:`);
|
|
|
2265
2728
|
return 1;
|
|
2266
2729
|
}
|
|
2267
2730
|
const scope = typeof options.project === "boolean" ? "project" : "user";
|
|
2268
|
-
const agentsDir = scope === "project" ?
|
|
2269
|
-
const sourceAgentsDir =
|
|
2731
|
+
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");
|
|
2732
|
+
const sourceAgentsDir = path13.resolve(__dirname2, "..", "agents");
|
|
2270
2733
|
if (subcommand === "install") {
|
|
2271
|
-
await
|
|
2734
|
+
await fs9.mkdir(agentsDir, { recursive: true });
|
|
2272
2735
|
let count = 0;
|
|
2273
|
-
const entries = await
|
|
2736
|
+
const entries = await fs9.readdir(sourceAgentsDir);
|
|
2274
2737
|
for (const entry of entries) {
|
|
2275
2738
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
2276
2739
|
continue;
|
|
2277
|
-
const src =
|
|
2278
|
-
const dest =
|
|
2279
|
-
await
|
|
2740
|
+
const src = path13.join(sourceAgentsDir, entry);
|
|
2741
|
+
const dest = path13.join(agentsDir, entry);
|
|
2742
|
+
await fs9.copyFile(src, dest);
|
|
2280
2743
|
count++;
|
|
2281
2744
|
}
|
|
2282
2745
|
console.log(`+ ${count} subagent specialists (generated from skills/agents/*/SKILL.md)`);
|
|
@@ -2284,14 +2747,14 @@ Using defaults:`);
|
|
|
2284
2747
|
} else {
|
|
2285
2748
|
let removed = 0;
|
|
2286
2749
|
try {
|
|
2287
|
-
const entries = await
|
|
2750
|
+
const entries = await fs9.readdir(agentsDir);
|
|
2288
2751
|
for (const entry of entries) {
|
|
2289
2752
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
2290
2753
|
continue;
|
|
2291
|
-
const filePath =
|
|
2292
|
-
const content = await
|
|
2754
|
+
const filePath = path13.join(agentsDir, entry);
|
|
2755
|
+
const content = await fs9.readFile(filePath, "utf8");
|
|
2293
2756
|
if (content.includes("massa-ai-owned: true")) {
|
|
2294
|
-
await
|
|
2757
|
+
await fs9.unlink(filePath);
|
|
2295
2758
|
removed++;
|
|
2296
2759
|
}
|
|
2297
2760
|
}
|