forked from farhoodlabs/paperclip
fix: harden heartbeat and adapter runtime workflows
This commit is contained in:
@@ -2441,15 +2441,14 @@ export function agentRoutes(db: Db) {
|
||||
}
|
||||
assertCompanyAccess(req, issue.companyId);
|
||||
|
||||
let run = issue.executionRunId ? await heartbeat.getRun(issue.executionRunId) : null;
|
||||
let run = issue.executionRunId ? await heartbeat.getRunIssueSummary(issue.executionRunId) : null;
|
||||
if (run && run.status !== "queued" && run.status !== "running") {
|
||||
run = null;
|
||||
}
|
||||
|
||||
if (!run && issue.assigneeAgentId && issue.status === "in_progress") {
|
||||
const candidateRun = await heartbeat.getActiveRunForAgent(issue.assigneeAgentId);
|
||||
const candidateContext = asRecord(candidateRun?.contextSnapshot);
|
||||
const candidateIssueId = asNonEmptyString(candidateContext?.issueId);
|
||||
const candidateRun = await heartbeat.getActiveRunIssueSummaryForAgent(issue.assigneeAgentId);
|
||||
const candidateIssueId = asNonEmptyString(candidateRun?.issueId);
|
||||
if (candidateRun && candidateIssueId === issue.id) {
|
||||
run = candidateRun;
|
||||
}
|
||||
@@ -2466,7 +2465,7 @@ export function agentRoutes(db: Db) {
|
||||
}
|
||||
|
||||
res.json({
|
||||
...redactCurrentUserValue(run, await getCurrentUserRedactionOptions()),
|
||||
...run,
|
||||
agentId: agent.id,
|
||||
agentName: agent.name,
|
||||
adapterType: agent.adapterType,
|
||||
|
||||
+31
-31
@@ -21,6 +21,26 @@ import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
||||
import { fetchAllQuotaWindows } from "../services/quota-windows.js";
|
||||
import { badRequest } from "../errors.js";
|
||||
|
||||
export function parseCostDateRange(query: Record<string, unknown>) {
|
||||
const fromRaw = query.from as string | undefined;
|
||||
const toRaw = query.to as string | undefined;
|
||||
const from = fromRaw ? new Date(fromRaw) : undefined;
|
||||
const to = toRaw ? new Date(toRaw) : undefined;
|
||||
if (from && isNaN(from.getTime())) throw badRequest("invalid 'from' date");
|
||||
if (to && isNaN(to.getTime())) throw badRequest("invalid 'to' date");
|
||||
return (from || to) ? { from, to } : undefined;
|
||||
}
|
||||
|
||||
export function parseCostLimit(query: Record<string, unknown>) {
|
||||
const raw = Array.isArray(query.limit) ? query.limit[0] : query.limit;
|
||||
if (raw == null || raw === "") return 100;
|
||||
const limit = typeof raw === "number" ? raw : Number.parseInt(String(raw), 10);
|
||||
if (!Number.isFinite(limit) || limit <= 0 || limit > 500) {
|
||||
throw badRequest("invalid 'limit' value");
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
export function costRoutes(db: Db) {
|
||||
const router = Router();
|
||||
const heartbeat = heartbeatService(db);
|
||||
@@ -92,30 +112,10 @@ export function costRoutes(db: Db) {
|
||||
res.status(201).json(event);
|
||||
});
|
||||
|
||||
function parseDateRange(query: Record<string, unknown>) {
|
||||
const fromRaw = query.from as string | undefined;
|
||||
const toRaw = query.to as string | undefined;
|
||||
const from = fromRaw ? new Date(fromRaw) : undefined;
|
||||
const to = toRaw ? new Date(toRaw) : undefined;
|
||||
if (from && isNaN(from.getTime())) throw badRequest("invalid 'from' date");
|
||||
if (to && isNaN(to.getTime())) throw badRequest("invalid 'to' date");
|
||||
return (from || to) ? { from, to } : undefined;
|
||||
}
|
||||
|
||||
function parseLimit(query: Record<string, unknown>) {
|
||||
const raw = Array.isArray(query.limit) ? query.limit[0] : query.limit;
|
||||
if (raw == null || raw === "") return 100;
|
||||
const limit = typeof raw === "number" ? raw : Number.parseInt(String(raw), 10);
|
||||
if (!Number.isFinite(limit) || limit <= 0 || limit > 500) {
|
||||
throw badRequest("invalid 'limit' value");
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
router.get("/companies/:companyId/costs/summary", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const summary = await costs.summary(companyId, range);
|
||||
res.json(summary);
|
||||
});
|
||||
@@ -123,7 +123,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/by-agent", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await costs.byAgent(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -131,7 +131,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/by-agent-model", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await costs.byAgentModel(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -139,7 +139,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/by-provider", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await costs.byProvider(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -147,7 +147,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/by-biller", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await costs.byBiller(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -155,7 +155,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/finance-summary", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const summary = await finance.summary(companyId, range);
|
||||
res.json(summary);
|
||||
});
|
||||
@@ -163,7 +163,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/finance-by-biller", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await finance.byBiller(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -171,7 +171,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/finance-by-kind", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await finance.byKind(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -179,8 +179,8 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/finance-events", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const limit = parseLimit(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const limit = parseCostLimit(req.query);
|
||||
const rows = await finance.list(companyId, range, limit);
|
||||
res.json(rows);
|
||||
});
|
||||
@@ -242,7 +242,7 @@ export function costRoutes(db: Db) {
|
||||
router.get("/companies/:companyId/costs/by-project", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
assertCompanyAccess(req, companyId);
|
||||
const range = parseDateRange(req.query);
|
||||
const range = parseCostDateRange(req.query);
|
||||
const rows = await costs.byProject(companyId, range);
|
||||
res.json(rows);
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ export function llmRoutes(db: Db) {
|
||||
"Notes:",
|
||||
"- Sensitive values are redacted in configuration read APIs.",
|
||||
"- New hires may be created in pending_approval state depending on company settings.",
|
||||
"- Timer heartbeats are opt-in for new hires. Leave runtimeConfig.heartbeat.enabled false unless the role truly needs scheduled work or the user explicitly asked for it.",
|
||||
"",
|
||||
];
|
||||
res.type("text/plain").send(lines.join("\n"));
|
||||
|
||||
Reference in New Issue
Block a user