Server: migration prompts, structured logging, heartbeat reaping, and issue-run tracking
Replace auto-migrate-if-empty with interactive migration flow that inspects pending migrations and prompts before applying. Add pino-pretty for structured console + file logging. Add reapOrphanedRuns to clean up stuck heartbeat runs on startup and periodically. Track runId through auth middleware, activity logs, and all mutation routes. Add issue-run cross-reference queries, live-run and active-run endpoints for issues, issue identifier lookup, reopen-via-comment flow, and done/cancelled -> todo status transitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,11 @@ export function actorMiddleware(db: Db): RequestHandler {
|
||||
return async (req, _res, next) => {
|
||||
req.actor = { type: "board", userId: "board" };
|
||||
|
||||
const runIdHeader = req.header("x-paperclip-run-id");
|
||||
|
||||
const authHeader = req.header("authorization");
|
||||
if (!authHeader?.toLowerCase().startsWith("bearer ")) {
|
||||
if (runIdHeader) req.actor.runId = runIdHeader;
|
||||
next();
|
||||
return;
|
||||
}
|
||||
@@ -60,6 +63,7 @@ export function actorMiddleware(db: Db): RequestHandler {
|
||||
agentId: claims.sub,
|
||||
companyId: claims.company_id,
|
||||
keyId: undefined,
|
||||
runId: runIdHeader || undefined,
|
||||
};
|
||||
next();
|
||||
return;
|
||||
@@ -75,6 +79,7 @@ export function actorMiddleware(db: Db): RequestHandler {
|
||||
agentId: key.agentId,
|
||||
companyId: key.companyId,
|
||||
keyId: key.id,
|
||||
runId: runIdHeader || undefined,
|
||||
};
|
||||
|
||||
next();
|
||||
|
||||
@@ -1,5 +1,41 @@
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import pino from "pino";
|
||||
import { pinoHttp } from "pino-http";
|
||||
|
||||
export const logger = pino();
|
||||
export const httpLogger = pinoHttp({ logger });
|
||||
const logDir = path.resolve(process.cwd(), ".paperclip", "logs");
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
|
||||
const logFile = path.join(logDir, "server.log");
|
||||
|
||||
const sharedOpts = {
|
||||
translateTime: "HH:MM:ss",
|
||||
ignore: "pid,hostname",
|
||||
};
|
||||
|
||||
export const logger = pino({
|
||||
level: "debug",
|
||||
}, pino.transport({
|
||||
targets: [
|
||||
{
|
||||
target: "pino-pretty",
|
||||
options: { ...sharedOpts, ignore: "pid,hostname,req,res", hideObject: true, colorize: true, destination: 1 },
|
||||
level: "info",
|
||||
},
|
||||
{
|
||||
target: "pino-pretty",
|
||||
options: { ...sharedOpts, colorize: false, destination: logFile, mkdir: true },
|
||||
level: "debug",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
export const httpLogger = pinoHttp({
|
||||
logger,
|
||||
customSuccessMessage(req, res) {
|
||||
return `${req.method} ${req.url} ${res.statusCode}`;
|
||||
},
|
||||
customErrorMessage(req, res) {
|
||||
return `${req.method} ${req.url} ${res.statusCode}`;
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user