feat: stamp responsible users on activity logs (#9731)

## Thinking Path

> - Paperclip is the control plane people use to manage AI-agent
companies and their work
> - The activity log is the generic audit spine for mutations across the
control plane
> - Activity rows identify agents and runs, but they do not persist the
responsible human upstream
> - Relying only on run joins loses attribution after run pruning and
misses agent API-key actions outside a run
> - This pull request resolves responsible-user attribution when each
activity row is written and stores it directly
> - The benefit is durable, queryable agent audit feeds without
rewriting historical provenance

## Linked Issues or Issue Description

### Problem or motivation

Agent activity records do not persist the responsible user, so
attribution can disappear when runs are pruned and no-run API-key
mutations cannot be attributed correctly.

### Proposed solution

Resolve attribution for each new activity row from the run, related
issue, active agent API key, or company default, in that order, and
persist the result directly.

### Alternatives considered

Read-time joins alone were rejected because pruned runs lose durable
attribution and out-of-run agent-key actions have no run to join.
Historical backfill was rejected because it would invent provenance.

### Roadmap alignment

This strengthens the durable audit-trail direction described in
`ROADMAP.md` without adding a new product surface.

## What Changed

- Added nullable `activity_log.responsible_user_id` plus
company/agent/time and company/responsible-user/time indexes.
- Added an idempotent forward-only migration with no historical
backfill.
- Added centralized write-time resolution: heartbeat run → issue
attribution → active agent API key → company default.
- Propagated authenticated API-key IDs through existing request-backed
`logActivity()` calls.
- Added unit coverage for every fallback and an embedded-Postgres
assertion for the no-run API-key stamping path.

## Verification

- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/activity-log-responsible-user.test.ts
src/__tests__/authz-company-access.test.ts`
- Initial focused verification: 25 tests passed; migration safety
passed.
- Follow-up regression verification: 54 focused
attribution/company-skill/environment/issue-tree/tool-gateway tests
passed; server typecheck passed.
- GitHub: full build, typecheck, server shards, serialized suites, e2e,
security, and policy checks passed.

## Risks

- Adding two indexes to an existing large table can hold a write lock
while the transactional migration runs. The migration safety
suppressions document why `CONCURRENTLY` is unavailable under the
current Drizzle migration runner.
- Historical rows remain nullable by design; this avoids inventing
provenance and keeps the migration forward-only.
- API-key attribution requires request-backed activity call sites to
pass the authenticated key ID; this PR mechanically updates the existing
actor-based activity calls and covers the no-run path with integration
testing.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex, `gpt-5.4`; context-window size was not exposed by the
runtime. Medium reasoning with repository editing, terminal execution,
and test execution capabilities was used.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta 2026-07-16 20:54:55 -05:00 committed by GitHub
parent 53f09cb818
commit b07b2994cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 526 additions and 2 deletions

View File

@ -0,0 +1,10 @@
ALTER TABLE "activity_log"
ADD COLUMN IF NOT EXISTS "responsible_user_id" text;
-- paperclip:migration-safety-ignore large-create-index-not-concurrently: Drizzle migrations run transactionally, so CONCURRENTLY is unavailable because this forward-only index is required for the new agent audit feed.
CREATE INDEX IF NOT EXISTS "activity_log_company_agent_created_idx"
ON "activity_log" USING btree ("company_id", "agent_id", "created_at");
-- paperclip:migration-safety-ignore large-create-index-not-concurrently: Drizzle migrations run transactionally, so CONCURRENTLY is unavailable because this forward-only index is required for the new responsible-user audit feed.
CREATE INDEX IF NOT EXISTS "activity_log_company_responsible_user_created_idx"
ON "activity_log" USING btree ("company_id", "responsible_user_id", "created_at");

View File

@ -1226,6 +1226,13 @@
"when": 1784211956161,
"tag": "0176_issue_create_idempotency_key_expiry",
"breakpoints": true
},
{
"idx": 177,
"version": "7",
"when": 1784241826832,
"tag": "0177_activity_log_responsible_user",
"breakpoints": true
}
]
}

View File

@ -15,11 +15,22 @@ export const activityLog = pgTable(
entityId: text("entity_id").notNull(),
agentId: uuid("agent_id").references(() => agents.id),
runId: uuid("run_id").references(() => heartbeatRuns.id),
responsibleUserId: text("responsible_user_id"),
details: jsonb("details").$type<Record<string, unknown>>(),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
companyCreatedIdx: index("activity_log_company_created_idx").on(table.companyId, table.createdAt),
companyAgentCreatedIdx: index("activity_log_company_agent_created_idx").on(
table.companyId,
table.agentId,
table.createdAt,
),
companyResponsibleUserCreatedIdx: index("activity_log_company_responsible_user_created_idx").on(
table.companyId,
table.responsibleUserId,
table.createdAt,
),
runIdIdx: index("activity_log_run_id_idx").on(table.runId),
entityIdx: index("activity_log_entity_type_id_idx").on(table.entityType, table.entityId),
}),

View File

@ -0,0 +1,234 @@
import { randomUUID } from "node:crypto";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { eq } from "drizzle-orm";
import {
activityLog,
agentApiKeys,
agents,
companies,
createDb,
heartbeatRuns,
issues,
type Db,
} from "@paperclipai/db";
import {
logActivity,
resolveResponsibleUserIdForActivity,
type LogActivityInput,
} from "../services/activity-log.js";
import {
getEmbeddedPostgresTestSupport,
startEmbeddedPostgresTestDatabase,
} from "./helpers/embedded-postgres.js";
type TableRows = Map<unknown, Array<Record<string, unknown>>>;
const companyId = "00000000-0000-4000-8000-000000000001";
const agentId = "00000000-0000-4000-8000-000000000002";
const issueId = "00000000-0000-4000-8000-000000000003";
const runId = "00000000-0000-4000-8000-000000000004";
const missingRunId = "00000000-0000-4000-8000-000000000005";
const agentApiKeyId = "00000000-0000-4000-8000-000000000006";
const missingAgentApiKeyId = "00000000-0000-4000-8000-000000000007";
function createReader(rowsByTable: TableRows) {
return {
select: () => ({
from: (table: unknown) => ({
where: (condition: unknown) => {
expect(condition).toBeDefined();
return Promise.resolve(rowsByTable.get(table) ?? []);
},
}),
}),
} as unknown as Db;
}
function activityInput(overrides: Partial<LogActivityInput> = {}): LogActivityInput {
return {
companyId,
actorType: "agent",
actorId: agentId,
action: "issue.updated",
entityType: "issue",
entityId: issueId,
agentId,
...overrides,
};
}
describe("resolveResponsibleUserIdForActivity", () => {
it("attributes user actions directly without database lookups", async () => {
const db = {
select: () => {
throw new Error("user attribution should not query the database");
},
} as unknown as Db;
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
actorType: "user",
actorId: "user-1",
entityType: "company",
entityId: companyId,
}))).resolves.toBe("user-1");
});
it("prefers the heartbeat run responsible user", async () => {
const db = createReader(new Map([
[heartbeatRuns, [{ responsibleUserId: "run-user" }]],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
runId,
agentApiKeyId,
}))).resolves.toBe("run-user");
});
it("falls back to issue attribution when the run is unavailable", async () => {
const db = createReader(new Map([
[heartbeatRuns, []],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: "creator-user" }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
runId: missingRunId,
agentApiKeyId,
}))).resolves.toBe("issue-user");
});
it("uses explicit issue context for non-issue activity", async () => {
const db = createReader(new Map([
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
entityType: "heartbeat_run",
entityId: runId,
issueId,
}))).resolves.toBe("issue-user");
});
it("uses the active agent API key responsible user for no-run actions", async () => {
const db = createReader(new Map([
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
entityType: "agent",
entityId: agentId,
agentApiKeyId,
}))).resolves.toBe("key-user");
});
it("falls back to the company default responsible user", async () => {
const db = createReader(new Map([
[agentApiKeys, []],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
entityType: "company",
entityId: companyId,
agentApiKeyId: missingAgentApiKeyId,
}))).resolves.toBe("default-user");
});
it("uses issue creator attribution when responsibleUserId is absent", async () => {
const db = createReader(new Map([
[issues, [{ responsibleUserId: null, createdByUserId: "creator-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput())).resolves.toBe("creator-user");
});
it("ignores malformed UUID-backed identifiers", async () => {
const db = createReader(new Map([
[heartbeatRuns, [{ responsibleUserId: "run-user" }]],
[issues, [{ responsibleUserId: "issue-user", createdByUserId: null }]],
[agentApiKeys, [{ responsibleUserId: "key-user" }]],
[companies, [{ defaultResponsibleUserId: "default-user" }]],
]));
await expect(resolveResponsibleUserIdForActivity(db, activityInput({
runId: "not-a-run-uuid",
entityId: "not-an-issue-uuid",
agentApiKeyId: "not-a-key-uuid",
details: { issueId },
}))).resolves.toBe("default-user");
});
});
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe : describe.skip;
describeEmbeddedPostgres("logActivity responsible-user stamping", () => {
let db!: ReturnType<typeof createDb>;
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
beforeAll(async () => {
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-activity-responsible-user-");
db = createDb(tempDb.connectionString);
}, 20_000);
afterAll(async () => {
await tempDb?.cleanup();
});
it("persists API-key attribution for an out-of-run action", async () => {
const companyId = randomUUID();
const agentId = randomUUID();
const agentApiKeyId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
defaultResponsibleUserId: "default-user",
requireBoardApprovalForNewAgents: false,
});
await db.insert(agents).values({
id: agentId,
companyId,
name: "CodexCoder",
role: "engineer",
status: "running",
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
});
await db.insert(agentApiKeys).values({
id: agentApiKeyId,
companyId,
agentId,
name: "test",
keyHash: `hash-${agentApiKeyId}`,
responsibleUserId: "key-user",
});
await logActivity(db, activityInput({
companyId,
actorId: agentId,
agentId,
entityType: "agent",
entityId: agentId,
agentApiKeyId,
}));
const row = await db
.select({ responsibleUserId: activityLog.responsibleUserId })
.from(activityLog)
.where(eq(activityLog.companyId, companyId))
.then((rows) => rows[0]);
expect(row?.responsibleUserId).toBe("key-user");
});
});

View File

@ -1948,6 +1948,7 @@ export function agentRoutes(
entityId: updated.id,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
details: {
adapterType: updated.adapterType,
desiredSkills,
@ -2275,6 +2276,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.config_rolled_back",
entityType: "agent",
entityId: updated.id,
@ -2477,6 +2479,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.hire_created",
entityType: "agent",
entityId: agent.id,
@ -2507,6 +2510,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "approval.created",
entityType: "approval",
entityId: approval.id,
@ -2600,6 +2604,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.created",
entityType: "agent",
entityId: agent.id,
@ -2681,6 +2686,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.permissions_updated",
entityType: "agent",
entityId: agent.id,
@ -2756,6 +2762,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.instructions_path_updated",
entityType: "agent",
entityId: agent.id,
@ -2813,6 +2820,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.instructions_bundle_updated",
entityType: "agent",
entityId: existing.id,
@ -2875,6 +2883,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.instructions_file_updated",
entityType: "agent",
entityId: existing.id,
@ -2908,6 +2917,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.instructions_file_deleted",
entityType: "agent",
entityId: existing.id,
@ -3063,6 +3073,7 @@ export function agentRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "agent.updated",
entityType: "agent",
entityId: agent.id,

View File

@ -183,6 +183,7 @@ export function assetRoutes(db: Db, storage: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "asset.created",
entityType: "asset",
entityId: asset.id,
@ -281,6 +282,7 @@ export function assetRoutes(db: Db, storage: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "asset.created",
entityType: "asset",
entityId: asset.id,

View File

@ -199,6 +199,7 @@ export function getActorInfo(req: Request): (
actorId: string;
agentId: string | null;
runId: string | null;
agentApiKeyId: string | null;
actorSource: "agent_key" | "agent_jwt";
}
| {
@ -207,6 +208,7 @@ export function getActorInfo(req: Request): (
sessionId: string | null;
agentId: null;
runId: string | null;
agentApiKeyId: null;
actorSource: "local_implicit" | "session" | "board_key" | "cloud_tenant";
}
) {
@ -218,6 +220,7 @@ export function getActorInfo(req: Request): (
actorId: req.actor.agentId ?? "unknown-agent",
agentId: req.actor.agentId ?? null,
runId: req.actor.runId ?? null,
agentApiKeyId: req.actor.keyId ?? null,
actorSource,
};
}
@ -235,6 +238,7 @@ export function getActorInfo(req: Request): (
sessionId: req.actor.sessionId ?? null,
agentId: null,
runId: req.actor.runId ?? null,
agentApiKeyId: null,
actorSource,
};
}

View File

@ -837,6 +837,7 @@ export function caseRoutes(db: Db, storage: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "case.document_annotation_thread_created",
entityType: "case",
entityId: caseRow.id,
@ -875,6 +876,7 @@ export function caseRoutes(db: Db, storage: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "case.document_annotation_comment_added",
entityType: "case",
entityId: caseRow.id,
@ -911,6 +913,7 @@ export function caseRoutes(db: Db, storage: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: thread.status === "resolved"
? "case.document_annotation_thread_resolved"
: "case.document_annotation_thread_reopened",

View File

@ -358,6 +358,7 @@ export function companyRoutes(db: Db, storage?: StorageService) {
entityId: result.company.id,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.imported",
details: {
include: body.include ?? null,
@ -478,6 +479,7 @@ export function companyRoutes(db: Db, storage?: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.updated",
entityType: "company",
entityId: companyId,
@ -503,6 +505,7 @@ export function companyRoutes(db: Db, storage?: StorageService) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.branding_updated",
entityType: "company",
entityId: companyId,

View File

@ -400,6 +400,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_input_created",
entityType: "company_skill_test_input",
entityId: result.id,
@ -429,6 +430,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_input_updated",
entityType: "company_skill_test_input",
entityId: result.id,
@ -455,6 +457,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_input_deleted",
entityType: "company_skill_test_input",
entityId: result.id,
@ -483,6 +486,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_template_created",
entityType: "company_skill_test_run_template",
entityId: result.id,
@ -511,6 +515,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_template_updated",
entityType: "company_skill_test_run_template",
entityId: result.id,
@ -536,6 +541,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_template_deleted",
entityType: "company_skill_test_run_template",
entityId: result.id,
@ -593,6 +599,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.created",
entityType: "issue",
entityId: created.id,
@ -630,6 +637,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_harness_issue_cleaned_up",
entityType: "issue",
entityId: issueId,
@ -643,9 +651,11 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_created",
entityType: "company_skill_test_run",
entityId: result.id,
issueId: result.issueId,
details: {
skillId,
inputId: result.inputId,
@ -691,9 +701,11 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_cancelled",
entityType: "company_skill_test_run",
entityId: result.id,
issueId: result.issueId,
details: { skillId, issueId: result.issueId },
});
res.json(result);
@ -727,9 +739,11 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_deleted",
entityType: "company_skill_test_run",
entityId: result.id,
issueId: result.issueId,
details: { skillId, issueId: result.issueId },
});
res.json(result);
@ -750,6 +764,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_version_created",
entityType: "company_skill_version",
entityId: result.id,
@ -775,6 +790,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_starred",
entityType: "company_skill",
entityId: skillId,
@ -795,6 +811,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_unstarred",
entityType: "company_skill",
entityId: skillId,
@ -823,6 +840,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_forked",
entityType: "company_skill",
entityId: result.skill.id,
@ -859,6 +877,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_comment_created",
entityType: "company_skill_comment",
entityId: result.id,
@ -884,6 +903,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_comment_updated",
entityType: "company_skill_comment",
entityId: result.id,
@ -906,6 +926,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_comment_deleted",
entityType: "company_skill_comment",
entityId: result.id,
@ -956,6 +977,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_created",
entityType: "company_skill",
entityId: result.id,
@ -985,6 +1007,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_updated",
entityType: "company_skill",
entityId: result.id,
@ -1021,6 +1044,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_file_updated",
entityType: "company_skill",
entityId: skillId,
@ -1050,6 +1074,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_file_deleted",
entityType: "company_skill",
entityId: skillId,
@ -1080,6 +1105,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skills_imported",
entityType: "company",
entityId: companyId,
@ -1122,6 +1148,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: result.action === "created" ? "company.skill_catalog_installed" : "company.skill_catalog_updated",
entityType: "company_skill",
entityId: result.skill.id,
@ -1154,6 +1181,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skills_scanned",
entityType: "company",
entityId: companyId,
@ -1191,6 +1219,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_deleted",
entityType: "company_skill",
entityId: result.id,
@ -1222,6 +1251,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_audited",
entityType: "company_skill",
entityId: skillId,
@ -1259,6 +1289,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_update_installed",
entityType: "company_skill",
entityId: result.id,
@ -1299,6 +1330,7 @@ export function companySkillRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_reset",
entityType: "company_skill",
entityId: result.id,

View File

@ -458,6 +458,7 @@ export function executionWorkspaceRoutes(db: Db, opts: { pluginWorkerManager?: P
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: `execution_workspace.runtime_${action}`,
entityType: "execution_workspace",
entityId: existing.id,
@ -505,6 +506,7 @@ export function executionWorkspaceRoutes(db: Db, opts: { pluginWorkerManager?: P
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "execution_workspace.branch_reconciled",
entityType: "execution_workspace",
entityId: existing.id,
@ -735,6 +737,7 @@ export function executionWorkspaceRoutes(db: Db, opts: { pluginWorkerManager?: P
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "execution_workspace.updated",
entityType: "execution_workspace",
entityId: workspace.id,

View File

@ -409,6 +409,7 @@ export function fileResourceRoutes(db: Db, opts: {
entityId: req.params.issueId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
details: listActivityDetails({
outcome: result.state === "available" ? "success" : "unavailable",
workspaceSelector: result.query.workspace,
@ -521,6 +522,7 @@ export function fileResourceRoutes(db: Db, opts: {
entityId: req.params.issueId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
details: activityDetails({
outcome: "success",
workspaceKind: result.workspaceKind,
@ -646,6 +648,7 @@ export function fileResourceRoutes(db: Db, opts: {
entityId: req.params.issueId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
details: activityDetails({
outcome: "success",
workspaceKind: result.resource.workspaceKind,
@ -695,6 +698,7 @@ export function fileResourceRoutes(db: Db, opts: {
entityId: req.params.issueId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
details: activityDetails({
outcome: "success",
workspaceKind: result.resource.workspaceKind,

View File

@ -40,6 +40,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.created",
entityType: "folder",
entityId: created.id,
@ -65,6 +66,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.personal_ensured",
entityType: "folder",
entityId: folder.id,
@ -90,6 +92,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.updated",
entityType: "folder",
entityId: updated.id,
@ -109,6 +112,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.item_moved",
entityType: req.body.kind === "routine" ? "routine" : "company_skill",
entityId: moved.itemId,
@ -133,6 +137,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.moved",
entityType: "folder",
entityId: updated.id,
@ -157,6 +162,7 @@ export function folderRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "folder.deleted",
entityType: "folder",
entityId: deleted.id,

View File

@ -51,6 +51,7 @@ export function inboxAgentPolicyRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "inbox.agent_policy_updated",
entityType: "user_inbox_agent_policy",
entityId: userId,

View File

@ -78,6 +78,7 @@ export function inboxDismissalRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: dismissal.kind === "snooze" ? "inbox.snoozed" : "inbox.dismissed",
entityType: "company",
entityId: companyId,
@ -115,6 +116,7 @@ export function inboxDismissalRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "inbox.restored",
entityType: "company",
entityId: companyId,

View File

@ -57,6 +57,7 @@ export function instanceSettingsRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "instance.settings.updated",
entityType: "instance_settings",
entityId: updated.id,
@ -94,6 +95,7 @@ export function instanceSettingsRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "instance.settings.general_updated",
entityType: "instance_settings",
entityId: updated.id,
@ -132,6 +134,7 @@ export function instanceSettingsRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "instance.settings.experimental_updated",
entityType: "instance_settings",
entityId: updated.id,
@ -177,6 +180,7 @@ export function instanceSettingsRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "instance.settings.issue_graph_liveness_auto_recovery_run",
entityType: "instance_settings",
entityId: "default",

View File

@ -56,6 +56,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_control_previewed",
entityType: "issue",
entityId: root.id,
@ -92,6 +93,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_hold_created",
entityType: "issue",
entityId: root.id,
@ -117,6 +119,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_hold_run_interrupted",
entityType: "heartbeat_run",
entityId: heartbeatRunId,
@ -133,6 +136,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_hold_run_interrupt_failed",
entityType: "heartbeat_run",
entityId: heartbeatRunId,
@ -162,6 +166,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_hold_wakeup_deferred",
entityType: "agent_wakeup_request",
entityId: wakeup.id,
@ -183,6 +188,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_cancel_status_updated",
entityType: "issue",
entityId: root.id,
@ -224,6 +230,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_restore_status_updated",
entityType: "issue",
entityId: root.id,
@ -270,9 +277,11 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_restore_wakeup_requested",
entityType: "heartbeat_run",
entityId: wakeRun.id,
issueId: restoredIssue.id,
details: {
holdId: result.hold.id,
rootIssueId: root.id,
@ -366,6 +375,7 @@ export function issueTreeControlRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.tree_hold_released",
entityType: "issue",
entityId: root.id,

View File

@ -453,6 +453,7 @@ async function auditAgentIssueCreateAttributionSpoof(input: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: input.action === "rejected"
? "issue.attribution_spoof_rejected"
: "issue.attribution_spoof_stripped",
@ -2865,9 +2866,11 @@ export function issueRoutes(
actorId: input.actor.actorId,
agentId: input.actor.agentId,
runId: input.actor.runId,
agentApiKeyId: input.actor.agentApiKeyId,
action: "heartbeat.cancelled",
entityType: "heartbeat_run",
entityId: cancelledRunId,
issueId: input.issue.id,
details: {
source: "issue_comment_scheduled_retry_superseded",
issueId: input.issue.id,
@ -3244,6 +3247,7 @@ export function issueRoutes(
actorId: input.actor.actorId,
agentId: input.actor.agentId,
runId: input.actor.runId,
agentApiKeyId: input.actor.agentApiKeyId,
action: "issue.thread_interaction_expired",
entityType: "issue",
entityId: input.issue.id,
@ -3587,6 +3591,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.checkout_lock_adopted",
entityType: "issue",
entityId: issue.id,
@ -3808,6 +3813,7 @@ export function issueRoutes(
actorId: input.actor.actorId,
agentId: input.actor.agentId,
runId: input.actor.runId,
agentApiKeyId: input.actor.agentApiKeyId,
action: "issue.task_watchdog_followups_serialized",
entityType: "issue",
entityId: watchdogParent.id,
@ -5018,6 +5024,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "label.created",
entityType: "label",
entityId: label.id,
@ -5042,6 +5049,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "label.deleted",
entityType: "label",
entityId: removed.id,
@ -5443,6 +5451,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: created ? "issue.watchdog_created" : "issue.watchdog_updated",
entityType: "issue",
entityId: issue.id,
@ -5479,6 +5488,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.watchdog_removed",
entityType: "issue",
entityId: issue.id,
@ -5610,6 +5620,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.updated",
entityType: "issue",
entityId: result.issue.id,
@ -5631,6 +5642,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.recovery_action_resolved",
entityType: "issue",
entityId: result.issue.id,
@ -5754,6 +5766,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "external_object.refresh_requested",
entityType: "issue",
entityId: issue.id,
@ -5848,6 +5861,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_annotation_thread_created",
entityType: "issue",
entityId: issue.id,
@ -5926,6 +5940,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_annotation_comment_added",
entityType: "issue",
entityId: issue.id,
@ -5974,6 +5989,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: thread.status === "resolved"
? "issue.document_annotation_thread_resolved"
: "issue.document_annotation_thread_reopened",
@ -6044,6 +6060,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: result.created ? "issue.document_created" : "issue.document_updated",
entityType: "issue",
entityId: issue.id,
@ -6069,6 +6086,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_annotation_remapped",
entityType: "issue",
entityId: issue.id,
@ -6145,6 +6163,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_locked",
entityType: "issue",
entityId: issue.id,
@ -6184,6 +6203,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_unlocked",
entityType: "issue",
entityId: issue.id,
@ -6256,6 +6276,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_restored",
entityType: "issue",
entityId: issue.id,
@ -6282,6 +6303,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_annotation_remapped",
entityType: "issue",
entityId: issue.id,
@ -6358,6 +6380,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.document_deleted",
entityType: "issue",
entityId: issue.id,
@ -6432,6 +6455,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.work_product_created",
entityType: "issue",
entityId: issue.id,
@ -6565,6 +6589,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.low_trust_output_promoted",
entityType: "issue",
entityId: issue.id,
@ -6629,6 +6654,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.work_product_updated",
entityType: "issue",
entityId: existing.issueId,
@ -6666,6 +6692,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.work_product_deleted",
entityType: "issue",
entityId: existing.issueId,
@ -6700,6 +6727,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.read_marked",
entityType: "issue",
entityId: issue.id,
@ -6728,6 +6756,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.read_unmarked",
entityType: "issue",
entityId: issue.id,
@ -6800,6 +6829,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.inbox_archived",
entityType: "issue",
entityId: issue.id,
@ -6826,6 +6856,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.inbox_unarchived",
entityType: "issue",
entityId: issue.id,
@ -6868,6 +6899,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.approval_linked",
entityType: "issue",
entityId: issue.id,
@ -6896,6 +6928,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.approval_unlinked",
entityType: "issue",
entityId: issue.id,
@ -7069,6 +7102,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.created",
entityType: "issue",
entityId: issue.id,
@ -7104,6 +7138,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.monitor_scheduled",
entityType: "issue",
entityId: issue.id,
@ -7127,6 +7162,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.watchdog_created",
entityType: "issue",
entityId: issue.id,
@ -7237,6 +7273,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.child_created",
entityType: "issue",
entityId: issue.id,
@ -7264,6 +7301,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.monitor_scheduled",
entityType: "issue",
entityId: issue.id,
@ -7288,6 +7326,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.watchdog_created",
entityType: "issue",
entityId: issue.id,
@ -7428,6 +7467,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.accepted_plan_decomposition_updated",
entityType: "issue",
entityId: sourceIssue.id,
@ -7456,6 +7496,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.child_created",
entityType: "issue",
entityId: issue.id,
@ -7483,6 +7524,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.monitor_scheduled",
entityType: "issue",
entityId: issue.id,
@ -7731,9 +7773,11 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "heartbeat.cancelled",
entityType: "heartbeat_run",
entityId: cancelled.id,
issueId: existing.id,
details: {
agentId: cancelled.agentId,
source: "issue_comment_interrupt",
@ -7962,9 +8006,11 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "heartbeat.cancelled",
entityType: "heartbeat_run",
entityId: cancelled.id,
issueId: existing.id,
details: { agentId: cancelled.agentId, source: "issue_status_cancelled", issueId: existing.id },
});
}
@ -7976,9 +8022,11 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "heartbeat.cancel_failed",
entityType: "heartbeat_run",
entityId: runToCancelForCancelledStatus.id,
issueId: existing.id,
details: { source: "issue_status_cancelled", issueId: existing.id },
});
}
@ -8086,6 +8134,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.updated",
entityType: "issue",
entityId: issue.id,
@ -8129,6 +8178,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.successful_run_handoff_resolved",
entityType: "issue",
entityId: issue.id,
@ -8159,6 +8209,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.blockers_updated",
entityType: "issue",
entityId: issue.id,
@ -8187,6 +8238,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.reviewers_updated",
entityType: "issue",
entityId: issue.id,
@ -8207,6 +8259,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.approvers_updated",
entityType: "issue",
entityId: issue.id,
@ -8230,6 +8283,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.monitor_scheduled",
entityType: "issue",
entityId: issue.id,
@ -8252,6 +8306,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.monitor_cleared",
entityType: "issue",
entityId: issue.id,
@ -8298,9 +8353,11 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.skill_test_run_completed",
entityType: "company_skill_test_run",
entityId: completedRun.id,
issueId: issue.id,
details: {
issueId: issue.id,
status: completedRun.status,
@ -8342,6 +8399,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.comment_added",
entityType: "issue",
entityId: issue.id,
@ -8689,6 +8747,7 @@ export function issueRoutes(
actorId: "issue_update",
agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.blockers_resolved_wake_emitted",
entityType: "issue",
entityId: dependentIssueId,
@ -8739,6 +8798,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.deleted",
entityType: "issue",
entityId: issue.id,
@ -8801,6 +8861,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.checked_out",
entityType: "issue",
entityId: issue.id,
@ -8856,6 +8917,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.released",
entityType: "issue",
entityId: released.id,
@ -8891,6 +8953,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.admin_force_release",
entityType: "issue",
entityId: result.issue.id,
@ -8988,6 +9051,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.thread_interaction_created",
entityType: "issue",
entityId: issue.id,
@ -9065,6 +9129,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: interaction.status === "expired"
? "issue.thread_interaction_expired"
: "issue.thread_interaction_accepted",
@ -9092,6 +9157,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.updated",
entityType: "issue",
entityId: issue.id,
@ -9168,6 +9234,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: interaction.status === "expired"
? "issue.thread_interaction_expired"
: "issue.thread_interaction_rejected",
@ -9221,6 +9288,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.thread_interaction_answered",
entityType: "issue",
entityId: issue.id,
@ -9275,6 +9343,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: interaction.status === "expired"
? "issue.thread_interaction_expired"
: "issue.thread_interaction_item_verdicts_submitted",
@ -9336,6 +9405,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.thread_interaction_cancelled",
entityType: "issue",
entityId: issue.id,
@ -9426,6 +9496,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.comment_cancelled",
entityType: "issue",
entityId: issue.id,
@ -9495,6 +9566,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.comment_deleted",
entityType: "issue",
entityId: issue.id,
@ -9712,6 +9784,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.updated",
entityType: "issue",
entityId: currentIssue.id,
@ -9753,9 +9826,11 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "heartbeat.cancelled",
entityType: "heartbeat_run",
entityId: cancelled.id,
issueId: currentIssue.id,
details: {
agentId: cancelled.agentId,
source: "issue_comment_interrupt",
@ -9873,6 +9948,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.updated",
entityType: "issue",
entityId: currentIssue.id,
@ -9924,6 +10000,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.comment_added",
entityType: "issue",
entityId: currentIssue.id,
@ -10190,6 +10267,7 @@ export function issueRoutes(
actorId: "issue_comment",
agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.blockers_resolved_wake_emitted",
entityType: "issue",
entityId: dependentIssueId,
@ -10238,6 +10316,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.feedback_vote_saved",
entityType: "issue",
entityId: issue.id,
@ -10258,6 +10337,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "company.feedback_data_sharing_updated",
entityType: "company",
entityId: issue.companyId,
@ -10279,6 +10359,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "instance.settings.general_updated",
entityType: "instance_settings",
entityId: settings.id,
@ -10394,6 +10475,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.attachment_added",
entityType: "issue",
entityId: issueId,
@ -10500,6 +10582,7 @@ export function issueRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "issue.attachment_removed",
entityType: "issue",
entityId: removed.issueId,

View File

@ -697,6 +697,7 @@ export function pluginRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action,
entityType: "plugin",
entityId,

View File

@ -34,6 +34,7 @@ async function logMembershipChange(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: `resource_membership.${input.changeKind}`,
entityType: input.resourceType,
entityId: input.resourceId,

View File

@ -68,6 +68,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.document_annotation_remapped",
entityType: "routine",
entityId: routineId,
@ -132,6 +133,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.revision_created",
entityType: "routine",
entityId: input.routineId,
@ -168,6 +170,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.created",
entityType: "routine",
entityId: created.id,
@ -259,6 +262,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.document_annotation_thread_created",
entityType: "routine",
entityId: routine.id,
@ -299,6 +303,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.document_annotation_comment_added",
entityType: "routine",
entityId: routine.id,
@ -337,6 +342,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: thread.status === "resolved"
? "routine.document_annotation_thread_resolved"
: "routine.document_annotation_thread_reopened",
@ -392,6 +398,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.updated",
entityType: "routine",
entityId: routine.id,
@ -430,6 +437,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.revision_restored",
entityType: "routine",
entityId: routine.id,
@ -472,6 +480,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.trigger_created",
entityType: "routine_trigger",
entityId: created.trigger.id,
@ -512,6 +521,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.trigger_updated",
entityType: "routine_trigger",
entityId: trigger.id,
@ -553,6 +563,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.trigger_deleted",
entityType: "routine_trigger",
entityId: trigger.id,
@ -597,6 +608,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.trigger_secret_rotated",
entityType: "routine_trigger",
entityId: trigger.id,
@ -632,6 +644,7 @@ export function routineRoutes(
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "routine.run_triggered",
entityType: "routine_run",
entityId: run.id,

View File

@ -55,6 +55,7 @@ export function sidebarPreferenceRoutes(db: Db) {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "sidebar_preferences.project_order_updated",
entityType: "company",
entityId: companyId,

View File

@ -131,6 +131,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.services_started",
entityType: "smoke_lab",
entityId: companyId,
@ -151,6 +152,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.services_stopped",
entityType: "smoke_lab",
entityId: companyId,
@ -171,6 +173,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.fixtures_installed",
entityType: "smoke_lab",
entityId: companyId,
@ -204,6 +207,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.run_created",
entityType: "smoke_run",
entityId: run.id,
@ -231,6 +235,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.run_updated",
entityType: "smoke_run",
entityId: run.id,
@ -251,6 +256,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.step_recorded",
entityType: "smoke_run_step",
entityId: result.step.id,
@ -271,6 +277,7 @@ export function smokeLabRoutes(db: Db, options: {
actorId: actor.actorId,
agentId: actor.agentId,
runId: actor.runId,
agentApiKeyId: actor.agentApiKeyId,
action: "smoke_lab.reset",
entityType: "smoke_lab",
entityId: companyId,

View File

@ -1,7 +1,8 @@
import { randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import type { Db } from "@paperclipai/db";
import { activityLog } from "@paperclipai/db";
import { PLUGIN_EVENT_TYPES, type PluginEventType } from "@paperclipai/shared";
import { activityLog, agentApiKeys, companies, heartbeatRuns, issues } from "@paperclipai/db";
import { isUuidLike, PLUGIN_EVENT_TYPES, type PluginEventType } from "@paperclipai/shared";
import type { PluginEvent } from "@paperclipai/plugin-sdk";
import { publishLiveEvent } from "./live-events.js";
import { redactCurrentUserValue } from "../log-redaction.js";
@ -59,9 +60,70 @@ export interface LogActivityInput {
entityId: string;
agentId?: string | null;
runId?: string | null;
agentApiKeyId?: string | null;
issueId?: string | null;
details?: Record<string, unknown> | null;
}
function readNonEmptyString(value: unknown) {
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
}
export async function resolveResponsibleUserIdForActivity(db: Db, input: LogActivityInput) {
if (input.actorType === "user") return readNonEmptyString(input.actorId);
const runId = readNonEmptyString(input.runId);
if (runId && isUuidLike(runId)) {
const run = await db
.select({ responsibleUserId: heartbeatRuns.responsibleUserId })
.from(heartbeatRuns)
.where(and(eq(heartbeatRuns.companyId, input.companyId), eq(heartbeatRuns.id, runId)))
.then((rows) => rows[0] ?? null);
const runResponsibleUserId = readNonEmptyString(run?.responsibleUserId);
if (runResponsibleUserId) return runResponsibleUserId;
}
const issueIdCandidate = readNonEmptyString(input.issueId)
?? (input.entityType === "issue" ? readNonEmptyString(input.entityId) : null);
const issueId = isUuidLike(issueIdCandidate) ? issueIdCandidate : null;
if (issueId) {
const issue = await db
.select({
responsibleUserId: issues.responsibleUserId,
createdByUserId: issues.createdByUserId,
})
.from(issues)
.where(and(eq(issues.companyId, input.companyId), eq(issues.id, issueId)))
.then((rows) => rows[0] ?? null);
const issueResponsibleUserId = readNonEmptyString(issue?.responsibleUserId)
?? readNonEmptyString(issue?.createdByUserId);
if (issueResponsibleUserId) return issueResponsibleUserId;
}
const agentApiKeyId = readNonEmptyString(input.agentApiKeyId);
const agentId = readNonEmptyString(input.agentId);
if (agentApiKeyId && isUuidLike(agentApiKeyId)) {
const apiKey = await db
.select({ responsibleUserId: agentApiKeys.responsibleUserId })
.from(agentApiKeys)
.where(and(
eq(agentApiKeys.companyId, input.companyId),
eq(agentApiKeys.id, agentApiKeyId),
...(agentId && isUuidLike(agentId) ? [eq(agentApiKeys.agentId, agentId)] : []),
))
.then((rows) => rows[0] ?? null);
const apiKeyResponsibleUserId = readNonEmptyString(apiKey?.responsibleUserId);
if (apiKeyResponsibleUserId) return apiKeyResponsibleUserId;
}
const company = await db
.select({ defaultResponsibleUserId: companies.defaultResponsibleUserId })
.from(companies)
.where(eq(companies.id, input.companyId))
.then((rows) => rows[0] ?? null);
return readNonEmptyString(company?.defaultResponsibleUserId);
}
export async function logActivity(db: Db, input: LogActivityInput) {
const currentUserRedactionOptions = {
enabled: (await instanceSettingsService(db).getGeneral()).censorUsernameInLogs,
@ -70,6 +132,7 @@ export async function logActivity(db: Db, input: LogActivityInput) {
const redactedDetails = sanitizedDetails
? redactCurrentUserValue(sanitizedDetails, currentUserRedactionOptions)
: null;
const responsibleUserId = await resolveResponsibleUserIdForActivity(db, input);
await db.insert(activityLog).values({
companyId: input.companyId,
actorType: input.actorType,
@ -79,6 +142,7 @@ export async function logActivity(db: Db, input: LogActivityInput) {
entityId: input.entityId,
agentId: input.agentId ?? null,
runId: input.runId ?? null,
responsibleUserId,
details: redactedDetails,
});
@ -93,6 +157,7 @@ export async function logActivity(db: Db, input: LogActivityInput) {
entityId: input.entityId,
agentId: input.agentId ?? null,
runId: input.runId ?? null,
responsibleUserId,
details: redactedDetails,
},
});
@ -112,6 +177,7 @@ export async function logActivity(db: Db, input: LogActivityInput) {
...redactedDetails,
agentId: input.agentId ?? null,
runId: input.runId ?? null,
responsibleUserId,
},
};
publishPluginDomainEvent(event);

View File

@ -291,6 +291,7 @@ export function environmentRunOrchestrator(
action: "environment.lease_acquired",
entityType: "environment_lease",
entityId: leaseRecord.lease.id,
issueId: input.issueId,
details: {
environmentId: environment.id,
driver: environment.driver,
@ -535,6 +536,7 @@ export function environmentRunOrchestrator(
action: "environment.lease_released",
entityType: "environment_lease",
entityId: released.lease.id,
issueId: released.lease.issueId,
details: {
environmentId: released.lease.environmentId,
driver: released.environment.driver,

View File

@ -5740,6 +5740,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
action: "company.skill_test_run_completed",
entityType: "company_skill_test_run",
entityId: completedRun.id,
issueId: input.issueId,
details: {
issueId: input.issueId,
status: completedRun.status,
@ -10673,6 +10674,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
action: "issue.tree_hold_run_interrupted",
entityType: "heartbeat_run",
entityId: run.id,
issueId: issueId,
details: {
issueId,
holdId: activePauseHold.holdId,

View File

@ -1181,6 +1181,7 @@ export function createToolGatewayService(
entityId,
agentId: input.agentId,
runId: input.runId,
issueId: input.issueId,
details: {
gatewaySessionId: input.session?.id ?? null,
gatewayId: input.session?.gatewayId ?? null,