Improve agent detail, issue creation, and approvals pages

Expand AgentDetail with heartbeat history and manual trigger controls.
Enhance NewIssueDialog with richer field options. Add agent connection
string retrieval API. Improve issue routes with parent chain resolution.
Clean up Approvals page layout. Update query keys and validators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-17 20:46:12 -06:00
parent 6dbbf1bbec
commit b95c05a242
10 changed files with 396 additions and 45 deletions

View File

@@ -247,6 +247,13 @@ export function agentRoutes(db: Db) {
res.json(agent);
});
router.get("/agents/:id/keys", async (req, res) => {
assertBoard(req);
const id = req.params.id as string;
const keys = await svc.listKeys(id);
res.json(keys);
});
router.post("/agents/:id/keys", validate(createAgentKeySchema), async (req, res) => {
assertBoard(req);
const id = req.params.id as string;
@@ -268,6 +275,17 @@ export function agentRoutes(db: Db) {
res.status(201).json(key);
});
router.delete("/agents/:id/keys/:keyId", async (req, res) => {
assertBoard(req);
const keyId = req.params.keyId as string;
const revoked = await svc.revokeKey(keyId);
if (!revoked) {
res.status(404).json({ error: "Key not found" });
return;
}
res.json({ ok: true });
});
router.post("/agents/:id/wakeup", validate(wakeAgentSchema), async (req, res) => {
const id = req.params.id as string;
const agent = await svc.getById(id);

View File

@@ -87,7 +87,8 @@ export function issueRoutes(db: Db) {
}
assertCompanyAccess(req, existing.companyId);
const issue = await svc.update(id, req.body);
const { comment: commentBody, ...updateFields } = req.body;
const issue = await svc.update(id, updateFields);
if (!issue) {
res.status(404).json({ error: "Issue not found" });
return;
@@ -102,9 +103,43 @@ export function issueRoutes(db: Db) {
action: "issue.updated",
entityType: "issue",
entityId: issue.id,
details: req.body,
details: updateFields,
});
let comment = null;
if (commentBody) {
comment = await svc.addComment(id, commentBody, {
agentId: actor.agentId ?? undefined,
userId: actor.actorType === "user" ? actor.actorId : undefined,
});
await logActivity(db, {
companyId: issue.companyId,
actorType: actor.actorType,
actorId: actor.actorId,
agentId: actor.agentId,
action: "issue.comment_added",
entityType: "issue",
entityId: issue.id,
details: { commentId: comment.id },
});
// @-mention wakeups
svc.findMentionedAgents(issue.companyId, commentBody).then((ids) => {
for (const mentionedId of ids) {
heartbeat.wakeup(mentionedId, {
source: "automation",
triggerDetail: "system",
reason: `Mentioned in comment on issue ${id}`,
payload: { issueId: id, commentId: comment!.id },
requestedByActorType: actor.actorType,
requestedByActorId: actor.actorId,
contextSnapshot: { issueId: id, commentId: comment!.id, source: "comment.mention" },
}).catch((err) => logger.warn({ err, agentId: mentionedId }, "failed to wake mentioned agent"));
}
}).catch((err) => logger.warn({ err, issueId: id }, "failed to resolve @-mentions"));
}
const assigneeChanged =
req.body.assigneeAgentId !== undefined && req.body.assigneeAgentId !== existing.assigneeAgentId;
if (assigneeChanged && issue.assigneeAgentId) {
@@ -121,7 +156,7 @@ export function issueRoutes(db: Db) {
.catch((err) => logger.warn({ err, issueId: issue.id }, "failed to wake assignee on issue update"));
}
res.json(issue);
res.json({ ...issue, comment });
});
router.delete("/issues/:id", async (req, res) => {