feat(adapter): agent instructions file support and docs:dev script

Add instructionsFilePath config to Claude and Codex adapters, allowing
agents to load external instruction files appended to the system prompt.
Claude uses --append-system-prompt-file; Codex prepends file contents
to the prompt. Add docs:dev script for local Mintlify preview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-26 16:34:15 -06:00
parent 02dc46e782
commit a63e1fd2db
3 changed files with 35 additions and 2 deletions

View File

@@ -15,7 +15,8 @@
"db:migrate": "pnpm --filter @paperclip/db migrate", "db:migrate": "pnpm --filter @paperclip/db migrate",
"secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts", "secrets:migrate-inline-env": "tsx scripts/migrate-inline-env-secrets.ts",
"db:backup": "./scripts/backup-db.sh", "db:backup": "./scripts/backup-db.sh",
"paperclip": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts" "paperclip": "node cli/node_modules/tsx/dist/cli.mjs cli/src/index.ts",
"docs:dev": "cd docs && npx mintlify dev"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.7.3", "typescript": "^5.7.3",

View File

@@ -267,6 +267,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
const chrome = asBoolean(config.chrome, false); const chrome = asBoolean(config.chrome, false);
const maxTurns = asNumber(config.maxTurnsPerRun, 0); const maxTurns = asNumber(config.maxTurnsPerRun, 0);
const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, false); const dangerouslySkipPermissions = asBoolean(config.dangerouslySkipPermissions, false);
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
const instructionsFileDir = instructionsFilePath ? `${path.dirname(instructionsFilePath)}/` : "";
const runtimeConfig = await buildClaudeRuntimeConfig({ const runtimeConfig = await buildClaudeRuntimeConfig({
runId, runId,
@@ -321,6 +323,13 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
if (model) args.push("--model", model); if (model) args.push("--model", model);
if (effort) args.push("--effort", effort); if (effort) args.push("--effort", effort);
if (maxTurns > 0) args.push("--max-turns", String(maxTurns)); if (maxTurns > 0) args.push("--max-turns", String(maxTurns));
if (instructionsFilePath) {
args.push("--append-system-prompt-file", instructionsFilePath);
args.push(
"--append-system-prompt",
`The above agent instructions were loaded from ${instructionsFilePath}. Resolve any relative file references from ${instructionsFileDir}.`,
);
}
args.push("--add-dir", skillsDir); args.push("--add-dir", skillsDir);
if (extraArgs.length > 0) args.push(...extraArgs); if (extraArgs.length > 0) args.push(...extraArgs);
return args; return args;

View File

@@ -230,8 +230,30 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
`[paperclip] Codex session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`, `[paperclip] Codex session "${runtimeSessionId}" was saved for cwd "${runtimeSessionCwd}" and will not be resumed in "${cwd}".\n`,
); );
} }
const instructionsFilePath = asString(config.instructionsFilePath, "").trim();
let instructionsPrefix = "";
if (instructionsFilePath) {
try {
const instructionsContents = await fs.readFile(instructionsFilePath, "utf8");
const instructionsDir = `${path.dirname(instructionsFilePath)}/`;
instructionsPrefix =
`${instructionsContents}\n\n` +
`The above agent instructions were loaded from ${instructionsFilePath}. ` +
`Resolve any relative file references from ${instructionsDir}.\n\n`;
await onLog(
"stderr",
`[paperclip] Loaded agent instructions file: ${instructionsFilePath}\n`,
);
} catch (err) {
const reason = err instanceof Error ? err.message : String(err);
await onLog(
"stderr",
`[paperclip] Warning: could not read agent instructions file "${instructionsFilePath}": ${reason}\n`,
);
}
}
const template = sessionId ? promptTemplate : bootstrapTemplate; const template = sessionId ? promptTemplate : bootstrapTemplate;
const prompt = renderTemplate(template, { const renderedPrompt = renderTemplate(template, {
agentId: agent.id, agentId: agent.id,
companyId: agent.companyId, companyId: agent.companyId,
runId, runId,
@@ -240,6 +262,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
run: { id: runId, source: "on_demand" }, run: { id: runId, source: "on_demand" },
context, context,
}); });
const prompt = `${instructionsPrefix}${renderedPrompt}`;
const buildArgs = (resumeSessionId: string | null) => { const buildArgs = (resumeSessionId: string | null) => {
const args = ["exec", "--json"]; const args = ["exec", "--json"];