mirror of
https://github.com/vercel/vercel-plugin.git
synced 2026-09-14 15:39:47 +08:00
8e24d0694a
Add a chainTo frontmatter field to skills that triggers follow-up skill injection when PostToolUse file contents match regex patterns. Add upgradeToSkill/upgradeWhy fields to validation rules so validate errors can recommend loading a specific skill. Register posttooluse-bash-chain.mjs in hooks.json. Add ChainToRule interface to patterns.mts and skill-map-frontmatter.mts, with parseChainToRules() parser. Add duplicate-key detection to the YAML parser. Reset dedup claim dir and session file on clear/compact events in session-start-seen-skills so skills re-inject into fresh context windows. Add chainTo rules to: agent-browser-verify, agent-browser, ai-elements, ai-gateway, ai-generation-persistence, ai-sdk, auth, bootstrap, chat-sdk, cms, cron-jobs, deployments-cicd, email, env-vars, geist, investigation-mode, json-render, marketplace, micro, ncc, next-forge, nextjs, observability, payments, react-best-practices, routing-middleware, runtime-cache, satori, shadcn, sign-in-with-vercel, swr, turbopack, turborepo, v0-dev, vercel-agent, vercel-api, vercel-cli, vercel-firewall, vercel-flags, vercel-functions, vercel-queues, vercel-sandbox, vercel-storage, verification, workflow. Add upgradeToSkill to ai-elements and ai-sdk validate rules. Expand ai-sdk validate messages with Run Skill() hints. Update nextjs, vercel-storage, runtime-cache, workflow, turborepo skill bodies. Add new skills: geistdocs (Geist design system docs), zzz-test-meta-name-mask (test fixture). Add skills/_chain-audit.md chain coverage audit doc. Delete .claude-plugin/marketplace.json, .claude-plugin/plugin.json (deprecated), skills/edge-runtime/SKILL.md (consolidated into vercel-functions). Add tests: posttooluse-chain.test.ts (4699 lines, chain injection e2e), ai-sdk-companion.test.ts (181 lines). Expand build-skill-map.test.ts (+335 lines), validate-rules.test.ts (+936 lines), session-start-seen-skills.test.ts (+74 lines), skill-map-frontmatter.test.ts (+50 lines), verification-skill.test.ts (+20 lines). Update build-manifest.ts to emit chainTo rules and upgradeToSkill fields. Rebuild generated/skill-manifest.json, generated/skill-catalog.md, generated/build-from-skills.manifest.json. Rebuild all compiled hooks/*.mjs. Update CLAUDE.md lexical prompt default to on. Update vercel.md ecosystem graph, docs, and cli-reference.
115 lines
3.7 KiB
JavaScript
115 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
// hooks/src/pretooluse-subagent-spawn-observe.mts
|
|
import { readFileSync } from "fs";
|
|
import { resolve } from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { appendPendingLaunch } from "./subagent-state.mjs";
|
|
import { createLogger, logCaughtError } from "./logger.mjs";
|
|
var log = createLogger();
|
|
var EMPTY_OUTPUT = "{}";
|
|
function isRecord(value) {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
function resolveSessionId(input, env) {
|
|
if (typeof input.session_id === "string" && input.session_id.trim() !== "") {
|
|
return input.session_id;
|
|
}
|
|
if (typeof env.SESSION_ID === "string" && env.SESSION_ID.trim() !== "") {
|
|
return env.SESSION_ID;
|
|
}
|
|
return null;
|
|
}
|
|
function parseInput(raw, env = process.env) {
|
|
const trimmed = (raw || "").trim();
|
|
if (!trimmed) {
|
|
log.debug("pretooluse-subagent-spawn-observe-skip", { reason: "stdin_empty" });
|
|
return null;
|
|
}
|
|
let input;
|
|
try {
|
|
const parsed = JSON.parse(trimmed);
|
|
if (!isRecord(parsed)) {
|
|
log.debug("pretooluse-subagent-spawn-observe-skip", { reason: "stdin_not_object" });
|
|
return null;
|
|
}
|
|
input = parsed;
|
|
} catch {
|
|
log.debug("pretooluse-subagent-spawn-observe-skip", { reason: "stdin_parse_fail" });
|
|
return null;
|
|
}
|
|
const toolName = typeof input.tool_name === "string" ? input.tool_name : "";
|
|
if (toolName !== "Agent") {
|
|
log.debug("pretooluse-subagent-spawn-observe-skip", { reason: "unsupported_tool", toolName });
|
|
return null;
|
|
}
|
|
const sessionId = resolveSessionId(input, env);
|
|
if (!sessionId) {
|
|
log.debug("pretooluse-subagent-spawn-observe-skip", { reason: "missing_session_id" });
|
|
return null;
|
|
}
|
|
const toolInput = isRecord(input.tool_input) ? input.tool_input : {};
|
|
return { sessionId, toolInput };
|
|
}
|
|
function buildPendingLaunchRecord(toolInput, createdAt) {
|
|
const resume = typeof toolInput.resume === "string" ? toolInput.resume : void 0;
|
|
const name = typeof toolInput.name === "string" ? toolInput.name : void 0;
|
|
const pendingLaunch = {
|
|
description: typeof toolInput.description === "string" ? toolInput.description : "",
|
|
prompt: typeof toolInput.prompt === "string" ? toolInput.prompt : "",
|
|
subagent_type: typeof toolInput.subagent_type === "string" ? toolInput.subagent_type : "",
|
|
createdAt,
|
|
...resume !== void 0 ? { resume } : {},
|
|
...name !== void 0 ? { name } : {}
|
|
};
|
|
return pendingLaunch;
|
|
}
|
|
function writePendingLaunchRecord(sessionId, toolInput) {
|
|
const createdAt = Date.now();
|
|
const payload = buildPendingLaunchRecord(toolInput, createdAt);
|
|
appendPendingLaunch(sessionId, payload);
|
|
log.debug("pretooluse-subagent-spawn-observe-recorded", {
|
|
sessionId,
|
|
subagentType: typeof payload.subagent_type === "string" ? payload.subagent_type : null,
|
|
name: typeof payload.name === "string" ? payload.name : null
|
|
});
|
|
return sessionId;
|
|
}
|
|
function run(rawInput) {
|
|
let raw = rawInput;
|
|
if (raw === void 0) {
|
|
try {
|
|
raw = readFileSync(0, "utf-8");
|
|
} catch {
|
|
return EMPTY_OUTPUT;
|
|
}
|
|
}
|
|
const parsed = parseInput(raw);
|
|
if (!parsed) {
|
|
return EMPTY_OUTPUT;
|
|
}
|
|
try {
|
|
writePendingLaunchRecord(parsed.sessionId, parsed.toolInput);
|
|
} catch (error) {
|
|
logCaughtError(log, "pretooluse-subagent-spawn-observe-write-failed", error, {
|
|
attempted: "write_pending_launch_record",
|
|
sessionId: parsed.sessionId,
|
|
state: "launch_observation_failed"
|
|
});
|
|
}
|
|
return EMPTY_OUTPUT;
|
|
}
|
|
function isMainModule() {
|
|
const entrypoint = fileURLToPath(import.meta.url);
|
|
return process.argv[1] ? resolve(process.argv[1]) === entrypoint : false;
|
|
}
|
|
if (isMainModule()) {
|
|
process.stdout.write(run());
|
|
}
|
|
export {
|
|
buildPendingLaunchRecord,
|
|
parseInput,
|
|
run,
|
|
writePendingLaunchRecord
|
|
};
|