@massa-ai/opencode-plugin 1.60.1 → 1.61.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.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -80,9 +80,9 @@ next_step: <string>
80
80
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
81
81
 
82
82
  ## Model Hint
83
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
84
- (`deep`) is the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot
85
- assignments.
83
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `judge` carries no
84
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
85
+ the per-slot fallback; `workflows/judge-with-debate.md` owns the live slot assignments.
86
86
 
87
87
  ## Validation Sensors
88
88
  - Every criterion score carries an exact quotation from the artifact.
@@ -71,8 +71,9 @@ overall: weighted-mean
71
71
  - References (paths relative to the `massa-ai` skill directory): `references/agent-orchestration.md`, `references/audit-report-io.md` (Judge With Debate Report Contracts).
72
72
 
73
73
  ## Model Hint
74
- See `references/agent-orchestration.md` (Model Diversity Fallback): `metadata.model_tier`
75
- (`deep`) is the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
74
+ See `references/agent-orchestration.md` (Model Diversity Fallback): `meta-judge` carries no
75
+ per-agent override in `skills/model-profiles.json`, so the active profile's host default is
76
+ the fallback; `workflows/judge-with-debate.md` owns the live model assignment.
76
77
 
77
78
  ## Validation Sensors
78
79
  - Output parses as YAML; weights sum to 1.0 (±0.001); every criterion carries id, name, weight, scale (min 1, max 5), rubric anchors for 1/3/5, and a non-empty checklist.
@@ -985,7 +985,7 @@ function getConfigForEnv() {
985
985
  } else {
986
986
  console.error(`[getConfigForEnv] embedding.provider "${provider}" has no env-projection branch \u2014 no embedding env vars were set`);
987
987
  }
988
- env.LOG_LEVEL = config.logging.level;
988
+ env.MASSA_AI_LOG_LEVEL = config.logging.level;
989
989
  env.ENABLE_METRICS = String(config.logging.enableMetrics);
990
990
  return env;
991
991
  }
@@ -1383,7 +1383,7 @@ var defaultConfig = {
1383
1383
  corsOrigins: envList("MASSA_AI_API_CORS_ORIGINS", fileConfig.security?.corsOrigins ?? [])
1384
1384
  },
1385
1385
  logging: {
1386
- level: process.env.LOG_LEVEL || fileConfig.logging?.level || "info",
1386
+ level: process.env.MASSA_AI_LOG_LEVEL || fileConfig.logging?.level || "info",
1387
1387
  enableMetrics: process.env.ENABLE_METRICS === "true" || process.env.ENABLE_METRICS === undefined && !!fileConfig.logging?.enableMetrics,
1388
1388
  file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || path4.join(resolvedDataDir, "logs", "massa-ai.log"),
1389
1389
  enableFileSink: envBool("MASSA_AI_LOG_ENABLE_FILE_SINK", fileConfig.logging?.enableFileSink ?? true),
@@ -1864,6 +1864,34 @@ var LOG_LEVEL_BUFFER_TAGS = {
1864
1864
  [LogLevel.WARN]: "warn",
1865
1865
  [LogLevel.ERROR]: "error"
1866
1866
  };
1867
+ var REPEAT_WINDOW_MS = 15 * 60 * 1000;
1868
+ var MAX_REPEAT_KEYS = 500;
1869
+ function formatAgo(ms) {
1870
+ const totalSeconds = Math.floor(ms / 1000);
1871
+ if (totalSeconds < 60)
1872
+ return `${totalSeconds}s`;
1873
+ return `${Math.floor(totalSeconds / 60)}m`;
1874
+ }
1875
+ var MAX_ERROR_TEXT_CHARS = 300;
1876
+ function capErrorText(value) {
1877
+ if (value.length <= MAX_ERROR_TEXT_CHARS)
1878
+ return value;
1879
+ const truncatedChars = value.length - MAX_ERROR_TEXT_CHARS;
1880
+ return `${value.slice(0, MAX_ERROR_TEXT_CHARS)}\u2026(truncated ${truncatedChars} chars)`;
1881
+ }
1882
+ function pickErrorFields(err, includeStack) {
1883
+ const out = { name: err.name, message: capErrorText(err.message) };
1884
+ if (includeStack)
1885
+ out.stack = err.stack;
1886
+ const code = err.code;
1887
+ if (code !== undefined)
1888
+ out.code = code;
1889
+ const cause = err.cause;
1890
+ if (cause !== undefined) {
1891
+ out.cause = capErrorText(cause instanceof Error ? cause.message : String(cause));
1892
+ }
1893
+ return out;
1894
+ }
1867
1895
 
1868
1896
  class Logger {
1869
1897
  _level;
@@ -1873,6 +1901,7 @@ class Logger {
1873
1901
  _maxFileSizeBytes;
1874
1902
  _maxFiles;
1875
1903
  _initialized = false;
1904
+ repeats = new Map;
1876
1905
  constructor() {}
1877
1906
  ensureInitialized() {
1878
1907
  if (!this._initialized) {
@@ -1933,13 +1962,70 @@ class Logger {
1933
1962
  shouldLog(level) {
1934
1963
  return level >= this.level;
1935
1964
  }
1965
+ serializeMetaErrors(meta) {
1966
+ if (!meta)
1967
+ return meta;
1968
+ let out;
1969
+ for (const [key, value] of Object.entries(meta)) {
1970
+ if (value instanceof Error) {
1971
+ if (!out)
1972
+ out = { ...meta };
1973
+ out[key] = pickErrorFields(value, false);
1974
+ }
1975
+ }
1976
+ return out ?? meta;
1977
+ }
1978
+ applyRepeatAccounting(level, message, meta) {
1979
+ if (level !== LogLevel.WARN && level !== LogLevel.ERROR)
1980
+ return meta;
1981
+ const label = typeof meta?.label === "string" ? meta.label : "";
1982
+ const key = `${level}|${message}|${label}`;
1983
+ const now = Date.now();
1984
+ const existing = this.repeats.get(key);
1985
+ if (!existing || now - existing.firstSeenAt > REPEAT_WINDOW_MS) {
1986
+ if (this.repeats.size >= MAX_REPEAT_KEYS)
1987
+ this.repeats.clear();
1988
+ this.repeats.set(key, { firstSeenAt: now, count: 1 });
1989
+ return meta;
1990
+ }
1991
+ existing.count += 1;
1992
+ return {
1993
+ ...meta,
1994
+ occurrences: existing.count,
1995
+ firstSeenAgo: formatAgo(now - existing.firstSeenAt)
1996
+ };
1997
+ }
1998
+ _resetRepeatsForTesting() {
1999
+ this.repeats.clear();
2000
+ }
2001
+ safeStringifyMeta(meta) {
2002
+ const seen = new WeakSet;
2003
+ try {
2004
+ return JSON.stringify(meta, (_key, value) => {
2005
+ if (typeof value === "bigint")
2006
+ return value.toString();
2007
+ if (typeof value === "object" && value !== null) {
2008
+ if (seen.has(value))
2009
+ return "[Circular]";
2010
+ seen.add(value);
2011
+ }
2012
+ return value;
2013
+ });
2014
+ } catch (err) {
2015
+ return JSON.stringify({
2016
+ metaUnserializable: err instanceof Error ? err.message : String(err)
2017
+ });
2018
+ }
2019
+ }
1936
2020
  formatMessage(level, message, meta, timestamp = new Date().toISOString()) {
1937
- const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
2021
+ const metaStr = meta ? ` ${this.safeStringifyMeta(meta)}` : "";
1938
2022
  return `[${timestamp}] [${level}] ${message}${metaStr}`;
1939
2023
  }
1940
2024
  emit(level, message, meta) {
2025
+ const serializedMeta = this.serializeMetaErrors(meta);
2026
+ const finalMeta = this.applyRepeatAccounting(level, message, serializedMeta);
1941
2027
  const ts = new Date().toISOString();
1942
- const line = this.formatMessage(LOG_LEVEL_LABELS[level], message, meta, ts);
2028
+ const line = this.formatMessage(LOG_LEVEL_LABELS[level], message, finalMeta, ts);
1943
2029
  console.error(line);
1944
2030
  if (this.enableFileSink) {
1945
2031
  const filePath = this.logFilePath;
@@ -1951,7 +2037,7 @@ class Logger {
1951
2037
  ts,
1952
2038
  level: LOG_LEVEL_BUFFER_TAGS[level],
1953
2039
  message,
1954
- ...meta ? { meta } : {}
2040
+ ...finalMeta ? { meta: finalMeta } : {}
1955
2041
  });
1956
2042
  }
1957
2043
  debug(message, meta) {
@@ -1973,11 +2059,7 @@ class Logger {
1973
2059
  if (this.shouldLog(LogLevel.ERROR)) {
1974
2060
  const errorMeta = error ? {
1975
2061
  ...meta,
1976
- error: {
1977
- name: error.name,
1978
- message: error.message,
1979
- stack: error.stack
1980
- }
2062
+ error: error instanceof Error ? pickErrorFields(error, true) : { message: String(error) }
1981
2063
  } : meta;
1982
2064
  this.emit(LogLevel.ERROR, message, errorMeta);
1983
2065
  }
@@ -2071,7 +2153,9 @@ class SmartRateLimiter {
2071
2153
  const hasRequestCapacity = this.requestLimiter.tryConsume(1);
2072
2154
  const hasTokenCapacity = this.tokenLimiter.tryConsume(estimatedTokens);
2073
2155
  if (!hasRequestCapacity) {
2074
- logger.warn("Request rate limit exceeded");
2156
+ logger.warn("Request rate limit exceeded", {
2157
+ availableTokens: this.requestLimiter.getAvailableTokens()
2158
+ });
2075
2159
  return false;
2076
2160
  }
2077
2161
  if (!hasTokenCapacity) {
@@ -2603,6 +2687,7 @@ function readRoles(liveRoot, activeProfile) {
2603
2687
  }
2604
2688
  function runtimeDriftReport(opts = {}) {
2605
2689
  const targetHome = opts.targetHome ?? os6.homedir();
2690
+ const host = opts.host ?? "claude";
2606
2691
  const stateFilePath = opts.stateFilePath ?? path10.join(targetHome, ".config", "massa-ai", "install-state.json");
2607
2692
  let state = opts.state ?? null;
2608
2693
  if (state === null) {
@@ -2612,9 +2697,24 @@ function runtimeDriftReport(opts = {}) {
2612
2697
  state = null;
2613
2698
  }
2614
2699
  }
2615
- const platform = state?.platforms?.claude;
2700
+ const platform = state?.platforms?.[host];
2616
2701
  const stateVersion = typeof platform?.plugin?.version === "string" ? platform.plugin.version : null;
2617
2702
  const activeProfile = platform?.modelProfile?.profile ?? null;
2703
+ if (host !== "claude") {
2704
+ return {
2705
+ host,
2706
+ route: "unresolved",
2707
+ liveRoot: null,
2708
+ sourceVersion: null,
2709
+ stateVersion,
2710
+ pinnedVersion: null,
2711
+ activeProfile,
2712
+ roles: [],
2713
+ envOverride: detectEnvOverride(opts.env ?? process.env),
2714
+ versionDrift: false,
2715
+ profileMaterialized: false
2716
+ };
2717
+ }
2618
2718
  const install = resolveClaudeMarketplaceInstall({ targetHome, pluginKey: opts.pluginKey });
2619
2719
  const liveRoot = install?.root ?? null;
2620
2720
  const sourceVersion = liveRoot === null ? null : readPluginVersion(liveRoot);
@@ -2683,7 +2783,7 @@ function listProfiles(opts = {}) {
2683
2783
  installed: false,
2684
2784
  skipped: false,
2685
2785
  skipReason: null,
2686
- activeProfile: platform2.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
2786
+ activeProfile: platform2.modelProfile?.profile ?? "balanced",
2687
2787
  bundleVersion: platform2.plugin?.version ?? null,
2688
2788
  availableProfiles: [],
2689
2789
  ...claudeDriftFields(host)
@@ -2710,7 +2810,7 @@ function listProfiles(opts = {}) {
2710
2810
  installed,
2711
2811
  skipped: false,
2712
2812
  skipReason: null,
2713
- activeProfile: platform?.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
2813
+ activeProfile: platform?.modelProfile?.profile ?? "balanced",
2714
2814
  bundleVersion: platform?.plugin?.version ?? null,
2715
2815
  availableProfiles,
2716
2816
  ...claudeDriftFields(host)
@@ -3676,6 +3776,14 @@ Commands:
3676
3776
  profile set <name> [--host <h>] [--dry-run]
3677
3777
  Switch installed agents to a profile (restart required after)
3678
3778
 
3779
+ doctor [--fix] [--host <h>] [--target <dir>]
3780
+ Report agent model/profile drift: live-tree vs recorded
3781
+ versions, per-role models, variant staleness, env
3782
+ overrides. --fix re-runs the profile switch for the
3783
+ recorded active profile (restart required after).
3784
+ --target redirects the home the state/registries are
3785
+ read from (test seam, same convention as bootstrap)
3786
+
3679
3787
  bootstrap list List every startup-contract rule: state, default, description
3680
3788
  bootstrap show Same as 'bootstrap list'
3681
3789
  bootstrap enable <rule-id> [--target <dir> --yes] [--dry-run]
@@ -3693,6 +3801,8 @@ Examples:
3693
3801
  massa-ai-config set embedding.dimensions 1024
3694
3802
  massa-ai-config agents install --user
3695
3803
  massa-ai-config profile set work --dry-run
3804
+ massa-ai-config doctor
3805
+ massa-ai-config doctor --fix
3696
3806
  massa-ai-config bootstrap list
3697
3807
  massa-ai-config bootstrap disable caveman
3698
3808
  `);
@@ -3736,6 +3846,30 @@ function formatSwitchReport(report) {
3736
3846
  A host session restart is required for the change to take effect.`);
3737
3847
  }
3738
3848
  }
3849
+ function formatDriftReport(report) {
3850
+ console.log(`doctor (${report.host}, route: ${report.route})`);
3851
+ console.log(` live root: ${report.liveRoot ?? "n/a"}`);
3852
+ console.log(` source version: ${report.sourceVersion ?? "n/a"} (live tree)`);
3853
+ console.log(` state version: ${report.stateVersion ?? "n/a"} (install-state)`);
3854
+ console.log(` pinned version: ${report.pinnedVersion ?? "n/a"} (installed_plugins)`);
3855
+ console.log(` active profile: ${report.activeProfile ?? "n/a"}`);
3856
+ for (const role of report.roles) {
3857
+ const stale = role.staleVariant ? " \u2014 STALE vs the recorded profile's variant" : "";
3858
+ console.log(` ${role.name}: model=${role.model ?? "unknown"} effort=${role.effort ?? "unknown"}${stale}`);
3859
+ }
3860
+ if (report.versionDrift) {
3861
+ console.log(` drift: live tree ${report.sourceVersion} != recorded ${report.stateVersion} \u2014 update the plugin (or re-run the installer)`);
3862
+ }
3863
+ if (report.profileMaterialized) {
3864
+ console.log(" drift: active agent files differ from the recorded profile's variants \u2014 run `massa-ai-config doctor --fix` (re-runs the profile switch)");
3865
+ }
3866
+ if (report.envOverride) {
3867
+ console.log(` override: ${report.envOverride.name}=${report.envOverride.value} wins over every per-agent model at runtime \u2014 remove it from the host env to let profiles govern`);
3868
+ }
3869
+ if (report.route !== "unresolved" && !report.versionDrift && !report.profileMaterialized && !report.envOverride) {
3870
+ console.log(" healthy: every recording agrees.");
3871
+ }
3872
+ }
3739
3873
  async function runCli(argv) {
3740
3874
  const args = argv;
3741
3875
  const command = args[0];
@@ -3990,6 +4124,39 @@ Using defaults:`);
3990
4124
  console.error("Usage: massa-ai-config profile <list|show|set> ...");
3991
4125
  return 1;
3992
4126
  }
4127
+ case "doctor": {
4128
+ const fix = options["fix"] === true;
4129
+ const hostOpt = typeof options.host === "string" ? options.host : undefined;
4130
+ if (hostOpt !== undefined && !isHost(hostOpt)) {
4131
+ console.error(`Error: unknown host "${hostOpt}"`);
4132
+ return 1;
4133
+ }
4134
+ const host = hostOpt ?? "claude";
4135
+ const targetHome = typeof options.target === "string" ? options.target : os9.homedir();
4136
+ try {
4137
+ let report = runtimeDriftReport({ targetHome, host });
4138
+ if (fix) {
4139
+ const profile = report.activeProfile;
4140
+ if (!profile) {
4141
+ console.error("Error: no recorded active profile in install-state.json \u2014 run " + "`massa-ai-config profile set <name>` first; there is nothing to fix from.");
4142
+ return 1;
4143
+ }
4144
+ const sourceRoot = findRepoRootWithMarker(__dirname2, GENERATOR_MARKER, GENERATOR_MARKER_MAX_LEVELS);
4145
+ formatVariantSync(syncGeneratedVariants({ sourceRoot, targetHome }));
4146
+ const switchReport = switchProfile({ profile, host, targetHome });
4147
+ formatSwitchReport(switchReport);
4148
+ if (!reportSucceeded(switchReport)) {
4149
+ return 1;
4150
+ }
4151
+ report = runtimeDriftReport({ targetHome, host });
4152
+ }
4153
+ formatDriftReport(report);
4154
+ return 0;
4155
+ } catch (e) {
4156
+ console.error(`Error: ${e.message}`);
4157
+ return 1;
4158
+ }
4159
+ }
3993
4160
  case "bootstrap": {
3994
4161
  const subcommand = args[1];
3995
4162
  if (subcommand === "list" || subcommand === "show") {
package/dist/index.js CHANGED
@@ -984,7 +984,7 @@ function getConfigForEnv() {
984
984
  } else {
985
985
  console.error(`[getConfigForEnv] embedding.provider "${provider}" has no env-projection branch \u2014 no embedding env vars were set`);
986
986
  }
987
- env.LOG_LEVEL = config.logging.level;
987
+ env.MASSA_AI_LOG_LEVEL = config.logging.level;
988
988
  env.ENABLE_METRICS = String(config.logging.enableMetrics);
989
989
  return env;
990
990
  }
@@ -1382,7 +1382,7 @@ var defaultConfig = {
1382
1382
  corsOrigins: envList("MASSA_AI_API_CORS_ORIGINS", fileConfig.security?.corsOrigins ?? [])
1383
1383
  },
1384
1384
  logging: {
1385
- level: process.env.LOG_LEVEL || fileConfig.logging?.level || "info",
1385
+ level: process.env.MASSA_AI_LOG_LEVEL || fileConfig.logging?.level || "info",
1386
1386
  enableMetrics: process.env.ENABLE_METRICS === "true" || process.env.ENABLE_METRICS === undefined && !!fileConfig.logging?.enableMetrics,
1387
1387
  file: process.env.MASSA_AI_LOG_FILE || fileConfig.logging?.file || path4.join(resolvedDataDir, "logs", "massa-ai.log"),
1388
1388
  enableFileSink: envBool("MASSA_AI_LOG_ENABLE_FILE_SINK", fileConfig.logging?.enableFileSink ?? true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/opencode-plugin",
3
- "version": "1.60.1",
3
+ "version": "1.61.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.60.1",
28
- "@massa-ai/shared": "^1.60.1"
27
+ "@massa-ai/core": "^1.61.0",
28
+ "@massa-ai/shared": "^1.61.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.5",