@massa-ai/opencode-plugin 1.60.0 → 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.
- package/agent-profiles/balanced/massa-ai-judge.md +3 -3
- package/agent-profiles/balanced/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/cheap/massa-ai-judge.md +3 -3
- package/agent-profiles/cheap/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/heavy/massa-ai-judge.md +3 -3
- package/agent-profiles/heavy/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/home/massa-ai-judge.md +3 -3
- package/agent-profiles/home/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/local_models/massa-ai-judge.md +3 -3
- package/agent-profiles/local_models/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/open_models/massa-ai-judge.md +3 -3
- package/agent-profiles/open_models/massa-ai-meta-judge.md +3 -2
- package/agent-profiles/work/massa-ai-judge.md +3 -3
- package/agent-profiles/work/massa-ai-meta-judge.md +3 -2
- package/agents/massa-ai-judge.md +3 -3
- package/agents/massa-ai-meta-judge.md +3 -2
- package/dist/config-cli.js +470 -114
- package/dist/index.js +2 -2
- package/package.json +3 -3
|
@@ -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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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.
|
package/agents/massa-ai-judge.md
CHANGED
|
@@ -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): `
|
|
84
|
-
|
|
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): `
|
|
75
|
-
|
|
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.
|
package/dist/config-cli.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 ? ` ${
|
|
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,
|
|
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
|
-
...
|
|
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) {
|
|
@@ -2343,9 +2427,9 @@ function acquireLock(stateFilePath, options = {}) {
|
|
|
2343
2427
|
}
|
|
2344
2428
|
}
|
|
2345
2429
|
// ../../packages/shared/dist/profile-switch/engine.js
|
|
2346
|
-
import
|
|
2347
|
-
import
|
|
2348
|
-
import
|
|
2430
|
+
import fs7 from "fs";
|
|
2431
|
+
import path11 from "path";
|
|
2432
|
+
import os7 from "os";
|
|
2349
2433
|
import crypto3 from "crypto";
|
|
2350
2434
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2351
2435
|
|
|
@@ -2425,12 +2509,13 @@ function selectRecord(records) {
|
|
|
2425
2509
|
}
|
|
2426
2510
|
return best ?? pool[pool.length - 1];
|
|
2427
2511
|
}
|
|
2428
|
-
function
|
|
2512
|
+
function resolveClaudeMarketplaceInstall(opts = {}) {
|
|
2429
2513
|
const targetHome = opts.targetHome ?? os5.homedir();
|
|
2430
2514
|
const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
|
|
2431
2515
|
const directoryResult = resolveDirectorySourceRoot(targetHome, pluginKey);
|
|
2432
|
-
if (directoryResult !== undefined)
|
|
2433
|
-
return directoryResult;
|
|
2516
|
+
if (directoryResult !== undefined) {
|
|
2517
|
+
return directoryResult === null ? null : { root: directoryResult, route: "directory-source" };
|
|
2518
|
+
}
|
|
2434
2519
|
const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2435
2520
|
let records;
|
|
2436
2521
|
try {
|
|
@@ -2452,7 +2537,202 @@ function resolveClaudeMarketplaceRoot(opts = {}) {
|
|
|
2452
2537
|
} catch {
|
|
2453
2538
|
return null;
|
|
2454
2539
|
}
|
|
2455
|
-
return installPath;
|
|
2540
|
+
return { root: installPath, route: "registry-cache" };
|
|
2541
|
+
}
|
|
2542
|
+
function resolveClaudeMarketplaceRoot(opts = {}) {
|
|
2543
|
+
return resolveClaudeMarketplaceInstall(opts)?.root ?? null;
|
|
2544
|
+
}
|
|
2545
|
+
function readInstalledPluginVersion(opts = {}) {
|
|
2546
|
+
const targetHome = opts.targetHome ?? os5.homedir();
|
|
2547
|
+
const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
|
|
2548
|
+
const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2549
|
+
let records;
|
|
2550
|
+
try {
|
|
2551
|
+
const parsed = JSON.parse(fs5.readFileSync(registryPath, "utf8"));
|
|
2552
|
+
records = parsed?.plugins?.[pluginKey];
|
|
2553
|
+
} catch {
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
if (!Array.isArray(records) || records.length === 0)
|
|
2557
|
+
return null;
|
|
2558
|
+
return selectRecord(records)?.version ?? null;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
// ../../packages/shared/dist/profile-switch/doctor.js
|
|
2562
|
+
import fs6 from "fs";
|
|
2563
|
+
import os6 from "os";
|
|
2564
|
+
import path10 from "path";
|
|
2565
|
+
|
|
2566
|
+
// ../../packages/shared/dist/profile-switch/frontmatter.js
|
|
2567
|
+
function parseFrontmatter(raw) {
|
|
2568
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(raw);
|
|
2569
|
+
if (!match) {
|
|
2570
|
+
throw new Error("charter missing YAML frontmatter (--- ... ---) block");
|
|
2571
|
+
}
|
|
2572
|
+
const yamlText = match[1] ?? "";
|
|
2573
|
+
const body = (match[2] ?? "").replace(/^\r?\n/, "");
|
|
2574
|
+
const frontmatter = parseSimpleYaml(yamlText);
|
|
2575
|
+
return { frontmatter, body };
|
|
2576
|
+
}
|
|
2577
|
+
function parseSimpleYaml(text) {
|
|
2578
|
+
const result = {};
|
|
2579
|
+
const lines = text.split(/\r?\n/);
|
|
2580
|
+
let i = 0;
|
|
2581
|
+
while (i < lines.length) {
|
|
2582
|
+
const line = lines[i] ?? "";
|
|
2583
|
+
if (line.trim() === "" || line.trim().startsWith("#")) {
|
|
2584
|
+
i++;
|
|
2585
|
+
continue;
|
|
2586
|
+
}
|
|
2587
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/.exec(line);
|
|
2588
|
+
if (!m) {
|
|
2589
|
+
i++;
|
|
2590
|
+
continue;
|
|
2591
|
+
}
|
|
2592
|
+
const key = m[1];
|
|
2593
|
+
const rest = (m[2] ?? "").trim();
|
|
2594
|
+
if (rest !== "") {
|
|
2595
|
+
result[key] = unquoteScalar(rest);
|
|
2596
|
+
i++;
|
|
2597
|
+
continue;
|
|
2598
|
+
}
|
|
2599
|
+
const nested = {};
|
|
2600
|
+
i++;
|
|
2601
|
+
while (i < lines.length) {
|
|
2602
|
+
const nestedLine = lines[i] ?? "";
|
|
2603
|
+
if (/^\s{2,}\S/.test(nestedLine) === false)
|
|
2604
|
+
break;
|
|
2605
|
+
const nm = /^\s{2,}([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/.exec(nestedLine);
|
|
2606
|
+
if (!nm)
|
|
2607
|
+
break;
|
|
2608
|
+
nested[nm[1]] = unquoteScalar((nm[2] ?? "").trim());
|
|
2609
|
+
i++;
|
|
2610
|
+
}
|
|
2611
|
+
result[key] = nested;
|
|
2612
|
+
}
|
|
2613
|
+
return result;
|
|
2614
|
+
}
|
|
2615
|
+
function unquoteScalar(s) {
|
|
2616
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
2617
|
+
return s.slice(1, -1);
|
|
2618
|
+
}
|
|
2619
|
+
return s;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
// ../../packages/shared/dist/profile-switch/doctor.js
|
|
2623
|
+
var ENV_OVERRIDE_VARS = ["CLAUDE_CODE_SUBAGENT_MODEL"];
|
|
2624
|
+
function readTextFile(filePath) {
|
|
2625
|
+
try {
|
|
2626
|
+
return fs6.readFileSync(filePath, "utf8");
|
|
2627
|
+
} catch {
|
|
2628
|
+
return null;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
function readJsonFile(filePath) {
|
|
2632
|
+
const raw = readTextFile(filePath);
|
|
2633
|
+
if (raw === null)
|
|
2634
|
+
return null;
|
|
2635
|
+
try {
|
|
2636
|
+
return JSON.parse(raw);
|
|
2637
|
+
} catch {
|
|
2638
|
+
return null;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
function readPluginVersion(pluginRoot) {
|
|
2642
|
+
const manifest = readJsonFile(path10.join(pluginRoot, ".claude-plugin", "plugin.json"));
|
|
2643
|
+
return typeof manifest?.version === "string" ? manifest.version : null;
|
|
2644
|
+
}
|
|
2645
|
+
function detectEnvOverride(env) {
|
|
2646
|
+
for (const name of ENV_OVERRIDE_VARS) {
|
|
2647
|
+
const value = env[name];
|
|
2648
|
+
if (typeof value === "string" && value.trim()) {
|
|
2649
|
+
return { name, value: value.trim() };
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
return null;
|
|
2653
|
+
}
|
|
2654
|
+
function readRoles(liveRoot, activeProfile) {
|
|
2655
|
+
const agentsDir = path10.join(liveRoot, "agents");
|
|
2656
|
+
let entries;
|
|
2657
|
+
try {
|
|
2658
|
+
entries = fs6.readdirSync(agentsDir, { withFileTypes: true });
|
|
2659
|
+
} catch {
|
|
2660
|
+
return [];
|
|
2661
|
+
}
|
|
2662
|
+
const roles = [];
|
|
2663
|
+
for (const entry of entries) {
|
|
2664
|
+
if (!entry.isFile() || !entry.name.startsWith("massa-ai-") || !entry.name.endsWith(".md")) {
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
const activeRaw = readTextFile(path10.join(agentsDir, entry.name));
|
|
2668
|
+
let model = null;
|
|
2669
|
+
let effort = null;
|
|
2670
|
+
if (activeRaw !== null) {
|
|
2671
|
+
try {
|
|
2672
|
+
const { frontmatter } = parseFrontmatter(activeRaw);
|
|
2673
|
+
model = typeof frontmatter.model === "string" ? frontmatter.model : null;
|
|
2674
|
+
effort = typeof frontmatter.effort === "string" ? frontmatter.effort : null;
|
|
2675
|
+
} catch {}
|
|
2676
|
+
}
|
|
2677
|
+
let staleVariant = false;
|
|
2678
|
+
if (activeProfile && activeRaw !== null) {
|
|
2679
|
+
const variantRaw = readTextFile(path10.join(liveRoot, "agent-profiles", activeProfile, entry.name));
|
|
2680
|
+
if (variantRaw !== null) {
|
|
2681
|
+
staleVariant = variantRaw !== activeRaw;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
roles.push({ name: entry.name, model, effort, staleVariant });
|
|
2685
|
+
}
|
|
2686
|
+
return roles.sort((a, b) => a.name.localeCompare(b.name));
|
|
2687
|
+
}
|
|
2688
|
+
function runtimeDriftReport(opts = {}) {
|
|
2689
|
+
const targetHome = opts.targetHome ?? os6.homedir();
|
|
2690
|
+
const host = opts.host ?? "claude";
|
|
2691
|
+
const stateFilePath = opts.stateFilePath ?? path10.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2692
|
+
let state = opts.state ?? null;
|
|
2693
|
+
if (state === null) {
|
|
2694
|
+
try {
|
|
2695
|
+
state = readInstallState(stateFilePath);
|
|
2696
|
+
} catch {
|
|
2697
|
+
state = null;
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
const platform = state?.platforms?.[host];
|
|
2701
|
+
const stateVersion = typeof platform?.plugin?.version === "string" ? platform.plugin.version : null;
|
|
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
|
+
}
|
|
2718
|
+
const install = resolveClaudeMarketplaceInstall({ targetHome, pluginKey: opts.pluginKey });
|
|
2719
|
+
const liveRoot = install?.root ?? null;
|
|
2720
|
+
const sourceVersion = liveRoot === null ? null : readPluginVersion(liveRoot);
|
|
2721
|
+
const pinnedVersion = readInstalledPluginVersion({ targetHome, pluginKey: opts.pluginKey });
|
|
2722
|
+
const roles = liveRoot === null ? [] : readRoles(liveRoot, activeProfile);
|
|
2723
|
+
return {
|
|
2724
|
+
host: "claude",
|
|
2725
|
+
route: install?.route ?? "unresolved",
|
|
2726
|
+
liveRoot,
|
|
2727
|
+
sourceVersion,
|
|
2728
|
+
stateVersion,
|
|
2729
|
+
pinnedVersion,
|
|
2730
|
+
activeProfile,
|
|
2731
|
+
roles,
|
|
2732
|
+
envOverride: detectEnvOverride(opts.env ?? process.env),
|
|
2733
|
+
versionDrift: sourceVersion !== null && stateVersion !== null && sourceVersion !== stateVersion,
|
|
2734
|
+
profileMaterialized: roles.some((role) => role.staleVariant)
|
|
2735
|
+
};
|
|
2456
2736
|
}
|
|
2457
2737
|
|
|
2458
2738
|
// ../../packages/shared/dist/profile-switch/engine.js
|
|
@@ -2470,10 +2750,10 @@ function namedError3(name, message) {
|
|
|
2470
2750
|
var UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`);
|
|
2471
2751
|
var NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found");
|
|
2472
2752
|
function defaultStatePath(targetHome) {
|
|
2473
|
-
return
|
|
2753
|
+
return path11.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2474
2754
|
}
|
|
2475
2755
|
function resolveCommon(opts) {
|
|
2476
|
-
const targetHome = opts.targetHome ??
|
|
2756
|
+
const targetHome = opts.targetHome ?? os7.homedir();
|
|
2477
2757
|
const stateFilePath = opts.stateFilePath ?? defaultStatePath(targetHome);
|
|
2478
2758
|
return { targetHome, stateFilePath };
|
|
2479
2759
|
}
|
|
@@ -2481,7 +2761,7 @@ function marketplaceRoots(targetHome, state) {
|
|
|
2481
2761
|
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
2482
2762
|
}
|
|
2483
2763
|
function claudeMarketplaceUnresolvedReason(targetHome) {
|
|
2484
|
-
const registryPath =
|
|
2764
|
+
const registryPath = path11.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2485
2765
|
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";
|
|
2486
2766
|
}
|
|
2487
2767
|
function listProfiles(opts = {}) {
|
|
@@ -2489,6 +2769,12 @@ function listProfiles(opts = {}) {
|
|
|
2489
2769
|
const state = readInstallState(stateFilePath);
|
|
2490
2770
|
const roots = marketplaceRoots(targetHome, state);
|
|
2491
2771
|
const universe = opts.hosts ?? HOSTS;
|
|
2772
|
+
const claudeDrift = universe.includes("claude") ? runtimeDriftReport({ targetHome, stateFilePath, state, env: opts.env }) : null;
|
|
2773
|
+
const claudeDriftFields = (host) => host === "claude" && claudeDrift !== null ? {
|
|
2774
|
+
liveRoot: claudeDrift.liveRoot,
|
|
2775
|
+
sourceVersion: claudeDrift.sourceVersion,
|
|
2776
|
+
envOverride: claudeDrift.envOverride ? `${claudeDrift.envOverride.name}=${claudeDrift.envOverride.value}` : null
|
|
2777
|
+
} : { liveRoot: null, sourceVersion: null, envOverride: null };
|
|
2492
2778
|
const hosts = universe.map((host) => {
|
|
2493
2779
|
if (host === "claude" && state.platforms.claude?.installRoute === "marketplace" && roots.claude === undefined) {
|
|
2494
2780
|
const platform2 = state.platforms.claude;
|
|
@@ -2497,9 +2783,10 @@ function listProfiles(opts = {}) {
|
|
|
2497
2783
|
installed: false,
|
|
2498
2784
|
skipped: false,
|
|
2499
2785
|
skipReason: null,
|
|
2500
|
-
activeProfile: platform2.modelProfile?.profile ??
|
|
2786
|
+
activeProfile: platform2.modelProfile?.profile ?? "balanced",
|
|
2501
2787
|
bundleVersion: platform2.plugin?.version ?? null,
|
|
2502
|
-
availableProfiles: []
|
|
2788
|
+
availableProfiles: [],
|
|
2789
|
+
...claudeDriftFields(host)
|
|
2503
2790
|
};
|
|
2504
2791
|
}
|
|
2505
2792
|
const layout = resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots });
|
|
@@ -2511,10 +2798,11 @@ function listProfiles(opts = {}) {
|
|
|
2511
2798
|
skipReason: layout.reason,
|
|
2512
2799
|
activeProfile: null,
|
|
2513
2800
|
bundleVersion: null,
|
|
2514
|
-
availableProfiles: []
|
|
2801
|
+
availableProfiles: [],
|
|
2802
|
+
...claudeDriftFields(host)
|
|
2515
2803
|
};
|
|
2516
2804
|
}
|
|
2517
|
-
const installed =
|
|
2805
|
+
const installed = fs7.existsSync(layout.activeDir);
|
|
2518
2806
|
const availableProfiles = listVariantProfiles(layout);
|
|
2519
2807
|
const platform = state.platforms[host];
|
|
2520
2808
|
return {
|
|
@@ -2522,17 +2810,18 @@ function listProfiles(opts = {}) {
|
|
|
2522
2810
|
installed,
|
|
2523
2811
|
skipped: false,
|
|
2524
2812
|
skipReason: null,
|
|
2525
|
-
activeProfile: platform?.modelProfile?.profile ??
|
|
2813
|
+
activeProfile: platform?.modelProfile?.profile ?? "balanced",
|
|
2526
2814
|
bundleVersion: platform?.plugin?.version ?? null,
|
|
2527
|
-
availableProfiles
|
|
2815
|
+
availableProfiles,
|
|
2816
|
+
...claudeDriftFields(host)
|
|
2528
2817
|
};
|
|
2529
2818
|
});
|
|
2530
2819
|
return { hosts };
|
|
2531
2820
|
}
|
|
2532
2821
|
function listVariantProfiles(layout) {
|
|
2533
|
-
if (!
|
|
2822
|
+
if (!fs7.existsSync(layout.variantsRoot))
|
|
2534
2823
|
return [];
|
|
2535
|
-
return
|
|
2824
|
+
return fs7.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2536
2825
|
}
|
|
2537
2826
|
function matchesGlob(filename, glob) {
|
|
2538
2827
|
const starIdx = glob.indexOf("*");
|
|
@@ -2543,7 +2832,7 @@ function matchesGlob(filename, glob) {
|
|
|
2543
2832
|
return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
|
|
2544
2833
|
}
|
|
2545
2834
|
function matchingFileNames(dir, glob) {
|
|
2546
|
-
return
|
|
2835
|
+
return fs7.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && matchesGlob(e.name, glob)).map((e) => e.name);
|
|
2547
2836
|
}
|
|
2548
2837
|
function detectGitAvailability(dir) {
|
|
2549
2838
|
try {
|
|
@@ -2571,7 +2860,7 @@ function gitTrackedFileNames(dir, filenames) {
|
|
|
2571
2860
|
var GUARD_PASS = { blocked: false, unchecked: false };
|
|
2572
2861
|
var GUARD_UNCHECKED = { blocked: false, unchecked: true };
|
|
2573
2862
|
function checkTrackedPathGuard(activeDir, filenames) {
|
|
2574
|
-
if (filenames.length === 0 || !
|
|
2863
|
+
if (filenames.length === 0 || !fs7.existsSync(activeDir))
|
|
2575
2864
|
return GUARD_PASS;
|
|
2576
2865
|
const availability = detectGitAvailability(activeDir);
|
|
2577
2866
|
if (availability === "no-git")
|
|
@@ -2582,53 +2871,53 @@ function checkTrackedPathGuard(activeDir, filenames) {
|
|
|
2582
2871
|
if (tracked.size === 0)
|
|
2583
2872
|
return GUARD_PASS;
|
|
2584
2873
|
const offending = filenames.find((name) => tracked.has(name));
|
|
2585
|
-
return { blocked: true, path:
|
|
2874
|
+
return { blocked: true, path: path11.join(activeDir, offending), unchecked: false };
|
|
2586
2875
|
}
|
|
2587
2876
|
function assertStateWritable(stateFilePath) {
|
|
2588
|
-
const dir =
|
|
2877
|
+
const dir = path11.dirname(stateFilePath);
|
|
2589
2878
|
try {
|
|
2590
|
-
|
|
2879
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
2591
2880
|
} catch (err) {
|
|
2592
2881
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
2593
2882
|
}
|
|
2594
|
-
const checkPath =
|
|
2883
|
+
const checkPath = fs7.existsSync(stateFilePath) ? stateFilePath : dir;
|
|
2595
2884
|
try {
|
|
2596
|
-
|
|
2885
|
+
fs7.accessSync(checkPath, fs7.constants.W_OK);
|
|
2597
2886
|
} catch (err) {
|
|
2598
2887
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
2599
2888
|
}
|
|
2600
2889
|
}
|
|
2601
2890
|
function copyFileRouteVariant(layout, variantDir) {
|
|
2602
|
-
|
|
2891
|
+
fs7.mkdirSync(layout.activeDir, { recursive: true });
|
|
2603
2892
|
let changed = 0;
|
|
2604
|
-
for (const entry of
|
|
2893
|
+
for (const entry of fs7.readdirSync(variantDir, { withFileTypes: true })) {
|
|
2605
2894
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
2606
2895
|
continue;
|
|
2607
|
-
|
|
2896
|
+
fs7.copyFileSync(path11.join(variantDir, entry.name), path11.join(layout.activeDir, entry.name));
|
|
2608
2897
|
changed++;
|
|
2609
2898
|
}
|
|
2610
2899
|
return changed;
|
|
2611
2900
|
}
|
|
2612
2901
|
function repointOpencodeVariant(layout, variantDir) {
|
|
2613
|
-
|
|
2902
|
+
fs7.mkdirSync(layout.activeDir, { recursive: true });
|
|
2614
2903
|
let changed = 0;
|
|
2615
|
-
for (const entry of
|
|
2904
|
+
for (const entry of fs7.readdirSync(variantDir, { withFileTypes: true })) {
|
|
2616
2905
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
2617
2906
|
continue;
|
|
2618
|
-
const dest =
|
|
2619
|
-
const target =
|
|
2907
|
+
const dest = path11.join(layout.activeDir, entry.name);
|
|
2908
|
+
const target = path11.resolve(path11.join(variantDir, entry.name));
|
|
2620
2909
|
let destExists = true;
|
|
2621
2910
|
let destIsSymlink = false;
|
|
2622
2911
|
try {
|
|
2623
|
-
destIsSymlink =
|
|
2912
|
+
destIsSymlink = fs7.lstatSync(dest).isSymbolicLink();
|
|
2624
2913
|
} catch {
|
|
2625
2914
|
destExists = false;
|
|
2626
2915
|
}
|
|
2627
2916
|
if (destExists && !destIsSymlink)
|
|
2628
2917
|
continue;
|
|
2629
2918
|
const tmp = `${dest}.massa-ai-switch.${crypto3.randomUUID()}`;
|
|
2630
|
-
|
|
2631
|
-
|
|
2919
|
+
fs7.symlinkSync(target, tmp);
|
|
2920
|
+
fs7.renameSync(tmp, dest);
|
|
2632
2921
|
changed++;
|
|
2633
2922
|
}
|
|
2634
2923
|
return changed;
|
|
@@ -2668,13 +2957,13 @@ function switchProfile(opts) {
|
|
|
2668
2957
|
if (fileHosts.length === 0) {
|
|
2669
2958
|
return { profile: opts.profile, dryRun, hosts: orderRows(universe, skipRows), restartRequired: false };
|
|
2670
2959
|
}
|
|
2671
|
-
const installedFileHosts = fileHosts.filter((h) =>
|
|
2960
|
+
const installedFileHosts = fileHosts.filter((h) => fs7.existsSync(h.layout.activeDir));
|
|
2672
2961
|
if (installedFileHosts.length === 0)
|
|
2673
2962
|
throw NoHostsDetectedError();
|
|
2674
2963
|
const withAvailability = fileHosts.map((h) => {
|
|
2675
|
-
const variantsRootExists =
|
|
2964
|
+
const variantsRootExists = fs7.existsSync(h.layout.variantsRoot);
|
|
2676
2965
|
const variantDir = h.layout.variantDir(opts.profile);
|
|
2677
|
-
const available = variantsRootExists &&
|
|
2966
|
+
const available = variantsRootExists && fs7.existsSync(variantDir) && fs7.statSync(variantDir).isDirectory();
|
|
2678
2967
|
return { ...h, variantsRootExists, variantDir, available };
|
|
2679
2968
|
});
|
|
2680
2969
|
if (!withAvailability.some((h) => h.available)) {
|
|
@@ -2710,7 +2999,7 @@ function switchProfile(opts) {
|
|
|
2710
2999
|
continue;
|
|
2711
3000
|
}
|
|
2712
3001
|
if (dryRun) {
|
|
2713
|
-
rows.push({ host: h.host, status: "
|
|
3002
|
+
rows.push({ host: h.host, status: "would-switch" });
|
|
2714
3003
|
continue;
|
|
2715
3004
|
}
|
|
2716
3005
|
const candidateNames = matchingFileNames(h.variantDir, h.layout.activeGlob);
|
|
@@ -2751,15 +3040,15 @@ function orderRows(universe, rows) {
|
|
|
2751
3040
|
}
|
|
2752
3041
|
// ../../packages/shared/dist/profile-switch/report.js
|
|
2753
3042
|
function reportSucceeded(report) {
|
|
2754
|
-
return report.hosts.every((h) => h.status === "switched" || h.status === "skipped");
|
|
3043
|
+
return report.hosts.every((h) => h.status === "switched" || h.status === "would-switch" || h.status === "skipped");
|
|
2755
3044
|
}
|
|
2756
3045
|
// ../../packages/shared/dist/profile-switch/variant-sync.js
|
|
2757
|
-
import
|
|
2758
|
-
import
|
|
2759
|
-
import
|
|
3046
|
+
import fs8 from "fs";
|
|
3047
|
+
import path12 from "path";
|
|
3048
|
+
import os8 from "os";
|
|
2760
3049
|
import crypto4 from "crypto";
|
|
2761
3050
|
function defaultStatePath2(targetHome) {
|
|
2762
|
-
return
|
|
3051
|
+
return path12.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2763
3052
|
}
|
|
2764
3053
|
function marketplaceRoots2(targetHome, state) {
|
|
2765
3054
|
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
@@ -2767,13 +3056,13 @@ function marketplaceRoots2(targetHome, state) {
|
|
|
2767
3056
|
var tempFileCounter2 = 0;
|
|
2768
3057
|
function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
2769
3058
|
const unique = `${process.pid}.${++tempFileCounter2}.${crypto4.randomBytes(6).toString("hex")}`;
|
|
2770
|
-
const tempFile =
|
|
3059
|
+
const tempFile = path12.join(destDir, `.${destName}.${unique}.tmp`);
|
|
2771
3060
|
try {
|
|
2772
|
-
|
|
2773
|
-
|
|
3061
|
+
fs8.writeFileSync(tempFile, content);
|
|
3062
|
+
fs8.renameSync(tempFile, path12.join(destDir, destName));
|
|
2774
3063
|
} catch (error) {
|
|
2775
3064
|
try {
|
|
2776
|
-
|
|
3065
|
+
fs8.unlinkSync(tempFile);
|
|
2777
3066
|
} catch {}
|
|
2778
3067
|
throw error;
|
|
2779
3068
|
}
|
|
@@ -2781,20 +3070,20 @@ function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
|
2781
3070
|
function isSafeDirName(name) {
|
|
2782
3071
|
if (name === "." || name === "..")
|
|
2783
3072
|
return false;
|
|
2784
|
-
if (name.includes("/") || name.includes("\\") || name.includes(
|
|
3073
|
+
if (name.includes("/") || name.includes("\\") || name.includes(path12.sep))
|
|
2785
3074
|
return false;
|
|
2786
|
-
return
|
|
3075
|
+
return path12.basename(name) === name;
|
|
2787
3076
|
}
|
|
2788
3077
|
function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
|
|
2789
3078
|
const layout = resolveHostLayout(host, { targetHome, marketplaceRoot });
|
|
2790
3079
|
if (layout.route === "skip") {
|
|
2791
3080
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: layout.reason };
|
|
2792
3081
|
}
|
|
2793
|
-
const srcDir =
|
|
2794
|
-
if (!
|
|
3082
|
+
const srcDir = path12.join(sourceRoot, "apps", `${host}-plugin`, "agent-profiles");
|
|
3083
|
+
if (!fs8.existsSync(srcDir) || !fs8.statSync(srcDir).isDirectory()) {
|
|
2795
3084
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: "no generated variants for this host" };
|
|
2796
3085
|
}
|
|
2797
|
-
if (!
|
|
3086
|
+
if (!fs8.existsSync(layout.variantsRoot)) {
|
|
2798
3087
|
return {
|
|
2799
3088
|
host,
|
|
2800
3089
|
status: "skipped",
|
|
@@ -2806,24 +3095,24 @@ function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
|
|
|
2806
3095
|
}
|
|
2807
3096
|
const profiles = [];
|
|
2808
3097
|
let files = 0;
|
|
2809
|
-
for (const entry of
|
|
3098
|
+
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
2810
3099
|
if (!entry.isDirectory())
|
|
2811
3100
|
continue;
|
|
2812
3101
|
if (!isSafeDirName(entry.name))
|
|
2813
3102
|
continue;
|
|
2814
|
-
const srcProfileDir =
|
|
2815
|
-
const destProfileDir =
|
|
2816
|
-
|
|
2817
|
-
for (const fileEntry of
|
|
3103
|
+
const srcProfileDir = path12.join(srcDir, entry.name);
|
|
3104
|
+
const destProfileDir = path12.join(layout.variantsRoot, entry.name);
|
|
3105
|
+
fs8.mkdirSync(destProfileDir, { recursive: true });
|
|
3106
|
+
for (const fileEntry of fs8.readdirSync(srcProfileDir, { withFileTypes: true })) {
|
|
2818
3107
|
if (!fileEntry.isFile())
|
|
2819
3108
|
continue;
|
|
2820
|
-
const content =
|
|
3109
|
+
const content = fs8.readFileSync(path12.join(srcProfileDir, fileEntry.name));
|
|
2821
3110
|
writeFileIntoDirAtomically(destProfileDir, fileEntry.name, content);
|
|
2822
3111
|
files++;
|
|
2823
3112
|
}
|
|
2824
3113
|
profiles.push(entry.name);
|
|
2825
3114
|
}
|
|
2826
|
-
const retained =
|
|
3115
|
+
const retained = fs8.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && !profiles.includes(e.name)).map((e) => e.name).sort();
|
|
2827
3116
|
return { host, status: "synced", profiles: profiles.sort(), retained, files };
|
|
2828
3117
|
}
|
|
2829
3118
|
function syncGeneratedVariants(opts) {
|
|
@@ -2839,7 +3128,7 @@ function syncGeneratedVariants(opts) {
|
|
|
2839
3128
|
}));
|
|
2840
3129
|
}
|
|
2841
3130
|
const sourceRoot = opts.sourceRoot;
|
|
2842
|
-
const targetHome = opts.targetHome ??
|
|
3131
|
+
const targetHome = opts.targetHome ?? os8.homedir();
|
|
2843
3132
|
const state = readInstallState(defaultStatePath2(targetHome));
|
|
2844
3133
|
const roots = marketplaceRoots2(targetHome, state);
|
|
2845
3134
|
return hosts.map((host) => {
|
|
@@ -2851,14 +3140,14 @@ function syncGeneratedVariants(opts) {
|
|
|
2851
3140
|
});
|
|
2852
3141
|
}
|
|
2853
3142
|
// ../../packages/shared/dist/profile-switch/repo-root.js
|
|
2854
|
-
import
|
|
2855
|
-
import
|
|
3143
|
+
import fs9 from "fs";
|
|
3144
|
+
import path13 from "path";
|
|
2856
3145
|
function findRepoRootWithMarker(startDir, marker, maxLevels) {
|
|
2857
3146
|
let dir = startDir;
|
|
2858
3147
|
for (let i = 0;i <= maxLevels; i++) {
|
|
2859
|
-
if (
|
|
3148
|
+
if (fs9.existsSync(path13.join(dir, marker)))
|
|
2860
3149
|
return dir;
|
|
2861
|
-
const parent =
|
|
3150
|
+
const parent = path13.dirname(dir);
|
|
2862
3151
|
if (parent === dir)
|
|
2863
3152
|
break;
|
|
2864
3153
|
dir = parent;
|
|
@@ -2953,7 +3242,7 @@ function assertKnownRuleId(id) {
|
|
|
2953
3242
|
}
|
|
2954
3243
|
// ../../packages/shared/dist/bootstrap/state.js
|
|
2955
3244
|
init_config_loader();
|
|
2956
|
-
import
|
|
3245
|
+
import fs10 from "fs";
|
|
2957
3246
|
var BOOTSTRAP_STATE_KEY = "bootstrap";
|
|
2958
3247
|
var BOOTSTRAP_RULES_KEY = "rules";
|
|
2959
3248
|
var BOOTSTRAP_STATE_PATH = `${BOOTSTRAP_STATE_KEY}.${BOOTSTRAP_RULES_KEY}`;
|
|
@@ -2987,7 +3276,7 @@ function resolveBootstrapState(doc) {
|
|
|
2987
3276
|
}
|
|
2988
3277
|
function readConfigBytes() {
|
|
2989
3278
|
try {
|
|
2990
|
-
return
|
|
3279
|
+
return fs10.readFileSync(getConfigPath(), "utf-8");
|
|
2991
3280
|
} catch (error) {
|
|
2992
3281
|
if (error?.code === "ENOENT")
|
|
2993
3282
|
return "";
|
|
@@ -3035,7 +3324,7 @@ function setBootstrapRuleEnabled(id, enabled) {
|
|
|
3035
3324
|
};
|
|
3036
3325
|
}
|
|
3037
3326
|
// ../../packages/shared/dist/bootstrap/render.js
|
|
3038
|
-
import
|
|
3327
|
+
import path14 from "path";
|
|
3039
3328
|
var BOOTSTRAP_BLOCK_START = "<!-- massa-ai:bootstrap:start -->";
|
|
3040
3329
|
var BOOTSTRAP_BLOCK_END = "<!-- massa-ai:bootstrap:end -->";
|
|
3041
3330
|
var CONTRACT_FILENAME = "MASSA-AI.md";
|
|
@@ -3067,19 +3356,19 @@ var HOST_CONFIG_DIR = {
|
|
|
3067
3356
|
function resolveHostRoot(host, targetHome, hostRoot) {
|
|
3068
3357
|
requireAbsoluteTargetHome(targetHome);
|
|
3069
3358
|
if (hostRoot === undefined)
|
|
3070
|
-
return
|
|
3071
|
-
const relative =
|
|
3072
|
-
if (!
|
|
3359
|
+
return path14.join(targetHome, ...HOST_CONFIG_DIR[host]);
|
|
3360
|
+
const relative = path14.relative(targetHome, hostRoot);
|
|
3361
|
+
if (!path14.isAbsolute(hostRoot) || relative === "" || relative.startsWith("..") || path14.isAbsolute(relative)) {
|
|
3073
3362
|
throw new BootstrapRenderError("HostRootOutsideTargetHomeError", `hostRoot must be an absolute directory inside targetHome, got "${hostRoot}" for targetHome "${targetHome}"`, [hostRoot, targetHome]);
|
|
3074
3363
|
}
|
|
3075
3364
|
return hostRoot;
|
|
3076
3365
|
}
|
|
3077
3366
|
function bootstrapContractPath(host, targetHome, hostRoot) {
|
|
3078
|
-
return
|
|
3367
|
+
return path14.join(resolveHostRoot(host, targetHome, hostRoot), CONTRACT_FILENAME);
|
|
3079
3368
|
}
|
|
3080
3369
|
function bootstrapStateFilePath(targetHome) {
|
|
3081
3370
|
requireAbsoluteTargetHome(targetHome);
|
|
3082
|
-
return
|
|
3371
|
+
return path14.join(targetHome, ".config", "massa-ai", "config.json");
|
|
3083
3372
|
}
|
|
3084
3373
|
function renderBootstrap(options) {
|
|
3085
3374
|
const { source, state, host, targetHome, hostRoot } = options;
|
|
@@ -3102,7 +3391,7 @@ ${body}`;
|
|
|
3102
3391
|
return { contract, pointer };
|
|
3103
3392
|
}
|
|
3104
3393
|
function requireAbsoluteTargetHome(targetHome) {
|
|
3105
|
-
if (!
|
|
3394
|
+
if (!path14.isAbsolute(targetHome)) {
|
|
3106
3395
|
throw new BootstrapRenderError("TargetHomeNotAbsoluteError", `targetHome must be an absolute path, got "${targetHome}"`, [targetHome]);
|
|
3107
3396
|
}
|
|
3108
3397
|
}
|
|
@@ -3255,8 +3544,8 @@ function buildBootstrapReport(input) {
|
|
|
3255
3544
|
}
|
|
3256
3545
|
// ../../packages/shared/dist/bootstrap/engine.js
|
|
3257
3546
|
init_config_loader();
|
|
3258
|
-
import
|
|
3259
|
-
import
|
|
3547
|
+
import fs11 from "fs";
|
|
3548
|
+
import path15 from "path";
|
|
3260
3549
|
var INSTALL_STATE_FILENAME = "install-state.json";
|
|
3261
3550
|
var WIRING_REMEDY = "scripts/install-skills.sh --apply";
|
|
3262
3551
|
|
|
@@ -3273,7 +3562,7 @@ function applyBootstrapState(options) {
|
|
|
3273
3562
|
const dryRun = options.dryRun ?? false;
|
|
3274
3563
|
const warn = options.onWarning ?? ((message) => console.warn(message));
|
|
3275
3564
|
const configPath = bootstrapStateFilePath(targetHome);
|
|
3276
|
-
const installStatePath =
|
|
3565
|
+
const installStatePath = path15.join(path15.dirname(configPath), INSTALL_STATE_FILENAME);
|
|
3277
3566
|
const { platforms } = readInstallState(installStatePath);
|
|
3278
3567
|
const installed = HOSTS.filter((host) => platforms[host] !== undefined);
|
|
3279
3568
|
if (installed.length === 0) {
|
|
@@ -3366,22 +3655,22 @@ function applyHost(input) {
|
|
|
3366
3655
|
}
|
|
3367
3656
|
function wiringArtifact(host, targetHome, hostRoot) {
|
|
3368
3657
|
const root = resolveHostRoot(host, targetHome, hostRoot);
|
|
3369
|
-
const contractPath =
|
|
3658
|
+
const contractPath = path15.join(root, CONTRACT_FILENAME);
|
|
3370
3659
|
switch (host) {
|
|
3371
3660
|
case "claude":
|
|
3372
|
-
return { file:
|
|
3661
|
+
return { file: path15.join(root, "CLAUDE.md"), token: `@${CONTRACT_FILENAME}` };
|
|
3373
3662
|
case "codex":
|
|
3374
3663
|
case "cursor":
|
|
3375
|
-
return { file:
|
|
3664
|
+
return { file: path15.join(root, "AGENTS.md"), token: contractPath };
|
|
3376
3665
|
case "opencode":
|
|
3377
3666
|
return { file: openCodeConfigPath(root), token: `"${contractPath}"` };
|
|
3378
3667
|
}
|
|
3379
3668
|
}
|
|
3380
3669
|
function openCodeConfigPath(root) {
|
|
3381
|
-
const json =
|
|
3382
|
-
if (
|
|
3670
|
+
const json = path15.join(root, "opencode.json");
|
|
3671
|
+
if (fs11.existsSync(json))
|
|
3383
3672
|
return json;
|
|
3384
|
-
return
|
|
3673
|
+
return path15.join(root, "opencode.jsonc");
|
|
3385
3674
|
}
|
|
3386
3675
|
function isWired(host, targetHome, hostRoot) {
|
|
3387
3676
|
const artifact = wiringArtifact(host, targetHome, hostRoot);
|
|
@@ -3394,7 +3683,7 @@ function notWiredReason(host, targetHome, hostRoot) {
|
|
|
3394
3683
|
}
|
|
3395
3684
|
function readFileOrNull(filePath) {
|
|
3396
3685
|
try {
|
|
3397
|
-
return
|
|
3686
|
+
return fs11.readFileSync(filePath, "utf-8");
|
|
3398
3687
|
} catch {
|
|
3399
3688
|
return null;
|
|
3400
3689
|
}
|
|
@@ -3440,11 +3729,11 @@ function formatBootstrapReport(report) {
|
|
|
3440
3729
|
init_config_loader();
|
|
3441
3730
|
// src/config-cli.ts
|
|
3442
3731
|
init_inference_providers();
|
|
3443
|
-
import { promises as
|
|
3444
|
-
import
|
|
3445
|
-
import
|
|
3732
|
+
import { promises as fs12 } from "fs";
|
|
3733
|
+
import path16 from "path";
|
|
3734
|
+
import os9 from "os";
|
|
3446
3735
|
import { fileURLToPath } from "url";
|
|
3447
|
-
var __dirname2 =
|
|
3736
|
+
var __dirname2 = path16.dirname(fileURLToPath(import.meta.url));
|
|
3448
3737
|
var WRITABLE_PROVIDERS = ["ollama", "lmstudio", "mistral", "openai", "google", "cohere"];
|
|
3449
3738
|
var GENERATOR_MARKER = "scripts/generate-subagent-artifacts.ts";
|
|
3450
3739
|
var GENERATOR_MARKER_MAX_LEVELS = 6;
|
|
@@ -3487,6 +3776,14 @@ Commands:
|
|
|
3487
3776
|
profile set <name> [--host <h>] [--dry-run]
|
|
3488
3777
|
Switch installed agents to a profile (restart required after)
|
|
3489
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
|
+
|
|
3490
3787
|
bootstrap list List every startup-contract rule: state, default, description
|
|
3491
3788
|
bootstrap show Same as 'bootstrap list'
|
|
3492
3789
|
bootstrap enable <rule-id> [--target <dir> --yes] [--dry-run]
|
|
@@ -3504,6 +3801,8 @@ Examples:
|
|
|
3504
3801
|
massa-ai-config set embedding.dimensions 1024
|
|
3505
3802
|
massa-ai-config agents install --user
|
|
3506
3803
|
massa-ai-config profile set work --dry-run
|
|
3804
|
+
massa-ai-config doctor
|
|
3805
|
+
massa-ai-config doctor --fix
|
|
3507
3806
|
massa-ai-config bootstrap list
|
|
3508
3807
|
massa-ai-config bootstrap disable caveman
|
|
3509
3808
|
`);
|
|
@@ -3547,6 +3846,30 @@ function formatSwitchReport(report) {
|
|
|
3547
3846
|
A host session restart is required for the change to take effect.`);
|
|
3548
3847
|
}
|
|
3549
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
|
+
}
|
|
3550
3873
|
async function runCli(argv) {
|
|
3551
3874
|
const args = argv;
|
|
3552
3875
|
const command = args[0];
|
|
@@ -3722,18 +4045,18 @@ Using defaults:`);
|
|
|
3722
4045
|
return 1;
|
|
3723
4046
|
}
|
|
3724
4047
|
const scope = typeof options.project === "boolean" ? "project" : "user";
|
|
3725
|
-
const agentsDir = scope === "project" ?
|
|
3726
|
-
const sourceAgentsDir =
|
|
4048
|
+
const agentsDir = scope === "project" ? path16.join(process.cwd(), ".opencode/agents") : path16.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path16.join(os9.homedir(), ".config"), "opencode", "agents");
|
|
4049
|
+
const sourceAgentsDir = path16.resolve(__dirname2, "..", "agents");
|
|
3727
4050
|
if (subcommand === "install") {
|
|
3728
|
-
await
|
|
4051
|
+
await fs12.mkdir(agentsDir, { recursive: true });
|
|
3729
4052
|
let count = 0;
|
|
3730
|
-
const entries = await
|
|
4053
|
+
const entries = await fs12.readdir(sourceAgentsDir);
|
|
3731
4054
|
for (const entry of entries) {
|
|
3732
4055
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
3733
4056
|
continue;
|
|
3734
|
-
const src =
|
|
3735
|
-
const dest =
|
|
3736
|
-
await
|
|
4057
|
+
const src = path16.join(sourceAgentsDir, entry);
|
|
4058
|
+
const dest = path16.join(agentsDir, entry);
|
|
4059
|
+
await fs12.copyFile(src, dest);
|
|
3737
4060
|
count++;
|
|
3738
4061
|
}
|
|
3739
4062
|
console.log(`+ ${count} subagent specialists (generated from skills/agents/*/SKILL.md)`);
|
|
@@ -3741,14 +4064,14 @@ Using defaults:`);
|
|
|
3741
4064
|
} else {
|
|
3742
4065
|
let removed = 0;
|
|
3743
4066
|
try {
|
|
3744
|
-
const entries = await
|
|
4067
|
+
const entries = await fs12.readdir(agentsDir);
|
|
3745
4068
|
for (const entry of entries) {
|
|
3746
4069
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
3747
4070
|
continue;
|
|
3748
|
-
const filePath =
|
|
3749
|
-
const content = await
|
|
4071
|
+
const filePath = path16.join(agentsDir, entry);
|
|
4072
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
3750
4073
|
if (content.includes("massa-ai-owned: true")) {
|
|
3751
|
-
await
|
|
4074
|
+
await fs12.unlink(filePath);
|
|
3752
4075
|
removed++;
|
|
3753
4076
|
}
|
|
3754
4077
|
}
|
|
@@ -3801,6 +4124,39 @@ Using defaults:`);
|
|
|
3801
4124
|
console.error("Usage: massa-ai-config profile <list|show|set> ...");
|
|
3802
4125
|
return 1;
|
|
3803
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
|
+
}
|
|
3804
4160
|
case "bootstrap": {
|
|
3805
4161
|
const subcommand = args[1];
|
|
3806
4162
|
if (subcommand === "list" || subcommand === "show") {
|
|
@@ -3825,9 +4181,9 @@ Using defaults:`);
|
|
|
3825
4181
|
return 1;
|
|
3826
4182
|
}
|
|
3827
4183
|
const targetOpt = typeof options.target === "string" ? options.target : undefined;
|
|
3828
|
-
const targetHome = targetOpt === undefined ?
|
|
3829
|
-
if (targetHome !==
|
|
3830
|
-
console.error(`Error: --target ${targetHome} is not your home (${
|
|
4184
|
+
const targetHome = targetOpt === undefined ? os9.homedir() : path16.resolve(targetOpt);
|
|
4185
|
+
if (targetHome !== os9.homedir() && options.yes !== true) {
|
|
4186
|
+
console.error(`Error: --target ${targetHome} is not your home (${os9.homedir()}) \u2014 pass --yes to confirm writing there`);
|
|
3831
4187
|
return 1;
|
|
3832
4188
|
}
|
|
3833
4189
|
const dryRun = options["dry-run"] === true;
|
|
@@ -3845,7 +4201,7 @@ Using defaults:`);
|
|
|
3845
4201
|
const report = applyBootstrapState({
|
|
3846
4202
|
targetHome,
|
|
3847
4203
|
dryRun,
|
|
3848
|
-
sourcePath: repoRoot === null ? undefined :
|
|
4204
|
+
sourcePath: repoRoot === null ? undefined : path16.join(repoRoot, "skills", "AGENTS.md")
|
|
3849
4205
|
});
|
|
3850
4206
|
console.log(formatBootstrapReport(report));
|
|
3851
4207
|
return bootstrapReportSucceeded(report) ? 0 : 1;
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
28
|
-
"@massa-ai/shared": "^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",
|