@massa-ai/cursor-plugin 1.59.0 → 1.60.1
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/hooks/massa-ai-hook
CHANGED
|
@@ -187,6 +187,93 @@ export function resolveHookApiKey(): string {
|
|
|
187
187
|
return "";
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// ── Session-start drift line (agent-runtime-drift T06) ─────────────────────
|
|
191
|
+
|
|
192
|
+
/** Same XDG resolution as getHookConfigPath — duplicated for the same
|
|
193
|
+
* dependency-freeze reason, pinned by tests on both sides. */
|
|
194
|
+
export function getInstallStatePath(): string {
|
|
195
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
196
|
+
const base = xdg && xdg.trim() ? xdg : path.join(homedir(), ".config");
|
|
197
|
+
return path.join(base, "massa-ai", "install-state.json");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function readJsonAt(filePath: string): Record<string, unknown> | null {
|
|
201
|
+
try {
|
|
202
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
203
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
204
|
+
? (parsed as Record<string, unknown>)
|
|
205
|
+
: null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Pinned one-key read (`^model:`) — deliberately NOT the shared frontmatter
|
|
213
|
+
* parser: this binary's dependency freeze (see getHookConfigPath) forbids
|
|
214
|
+
* importing @massa-ai/shared. The cross-side test pins this regex against the
|
|
215
|
+
* shared parser's output (spec INV B3), so the writer and this reader cannot
|
|
216
|
+
* silently disagree about what `model:` means.
|
|
217
|
+
*/
|
|
218
|
+
function readModelLine(filePath: string): string | null {
|
|
219
|
+
try {
|
|
220
|
+
const match = /^model:\s*(.+)\r?$/m.exec(readFileSync(filePath, "utf8"));
|
|
221
|
+
const value = match?.[1]?.trim();
|
|
222
|
+
return value ? value.replace(/^["']|["']$/g, "") : null;
|
|
223
|
+
} catch {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The ≤2-line session-start drift report (agent-runtime-drift): version
|
|
230
|
+
* drift (live bundle vs recorded state), agent drift (active investigator
|
|
231
|
+
* model vs the recorded profile's variant), and the host env override that
|
|
232
|
+
* nullifies every per-agent model at runtime. Null when every recording
|
|
233
|
+
* agrees — silent when healthy. Read-only (INV5); every failure degrades to
|
|
234
|
+
* fewer lines, never a throw. The fix path is the existing profile switch,
|
|
235
|
+
* never the hook.
|
|
236
|
+
*/
|
|
237
|
+
export function buildSessionStartDoctorLine(
|
|
238
|
+
pluginRoot: string,
|
|
239
|
+
env: Readonly<Record<string, string | undefined>>,
|
|
240
|
+
): string | null {
|
|
241
|
+
const lines: string[] = [];
|
|
242
|
+
|
|
243
|
+
const sourceVersion = readJsonAt(path.join(pluginRoot, ".claude-plugin", "plugin.json"))?.version;
|
|
244
|
+
const state = readJsonAt(getInstallStatePath());
|
|
245
|
+
const platforms = state?.platforms as Record<string, Record<string, unknown>> | undefined;
|
|
246
|
+
const claude = platforms?.claude;
|
|
247
|
+
const stateVersion = claude?.plugin?.version;
|
|
248
|
+
if (typeof sourceVersion === "string" && typeof stateVersion === "string" && sourceVersion !== stateVersion) {
|
|
249
|
+
lines.push(
|
|
250
|
+
`[massa-ai] version drift: live bundle ${sourceVersion} vs recorded ${stateVersion} — update the plugin or re-run the installer`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const profile = claude?.modelProfile?.profile;
|
|
255
|
+
if (typeof profile === "string" && profile) {
|
|
256
|
+
const activeModel = readModelLine(path.join(pluginRoot, "agents", "massa-ai-investigator.md"));
|
|
257
|
+
const variantModel = readModelLine(
|
|
258
|
+
path.join(pluginRoot, "agent-profiles", profile, "massa-ai-investigator.md"),
|
|
259
|
+
);
|
|
260
|
+
if (activeModel && variantModel && activeModel !== variantModel) {
|
|
261
|
+
lines.push(
|
|
262
|
+
`[massa-ai] agent drift: investigator ${activeModel} vs ${profile} variant ${variantModel} — re-run the profile switch`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const override = env.CLAUDE_CODE_SUBAGENT_MODEL;
|
|
268
|
+
if (typeof override === "string" && override.trim()) {
|
|
269
|
+
lines.push(
|
|
270
|
+
`[massa-ai] CLAUDE_CODE_SUBAGENT_MODEL=${override.trim()} overrides every per-agent model at runtime`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return lines.length ? lines.slice(0, 2).join("\n") : null;
|
|
275
|
+
}
|
|
276
|
+
|
|
190
277
|
// ── POST helper ─────────────────────────────────────────────────────────────
|
|
191
278
|
|
|
192
279
|
export function postObservation(
|
|
@@ -299,6 +386,17 @@ export async function main(stdinInput?: string): Promise<void> {
|
|
|
299
386
|
|
|
300
387
|
const projectId = resolveProjectId(sessionId, cwd);
|
|
301
388
|
|
|
389
|
+
// agent-runtime-drift (T06): session-start prints a bounded drift line when
|
|
390
|
+
// the recordings disagree (read-only, silent when healthy — INV5).
|
|
391
|
+
if (subcommand === "session-start") {
|
|
392
|
+
try {
|
|
393
|
+
const doctorLine = buildSessionStartDoctorLine(path.resolve(import.meta.dirname, ".."), process.env);
|
|
394
|
+
if (doctorLine) process.stdout.write(doctorLine + "\n");
|
|
395
|
+
} catch {
|
|
396
|
+
// silent-degrade: drift reporting must never block the agent
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
302
400
|
const baseUrl = process.env.MASSA_AI_API_BASE || "http://localhost:3333";
|
|
303
401
|
const hookUrl = `${baseUrl}/api/v1/hook`;
|
|
304
402
|
|
package/package.json
CHANGED
|
@@ -303,7 +303,7 @@ like every other dispatch.
|
|
|
303
303
|
```md
|
|
304
304
|
🤖 [Agent Started] Investigator — model opus, effort high. Scope: the four emitters.
|
|
305
305
|
🤖 [Agent Started] Designer — model/effort unknown (no installed agent file at
|
|
306
|
-
|
|
306
|
+
<liveRoot>/agents/massa-ai-designer.md). Dispatching anyway.
|
|
307
307
|
```
|
|
308
308
|
|
|
309
309
|
That second line is a measured case, not a hypothetical: on a machine with plugin
|
|
@@ -316,7 +316,7 @@ this table so the two cannot drift silently:
|
|
|
316
316
|
|
|
317
317
|
| Host | Installed agents directory | Glob | Model / effort keys |
|
|
318
318
|
| --- | --- | --- | --- |
|
|
319
|
-
| Claude — marketplace route | `<marketplaceRoot>/agents`
|
|
319
|
+
| Claude — marketplace route | `<marketplaceRoot>/agents`, where `<marketplaceRoot>` is `resolveClaudeMarketplaceInstall`'s live root: for a **directory-source** marketplace the host loads the plugin LIVE from the source bundle — e.g. `<repo>/apps/claude-plugin/agents`; for any other kind it is the *versioned* cache snapshot, e.g. `~/.claude/plugins/cache/massa-ai/massa-ai/1.48.0/agents` (a stale-able snapshot — never hardcode it; read `profile_list`'s `liveRoot`) | `massa-ai-*.md` | `model:` / `effort:` |
|
|
320
320
|
| Claude — file route | `~/.claude/agents` | `massa-ai-*.md` | `model:` / `effort:` |
|
|
321
321
|
| Codex | `~/.codex/agents` | `massa-ai-*.toml` | `model` / `model_reasoning_effort` |
|
|
322
322
|
| OpenCode | `~/.config/opencode/agents` | `massa-ai-*.md` | `model:` / `reasoningEffort:` |
|