[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
import { Buffer } from "node:buffer";
|
2026-04-26 21:17:38 -05:00
|
|
|
import { and, asc, desc, eq, gt, inArray, isNull, lt, ne, notInArray, or, sql } from "drizzle-orm";
|
2026-03-03 08:45:26 -06:00
|
|
|
import type { Db } from "@paperclipai/db";
|
2026-02-20 10:31:56 -06:00
|
|
|
import {
|
2026-03-21 12:20:48 -05:00
|
|
|
activityLog,
|
2026-04-24 15:50:32 -05:00
|
|
|
agentWakeupRequests,
|
2026-02-20 10:31:56 -06:00
|
|
|
agents,
|
2026-04-26 21:17:38 -05:00
|
|
|
approvals,
|
2026-02-20 10:31:56 -06:00
|
|
|
assets,
|
|
|
|
|
companies,
|
2026-02-23 14:40:32 -06:00
|
|
|
companyMemberships,
|
2026-03-13 22:17:49 -05:00
|
|
|
documents,
|
2026-02-20 10:31:56 -06:00
|
|
|
goals,
|
2026-02-21 08:23:44 -06:00
|
|
|
heartbeatRuns,
|
2026-03-13 17:12:25 -05:00
|
|
|
executionWorkspaces,
|
2026-04-26 21:17:38 -05:00
|
|
|
issueApprovals,
|
2026-02-20 10:31:56 -06:00
|
|
|
issueAttachments,
|
2026-03-26 08:19:16 -05:00
|
|
|
issueInboxArchives,
|
2026-02-25 08:38:37 -06:00
|
|
|
issueLabels,
|
2026-04-04 13:56:04 -05:00
|
|
|
issueRelations,
|
2026-02-20 10:31:56 -06:00
|
|
|
issueComments,
|
2026-03-13 22:17:49 -05:00
|
|
|
issueDocuments,
|
2026-03-06 08:34:19 -06:00
|
|
|
issueReadStates,
|
2026-04-26 21:17:38 -05:00
|
|
|
issueThreadInteractions,
|
2026-02-20 10:31:56 -06:00
|
|
|
issues,
|
2026-02-25 08:38:37 -06:00
|
|
|
labels,
|
|
|
|
|
projectWorkspaces,
|
2026-02-20 10:31:56 -06:00
|
|
|
projects,
|
2026-03-03 08:45:26 -06:00
|
|
|
} from "@paperclipai/db";
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
import type {
|
|
|
|
|
IssueBlockerAttention,
|
|
|
|
|
IssueProductivityReview,
|
|
|
|
|
IssueProductivityReviewTrigger,
|
|
|
|
|
IssueRelationIssueSummary,
|
|
|
|
|
} from "@paperclipai/shared";
|
|
|
|
|
import { clampIssueRequestDepth, extractAgentMentionIds, extractProjectMentionIds, isUuidLike } from "@paperclipai/shared";
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
import { conflict, notFound, unprocessable } from "../errors.js";
|
Honor reuse-existing preference and assignee default environment in issue runs (#5139)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside execution workspaces (a per-issue cwd + env), and
an issue
> can prefer to reuse an existing workspace or get a fresh one each time
> - The heartbeat service was reading the existing workspace's config to
derive
> environment selection regardless of whether the issue actually wanted
to reuse
> it. So fresh-run issues were inheriting stale config from a workspace
that was
> about to be discarded
> - Separately, when an issue is assigned to an agent, the issue's
execution
> workspace settings weren't picking up the agent's
`defaultEnvironmentId`,
> even though the agent's choice is the natural default for that issue
> - This PR makes both selection paths honor the obvious source of
truth:
> workspace config flows only when the issue actually wants
`reuse_existing`,
> and the assignee agent's default environment is applied at assignment
time if
> nothing else is set on the issue
> - The benefit is that re-running a flaky issue picks up the right
environment
> instead of inheriting the previous run's config, and assigning an
agent to an
> issue does the obvious thing without operator intervention
## What Changed
- `server/src/services/heartbeat.ts`: introduce
`reusableExecutionWorkspaceConfig`
that is non-null only when `shouldReuseExisting` is true. Both
`resolveExecutionWorkspaceEnvironmentId(...)` and
`applyPersistedExecutionWorkspaceConfig(...)` now read from it instead
of
unconditionally consulting `existingExecutionWorkspace?.config`.
Fresh-run
issues no longer inherit stale environment config from an in-flight
workspace
about to be discarded.
- `server/src/services/issues.ts`: when an issue update sets a new
`assigneeAgentId` and isolated workspaces are enabled, populate
`executionWorkspaceSettings.environmentId` from the assignee agent's
`defaultEnvironmentId` if the issue doesn't have an explicit
`environmentId` set yet.
- Tests added in `heartbeat-plugin-environment.test.ts` (~216 lines) and
`issues-service.test.ts` (~85 lines) covering both paths.
## Verification
- `pnpm --filter @paperclipai/server test --
heartbeat-plugin-environment issues-service`
- Manual QA: assign an issue to an agent that has a non-default
`defaultEnvironmentId`, confirm the issue's workspace settings now
include that
environment id without operator intervention. Trigger a rerun on an
issue
whose existing workspace points at a stale environment, confirm the
rerun uses
the freshly-resolved environment.
## Risks
- Behavioural shift on assignment: previously assigning an agent didn't
propagate the agent's default environment to the issue. Now it does.
Callers
that explicitly want the issue to keep its existing/null environment
must set
`executionWorkspaceSettings.environmentId` themselves; the new logic
only
fires when no explicit value is set.
- Behavioural shift on rerun: stale workspace config is no longer
applied to
fresh runs. Operators who relied on this implicit inheritance may see
different environment selection on the first rerun after deploy.
Mitigation:
the explicit isssue settings and project policy are still honored as
before.
## Model Used
- OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI
- Provider: OpenAI
- Used to author the code changes in this PR
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A (no UI changes)
- [ ] I have updated relevant documentation to reflect my changes — N/A
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-03 18:33:55 -07:00
|
|
|
import { parseObject } from "../adapters/utils.js";
|
2026-03-10 09:03:31 -05:00
|
|
|
import {
|
|
|
|
|
defaultIssueExecutionWorkspaceSettingsForProject,
|
2026-03-17 09:24:28 -05:00
|
|
|
gateProjectExecutionWorkspacePolicy,
|
2026-03-30 14:08:44 -05:00
|
|
|
issueExecutionWorkspaceModeForPersistedWorkspace,
|
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
|
|
|
parseIssueExecutionWorkspaceSettings,
|
2026-03-10 09:03:31 -05:00
|
|
|
parseProjectExecutionWorkspacePolicy,
|
|
|
|
|
} from "./execution-workspace-policy.js";
|
Fix runtime state race, workspace sync, plugin startup, and orphaned leases (#4804)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments that are leased, and the server
manages runtime state, workspace configuration, and plugin lifecycle
> - Several edge cases caused failures during concurrent operations: a
race condition in runtime state insertion could produce duplicate-key
errors, reused workspaces didn't sync their configuration when the
parent issue was updated, sandbox provider plugins could be queried
before registration completed, and orphaned environment leases from
failed runs were never released
> - This PR fixes these four runtime/environment issues
> - The benefit is more reliable concurrent agent execution and proper
resource cleanup
## What Changed
- `services/heartbeat.ts`: Fixed a race condition where concurrent
runtime state inserts could fail with a duplicate-key error by using an
upsert pattern
- `services/issues.ts`: Sync reused workspace configuration when an
issue is updated, so the workspace reflects the latest issue state
- `services/environment-runtime.ts`: Fixed a startup race where sandbox
provider plugins could be queried before registration completed, by
awaiting plugin readiness before resolving environment drivers
- `services/heartbeat.ts`: Release environment leases for orphaned runs
that lost their process without cleanup
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
runtime state upsert and process recovery lease cleanup
- `pnpm typecheck` — clean
- Manual: trigger concurrent agent runs to verify no duplicate-key
failures; verify orphaned leases are released after process loss
## Risks
- Low risk. The runtime state upsert changes insert-to-upsert behavior,
which could mask a legitimate duplicate if two different runs produce
the same key — but this is prevented by the run ID being part of the
key. The plugin startup await is bounded by the existing registration
timeout.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-29 16:37:10 -07:00
|
|
|
import { mergeExecutionWorkspaceConfig } from "./execution-workspaces.js";
|
[codex] Add issue monitor liveness controls (#4988)
## Thinking Path
> - Paperclip is a control plane for autonomous AI companies where work
must stay observable, governable, and recoverable.
> - The task/heartbeat subsystem owns agent execution continuity, issue
state transitions, and visible recovery behavior.
> - Waiting on an external service is not the same as being blocked when
the assignee still owns a future check.
> - The gap was that agents had no first-class one-shot monitor state
for external-service waits, so recovery could look stalled or require ad
hoc comments.
> - This pull request adds bounded issue monitors that can wake the
owner, clear exhausted waits, and produce explicit recovery behavior.
> - It also surfaces monitor status in the board UI and documents when
to use monitors versus `blocked`.
> - The benefit is clearer liveness semantics for asynchronous waits
without weakening single-assignee task ownership.
## What Changed
- Added issue monitor fields, shared types, validators, constants, and
an idempotent `0075` migration for scheduled monitor state.
- Added server-side monitor scheduling, dispatch, recovery bounds,
activity logging, and external-ref redaction.
- Added board/agent route coverage for monitor permissions and child
monitor scheduling.
- Added issue detail/property UI for monitor state, a monitor activity
card, and Storybook stories for review surfaces.
- Documented monitor semantics and recovery policy behavior in
`doc/execution-semantics.md`.
- Addressed Greptile review feedback by preserving monitor state in
skipped-stage builders and making board monitor saves send `scheduledBy:
"board"`.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issue-execution-policy-routes.test.ts
server/src/__tests__/issue-execution-policy.test.ts
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/lib/activity-format.test.ts`
- First run passed 5 files and failed to collect 2 server suites because
the worktree was missing the optional `acpx/runtime` dependency.
- After `pnpm install --frozen-lockfile`, reran the 2 failed suites
successfully.
- `pnpm exec vitest run
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server typecheck
&& pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run
server/src/__tests__/issue-execution-policy.test.ts
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/server typecheck && pnpm --filter
@paperclipai/ui typecheck`
- `pnpm exec vitest run
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- Storybook screenshot captured from
`http://127.0.0.1:6006/iframe.html?viewMode=story&id=product-issue-monitor-surfaces--monitor-surfaces`
with Playwright.
## Screenshots

## Risks
- Medium: this changes heartbeat recovery behavior for scheduled
external-service waits, so regressions could affect wake timing or
recovery issue creation.
- Migration risk is reduced by using `IF NOT EXISTS` for the new issue
monitor columns and index.
- External monitor references are treated as secret-adjacent and are
intentionally omitted from visible activity/wake payloads.
> 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 coding agent with repository tool use and terminal
execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots or Storybook review surfaces
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-03 08:58:53 -05:00
|
|
|
import { buildInitialIssueMonitorFields, normalizeIssueExecutionPolicy } from "./issue-execution-policy.js";
|
2026-03-17 09:24:28 -05:00
|
|
|
import { instanceSettingsService } from "./instance-settings.js";
|
2026-03-11 22:17:21 -05:00
|
|
|
import { redactCurrentUserText } from "../log-redaction.js";
|
2026-03-12 08:50:31 -05:00
|
|
|
import { resolveIssueGoalId, resolveNextIssueGoalId } from "./issue-goal-fallback.js";
|
|
|
|
|
import { getDefaultCompanyGoal } from "./goals.js";
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
import {
|
2026-04-24 15:50:32 -05:00
|
|
|
isVerifiedIssueTreeControlInteractionWake,
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
issueTreeControlService,
|
|
|
|
|
type ActiveIssueTreePauseHoldGate,
|
|
|
|
|
} from "./issue-tree-control.js";
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
import { parseIssueGraphLivenessIncidentKey } from "./recovery/origins.js";
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
const ALL_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked", "done", "cancelled"];
|
2026-03-13 10:18:00 -05:00
|
|
|
const MAX_ISSUE_COMMENT_PAGE_LIMIT = 500;
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
export const ISSUE_LIST_DEFAULT_LIMIT = 500;
|
|
|
|
|
export const ISSUE_LIST_MAX_LIMIT = 1000;
|
|
|
|
|
const ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE = 500;
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
export const MAX_CHILD_ISSUES_CREATED_BY_HELPER = 25;
|
|
|
|
|
const MAX_CHILD_COMPLETION_SUMMARIES = 20;
|
|
|
|
|
const CHILD_COMPLETION_SUMMARY_BODY_MAX_CHARS = 500;
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
function assertTransition(from: string, to: string) {
|
|
|
|
|
if (from === to) return;
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
if (!ALL_ISSUE_STATUSES.includes(to)) {
|
|
|
|
|
throw conflict(`Unknown issue status: ${to}`);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyStatusSideEffects(
|
|
|
|
|
status: string | undefined,
|
|
|
|
|
patch: Partial<typeof issues.$inferInsert>,
|
|
|
|
|
): Partial<typeof issues.$inferInsert> {
|
|
|
|
|
if (!status) return patch;
|
|
|
|
|
|
|
|
|
|
if (status === "in_progress" && !patch.startedAt) {
|
|
|
|
|
patch.startedAt = new Date();
|
|
|
|
|
}
|
|
|
|
|
if (status === "done") {
|
|
|
|
|
patch.completedAt = new Date();
|
|
|
|
|
}
|
|
|
|
|
if (status === "cancelled") {
|
|
|
|
|
patch.cancelledAt = new Date();
|
|
|
|
|
}
|
|
|
|
|
return patch;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
function readStringFromRecord(record: unknown, key: string) {
|
|
|
|
|
if (!record || typeof record !== "object") return null;
|
|
|
|
|
const value = (record as Record<string, unknown>)[key];
|
|
|
|
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
|
|
|
}
|
|
|
|
|
|
Fix runtime state race, workspace sync, plugin startup, and orphaned leases (#4804)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments that are leased, and the server
manages runtime state, workspace configuration, and plugin lifecycle
> - Several edge cases caused failures during concurrent operations: a
race condition in runtime state insertion could produce duplicate-key
errors, reused workspaces didn't sync their configuration when the
parent issue was updated, sandbox provider plugins could be queried
before registration completed, and orphaned environment leases from
failed runs were never released
> - This PR fixes these four runtime/environment issues
> - The benefit is more reliable concurrent agent execution and proper
resource cleanup
## What Changed
- `services/heartbeat.ts`: Fixed a race condition where concurrent
runtime state inserts could fail with a duplicate-key error by using an
upsert pattern
- `services/issues.ts`: Sync reused workspace configuration when an
issue is updated, so the workspace reflects the latest issue state
- `services/environment-runtime.ts`: Fixed a startup race where sandbox
provider plugins could be queried before registration completed, by
awaiting plugin readiness before resolving environment drivers
- `services/heartbeat.ts`: Release environment leases for orphaned runs
that lost their process without cleanup
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
runtime state upsert and process recovery lease cleanup
- `pnpm typecheck` — clean
- Manual: trigger concurrent agent runs to verify no duplicate-key
failures; verify orphaned leases are released after process loss
## Risks
- Low risk. The runtime state upsert changes insert-to-upsert behavior,
which could mask a legitimate duplicate if two different runs produce
the same key — but this is prevented by the run ID being part of the
key. The plugin startup await is bounded by the existing registration
timeout.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-29 16:37:10 -07:00
|
|
|
function buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(
|
|
|
|
|
settings: ReturnType<typeof parseIssueExecutionWorkspaceSettings>,
|
|
|
|
|
) {
|
|
|
|
|
return {
|
|
|
|
|
environmentId: settings?.environmentId ?? null,
|
|
|
|
|
provisionCommand: settings?.workspaceStrategy?.provisionCommand ?? null,
|
|
|
|
|
teardownCommand: settings?.workspaceStrategy?.teardownCommand ?? null,
|
|
|
|
|
workspaceRuntime: settings?.workspaceRuntime ?? null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
export interface IssueFilters {
|
|
|
|
|
status?: string;
|
|
|
|
|
assigneeAgentId?: string;
|
2026-03-21 12:20:48 -05:00
|
|
|
participantAgentId?: string;
|
2026-02-26 16:33:39 -06:00
|
|
|
assigneeUserId?: string;
|
2026-03-06 08:21:03 -06:00
|
|
|
touchedByUserId?: string;
|
2026-03-26 08:19:16 -05:00
|
|
|
inboxArchivedByUserId?: string;
|
2026-03-06 08:21:03 -06:00
|
|
|
unreadForUserId?: string;
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
projectId?: string;
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
workspaceId?: string;
|
2026-03-28 22:21:24 -05:00
|
|
|
executionWorkspaceId?: string;
|
2026-03-10 15:54:31 +02:00
|
|
|
parentId?: string;
|
[codex] Improve issue thread review flow (#4381)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Issue detail is where operators coordinate review, approvals, and
follow-up work with active runs
> - That thread UI needs to surface blockers, descendants, review
handoffs, and reply ergonomics clearly enough for humans to guide agent
work
> - Several small gaps in the issue-thread flow were making review and
navigation clunkier than necessary
> - This pull request improves the reply composer, descendant/blocker
presentation, interaction folding, and review-request handoff plumbing
together as one cohesive issue-thread workflow slice
> - The benefit is a cleaner operator review loop without changing the
broader task model
## What Changed
- restored and refined the floating reply composer behavior in the issue
thread
- folded expired confirmation interactions and improved post-submit
thread scrolling behavior
- surfaced descendant issue context and inline blocker/paused-assignee
notices on the issue detail view
- tightened large-board first paint behavior in `IssuesList`
- added loose review-request handoffs through the issue
execution-policy/update path and covered them with tests
## Verification
- `pnpm vitest run ui/src/pages/IssueDetail.test.tsx`
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-execution-policy.test.ts`
- `pnpm exec vitest run --project @paperclipai/ui
ui/src/components/IssueChatThread.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/lib/issue-tree.test.ts
ui/src/api/issues.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-utils
packages/adapter-utils/src/server-utils.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/issue-comment-reopen-routes.test.ts -t "coerces
executor handoff patches into workflow-controlled review wakes|wakes the
return assignee with execution_changes_requested"`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/issue-execution-policy.test.ts
server/src/__tests__/issues-service.test.ts`
## Visual Evidence
- UI layout changes are covered by the focused issue-thread component
and issue-detail tests listed above. Browser screenshots were not
attachable from this automated greploop environment, so reviewers should
use the running preview for final visual confirmation.
## Risks
- Moderate UI-flow risk: these changes touch the issue detail experience
in multiple spots, so regressions would most likely show up as
thread-layout quirks or incorrect review-handoff behavior
> 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-based coding agent with tool use and code execution
in the Codex CLI environment
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots or documented the visual verification path
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-24 08:02:45 -05:00
|
|
|
descendantOf?: string;
|
2026-02-25 08:38:37 -06:00
|
|
|
labelId?: string;
|
2026-03-19 08:39:24 -05:00
|
|
|
originKind?: string;
|
|
|
|
|
originId?: string;
|
|
|
|
|
includeRoutineExecutions?: boolean;
|
[codex] improve issue and routine UI responsiveness (#3744)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Operators rely on issue, inbox, and routine views to understand what
the company is doing in real time
> - Those views need to stay fast and readable even when issue lists,
markdown comments, and run metadata get large
> - The current branch had a coherent set of UI and live-update
improvements spread across issue search, issue detail rendering, routine
affordances, and workspace lookups
> - This pull request groups those board-facing changes into one
standalone branch that can merge independently of the heartbeat/runtime
work
> - The benefit is a faster, clearer issue and routine workflow without
changing the underlying task model
## What Changed
- Show routine execution issues by default and rename the filter to
`Hide routine runs` so the default state no longer looks like an active
filter.
- Show the routine name in the run dialog and tighten the issue
properties pane with a workspace link, copy-on-click behavior, and an
inline parent arrow.
- Reduce issue detail rerenders, keep queued issue chat mounted, improve
issues page search responsiveness, and speed up issues first paint.
- Add inbox "other search results", refresh visible issue runs after
status updates, and optimize workspace lookups through summary-mode
execution workspace queries.
- Improve markdown wrapping and scrolling behavior for long strings and
self-comment code blocks.
- Relax the markdown sanitizer assertion so the test still validates
safety after the new wrap-friendly inline styles.
## Verification
- `pnpm vitest run ui/src/components/IssuesList.test.tsx
ui/src/lib/inbox.test.ts ui/src/pages/Issues.test.tsx
ui/src/context/BreadcrumbContext.test.tsx
ui/src/context/LiveUpdatesProvider.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/api/execution-workspaces.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts`
## Risks
- This touches several issue-facing UI surfaces at once, so regressions
would most likely show up as stale rendering, search result mismatches,
or small markdown presentation differences.
- The workspace lookup optimization depends on the summary-mode route
shape staying aligned between server and UI.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment.
Exact backend model deployment ID was not exposed in-session.
Tool-assisted editing and shell execution were 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 15:54:05 -05:00
|
|
|
excludeRoutineExecutions?: boolean;
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
includeBlockedBy?: boolean;
|
2026-02-26 16:33:39 -06:00
|
|
|
q?: string;
|
2026-04-06 20:30:50 -05:00
|
|
|
limit?: number;
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
offset?: number;
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
}
|
2026-02-16 13:31:58 -06:00
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
type IssueRow = typeof issues.$inferSelect;
|
|
|
|
|
type IssueLabelRow = typeof labels.$inferSelect;
|
2026-03-02 16:43:59 -06:00
|
|
|
type IssueActiveRunRow = {
|
|
|
|
|
id: string;
|
|
|
|
|
status: string;
|
|
|
|
|
agentId: string;
|
|
|
|
|
invocationSource: string;
|
|
|
|
|
triggerDetail: string | null;
|
|
|
|
|
startedAt: Date | null;
|
|
|
|
|
finishedAt: Date | null;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
};
|
2026-02-25 08:38:37 -06:00
|
|
|
type IssueWithLabels = IssueRow & { labels: IssueLabelRow[]; labelIds: string[] };
|
2026-03-02 16:43:59 -06:00
|
|
|
type IssueWithLabelsAndRun = IssueWithLabels & { activeRun: IssueActiveRunRow | null };
|
2026-03-06 08:21:03 -06:00
|
|
|
type IssueUserCommentStats = {
|
|
|
|
|
issueId: string;
|
|
|
|
|
myLastCommentAt: Date | null;
|
|
|
|
|
lastExternalCommentAt: Date | null;
|
|
|
|
|
};
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
type IssueReadStat = {
|
|
|
|
|
issueId: string;
|
|
|
|
|
myLastReadAt: Date | null;
|
|
|
|
|
};
|
2026-04-02 11:38:57 -05:00
|
|
|
type IssueLastActivityStat = {
|
|
|
|
|
issueId: string;
|
|
|
|
|
latestCommentAt: Date | null;
|
|
|
|
|
latestLogAt: Date | null;
|
|
|
|
|
};
|
2026-03-06 08:21:03 -06:00
|
|
|
type IssueUserContextInput = {
|
|
|
|
|
createdByUserId: string | null;
|
|
|
|
|
assigneeUserId: string | null;
|
2026-03-06 09:03:27 -06:00
|
|
|
createdAt: Date | string;
|
|
|
|
|
updatedAt: Date | string;
|
2026-03-06 08:21:03 -06:00
|
|
|
};
|
2026-03-24 08:11:09 -05:00
|
|
|
type ProjectGoalReader = Pick<Db, "select">;
|
2026-03-30 14:08:44 -05:00
|
|
|
type DbReader = Pick<Db, "select">;
|
|
|
|
|
type IssueCreateInput = Omit<typeof issues.$inferInsert, "companyId"> & {
|
|
|
|
|
labelIds?: string[];
|
2026-04-04 13:56:04 -05:00
|
|
|
blockedByIssueIds?: string[];
|
2026-03-30 14:08:44 -05:00
|
|
|
inheritExecutionWorkspaceFromIssueId?: string | null;
|
|
|
|
|
};
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
type IssueChildCreateInput = IssueCreateInput & {
|
|
|
|
|
acceptanceCriteria?: string[];
|
|
|
|
|
blockParentUntilDone?: boolean;
|
|
|
|
|
actorAgentId?: string | null;
|
|
|
|
|
actorUserId?: string | null;
|
|
|
|
|
};
|
2026-04-04 13:56:04 -05:00
|
|
|
type IssueRelationSummaryMap = {
|
|
|
|
|
blockedBy: IssueRelationIssueSummary[];
|
|
|
|
|
blocks: IssueRelationIssueSummary[];
|
|
|
|
|
};
|
2026-04-20 16:03:57 -05:00
|
|
|
export type IssueDependencyReadiness = {
|
|
|
|
|
issueId: string;
|
|
|
|
|
blockerIssueIds: string[];
|
|
|
|
|
unresolvedBlockerIssueIds: string[];
|
|
|
|
|
unresolvedBlockerCount: number;
|
|
|
|
|
allBlockersDone: boolean;
|
|
|
|
|
isDependencyReady: boolean;
|
|
|
|
|
};
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
export type ChildIssueCompletionSummary = {
|
|
|
|
|
id: string;
|
|
|
|
|
identifier: string | null;
|
|
|
|
|
title: string;
|
|
|
|
|
status: string;
|
|
|
|
|
priority: string;
|
|
|
|
|
assigneeAgentId: string | null;
|
|
|
|
|
assigneeUserId: string | null;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
summary: string | null;
|
|
|
|
|
};
|
2026-02-25 08:38:37 -06:00
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
function sameRunLock(checkoutRunId: string | null, actorRunId: string | null) {
|
|
|
|
|
if (actorRunId) return checkoutRunId === actorRunId;
|
|
|
|
|
return checkoutRunId == null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 08:23:44 -06:00
|
|
|
const TERMINAL_HEARTBEAT_RUN_STATUSES = new Set(["succeeded", "failed", "cancelled", "timed_out"]);
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
const ISSUE_LIST_DESCRIPTION_MAX_CHARS = 1200;
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
const ISSUE_LIST_DESCRIPTION_MAX_BYTES = ISSUE_LIST_DESCRIPTION_MAX_CHARS * 4;
|
2026-02-21 08:23:44 -06:00
|
|
|
|
2026-02-26 16:33:39 -06:00
|
|
|
function escapeLikePattern(value: string): string {
|
|
|
|
|
return value.replace(/[\\%_]/g, "\\$&");
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
export function clampIssueListLimit(limit: number): number {
|
|
|
|
|
return Math.min(ISSUE_LIST_MAX_LIMIT, Math.max(1, Math.floor(limit)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function chunkList<T>(values: T[], size: number): T[][] {
|
|
|
|
|
const chunks: T[][] = [];
|
|
|
|
|
for (let index = 0; index < values.length; index += size) {
|
|
|
|
|
chunks.push(values.slice(index, index + size));
|
|
|
|
|
}
|
|
|
|
|
return chunks;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
function truncateInlineSummary(value: string | null | undefined, maxChars = CHILD_COMPLETION_SUMMARY_BODY_MAX_CHARS) {
|
|
|
|
|
const normalized = value?.trim();
|
|
|
|
|
if (!normalized) return null;
|
|
|
|
|
return normalized.length > maxChars ? `${normalized.slice(0, Math.max(0, maxChars - 15)).trimEnd()} [truncated]` : normalized;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
function truncateByCodePoint(value: string, maxChars: number): string {
|
|
|
|
|
if (value.length <= maxChars) return value;
|
|
|
|
|
return Array.from(value).slice(0, maxChars).join("");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function decodeDatabaseTextPreview(value: string | null | undefined, maxChars: number): string | null {
|
|
|
|
|
if (value == null) return null;
|
|
|
|
|
return truncateByCodePoint(Buffer.from(value, "base64").toString("utf8"), maxChars);
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
function appendAcceptanceCriteriaToDescription(description: string | null | undefined, acceptanceCriteria: string[] | undefined) {
|
|
|
|
|
const criteria = (acceptanceCriteria ?? []).map((item) => item.trim()).filter(Boolean);
|
|
|
|
|
if (criteria.length === 0) return description ?? null;
|
|
|
|
|
const base = description?.trim() ?? "";
|
|
|
|
|
const criteriaMarkdown = ["## Acceptance Criteria", "", ...criteria.map((item) => `- ${item}`)].join("\n");
|
|
|
|
|
return base ? `${base}\n\n${criteriaMarkdown}` : criteriaMarkdown;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-20 16:03:57 -05:00
|
|
|
function createIssueDependencyReadiness(issueId: string): IssueDependencyReadiness {
|
|
|
|
|
return {
|
|
|
|
|
issueId,
|
|
|
|
|
blockerIssueIds: [],
|
|
|
|
|
unresolvedBlockerIssueIds: [],
|
|
|
|
|
unresolvedBlockerCount: 0,
|
|
|
|
|
allBlockersDone: true,
|
|
|
|
|
isDependencyReady: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function listIssueDependencyReadinessMap(
|
|
|
|
|
dbOrTx: Pick<Db, "select">,
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
) {
|
|
|
|
|
const uniqueIssueIds = [...new Set(issueIds.filter(Boolean))];
|
|
|
|
|
const readinessMap = new Map<string, IssueDependencyReadiness>();
|
|
|
|
|
for (const issueId of uniqueIssueIds) {
|
|
|
|
|
readinessMap.set(issueId, createIssueDependencyReadiness(issueId));
|
|
|
|
|
}
|
|
|
|
|
if (uniqueIssueIds.length === 0) return readinessMap;
|
|
|
|
|
|
|
|
|
|
const blockerRows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueRelations.relatedIssueId,
|
|
|
|
|
blockerIssueId: issueRelations.issueId,
|
|
|
|
|
blockerStatus: issues.status,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, uniqueIssueIds),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const row of blockerRows) {
|
|
|
|
|
const current = readinessMap.get(row.issueId) ?? createIssueDependencyReadiness(row.issueId);
|
|
|
|
|
current.blockerIssueIds.push(row.blockerIssueId);
|
|
|
|
|
// Only done blockers resolve dependents; cancelled blockers stay unresolved
|
|
|
|
|
// until an operator removes or replaces the blocker relationship explicitly.
|
|
|
|
|
if (row.blockerStatus !== "done") {
|
|
|
|
|
current.unresolvedBlockerIssueIds.push(row.blockerIssueId);
|
|
|
|
|
current.unresolvedBlockerCount += 1;
|
|
|
|
|
current.allBlockersDone = false;
|
|
|
|
|
current.isDependencyReady = false;
|
|
|
|
|
}
|
|
|
|
|
readinessMap.set(row.issueId, current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return readinessMap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function listUnresolvedBlockerIssueIds(
|
|
|
|
|
dbOrTx: Pick<Db, "select">,
|
|
|
|
|
companyId: string,
|
|
|
|
|
blockerIssueIds: string[],
|
|
|
|
|
) {
|
|
|
|
|
const uniqueBlockerIssueIds = [...new Set(blockerIssueIds.filter(Boolean))];
|
|
|
|
|
if (uniqueBlockerIssueIds.length === 0) return [];
|
|
|
|
|
return dbOrTx
|
|
|
|
|
.select({ id: issues.id })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
inArray(issues.id, uniqueBlockerIssueIds),
|
|
|
|
|
// Cancelled blockers intentionally remain unresolved until the relation changes.
|
|
|
|
|
ne(issues.status, "done"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.then((rows) => rows.map((row) => row.id));
|
|
|
|
|
}
|
2026-03-24 08:11:09 -05:00
|
|
|
async function getProjectDefaultGoalId(
|
|
|
|
|
db: ProjectGoalReader,
|
|
|
|
|
companyId: string,
|
|
|
|
|
projectId: string | null | undefined,
|
|
|
|
|
) {
|
|
|
|
|
if (!projectId) return null;
|
|
|
|
|
const row = await db
|
|
|
|
|
.select({ goalId: projects.goalId })
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(and(eq(projects.id, projectId), eq(projects.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
return row?.goalId ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 14:08:44 -05:00
|
|
|
async function getWorkspaceInheritanceIssue(
|
|
|
|
|
db: DbReader,
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueId: string,
|
|
|
|
|
) {
|
|
|
|
|
const issue = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
projectId: issues.projectId,
|
|
|
|
|
projectWorkspaceId: issues.projectWorkspaceId,
|
|
|
|
|
executionWorkspaceId: issues.executionWorkspaceId,
|
|
|
|
|
executionWorkspaceSettings: issues.executionWorkspaceSettings,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(and(eq(issues.id, issueId), eq(issues.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issue) {
|
|
|
|
|
throw notFound("Workspace inheritance issue not found");
|
|
|
|
|
}
|
|
|
|
|
return issue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:21:03 -06:00
|
|
|
function touchedByUserCondition(companyId: string, userId: string) {
|
|
|
|
|
return sql<boolean>`
|
|
|
|
|
(
|
|
|
|
|
${issues.createdByUserId} = ${userId}
|
|
|
|
|
OR ${issues.assigneeUserId} = ${userId}
|
2026-03-06 08:34:19 -06:00
|
|
|
OR EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueReadStates}
|
|
|
|
|
WHERE ${issueReadStates.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueReadStates.companyId} = ${companyId}
|
|
|
|
|
AND ${issueReadStates.userId} = ${userId}
|
|
|
|
|
)
|
2026-03-06 08:21:03 -06:00
|
|
|
OR EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND ${issueComments.authorUserId} = ${userId}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 12:20:48 -05:00
|
|
|
function participatedByAgentCondition(companyId: string, agentId: string) {
|
|
|
|
|
return sql<boolean>`
|
|
|
|
|
(
|
|
|
|
|
${issues.createdByAgentId} = ${agentId}
|
|
|
|
|
OR ${issues.assigneeAgentId} = ${agentId}
|
|
|
|
|
OR EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND ${issueComments.authorAgentId} = ${agentId}
|
|
|
|
|
)
|
|
|
|
|
OR EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${activityLog}
|
|
|
|
|
WHERE ${activityLog.companyId} = ${companyId}
|
|
|
|
|
AND ${activityLog.entityType} = 'issue'
|
|
|
|
|
AND ${activityLog.entityId} = ${issues.id}::text
|
|
|
|
|
AND ${activityLog.agentId} = ${agentId}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:21:03 -06:00
|
|
|
function myLastCommentAtExpr(companyId: string, userId: string) {
|
|
|
|
|
return sql<Date | null>`
|
|
|
|
|
(
|
|
|
|
|
SELECT MAX(${issueComments.createdAt})
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND ${issueComments.authorUserId} = ${userId}
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:34:19 -06:00
|
|
|
function myLastReadAtExpr(companyId: string, userId: string) {
|
|
|
|
|
return sql<Date | null>`
|
|
|
|
|
(
|
|
|
|
|
SELECT MAX(${issueReadStates.lastReadAt})
|
|
|
|
|
FROM ${issueReadStates}
|
|
|
|
|
WHERE ${issueReadStates.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueReadStates.companyId} = ${companyId}
|
|
|
|
|
AND ${issueReadStates.userId} = ${userId}
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:21:03 -06:00
|
|
|
function myLastTouchAtExpr(companyId: string, userId: string) {
|
|
|
|
|
const myLastCommentAt = myLastCommentAtExpr(companyId, userId);
|
2026-03-06 08:34:19 -06:00
|
|
|
const myLastReadAt = myLastReadAtExpr(companyId, userId);
|
2026-03-06 08:21:03 -06:00
|
|
|
return sql<Date | null>`
|
2026-03-06 08:34:19 -06:00
|
|
|
GREATEST(
|
|
|
|
|
COALESCE(${myLastCommentAt}, to_timestamp(0)),
|
|
|
|
|
COALESCE(${myLastReadAt}, to_timestamp(0)),
|
|
|
|
|
COALESCE(CASE WHEN ${issues.createdByUserId} = ${userId} THEN ${issues.createdAt} ELSE NULL END, to_timestamp(0)),
|
|
|
|
|
COALESCE(CASE WHEN ${issues.assigneeUserId} = ${userId} THEN ${issues.updatedAt} ELSE NULL END, to_timestamp(0))
|
2026-03-06 08:21:03 -06:00
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 08:19:16 -05:00
|
|
|
function lastExternalCommentAtExpr(companyId: string, userId: string) {
|
|
|
|
|
return sql<Date | null>`
|
|
|
|
|
(
|
|
|
|
|
SELECT MAX(${issueComments.createdAt})
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND (
|
|
|
|
|
${issueComments.authorUserId} IS NULL
|
|
|
|
|
OR ${issueComments.authorUserId} <> ${userId}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function issueLastActivityAtExpr(companyId: string, userId: string) {
|
|
|
|
|
const lastExternalCommentAt = lastExternalCommentAtExpr(companyId, userId);
|
|
|
|
|
const myLastTouchAt = myLastTouchAtExpr(companyId, userId);
|
|
|
|
|
return sql<Date>`
|
2026-04-02 11:38:57 -05:00
|
|
|
GREATEST(
|
|
|
|
|
COALESCE(${lastExternalCommentAt}, to_timestamp(0)),
|
2026-03-26 08:19:16 -05:00
|
|
|
CASE
|
|
|
|
|
WHEN ${issues.updatedAt} > COALESCE(${myLastTouchAt}, to_timestamp(0))
|
|
|
|
|
THEN ${issues.updatedAt}
|
|
|
|
|
ELSE to_timestamp(0)
|
|
|
|
|
END
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 11:38:57 -05:00
|
|
|
const ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS = [
|
|
|
|
|
"issue.read_marked",
|
|
|
|
|
"issue.read_unmarked",
|
|
|
|
|
"issue.inbox_archived",
|
|
|
|
|
"issue.inbox_unarchived",
|
|
|
|
|
] as const;
|
|
|
|
|
|
|
|
|
|
function issueLatestCommentAtExpr(companyId: string) {
|
|
|
|
|
return sql<Date | null>`
|
|
|
|
|
(
|
|
|
|
|
SELECT MAX(${issueComments.createdAt})
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function issueLatestLogAtExpr(companyId: string) {
|
|
|
|
|
return sql<Date | null>`
|
|
|
|
|
(
|
|
|
|
|
SELECT MAX(${activityLog.createdAt})
|
|
|
|
|
FROM ${activityLog}
|
|
|
|
|
WHERE ${activityLog.companyId} = ${companyId}
|
|
|
|
|
AND ${activityLog.entityType} = 'issue'
|
|
|
|
|
AND ${activityLog.entityId} = ${issues.id}::text
|
|
|
|
|
AND ${activityLog.action} NOT IN (${sql.join(
|
|
|
|
|
ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql`${action}`),
|
|
|
|
|
sql`, `,
|
|
|
|
|
)})
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function issueCanonicalLastActivityAtExpr(companyId: string) {
|
|
|
|
|
const latestCommentAt = issueLatestCommentAtExpr(companyId);
|
|
|
|
|
const latestLogAt = issueLatestLogAtExpr(companyId);
|
|
|
|
|
return sql<Date>`
|
|
|
|
|
GREATEST(
|
|
|
|
|
${issues.updatedAt},
|
|
|
|
|
COALESCE(${latestCommentAt}, to_timestamp(0)),
|
|
|
|
|
COALESCE(${latestLogAt}, to_timestamp(0))
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:21:03 -06:00
|
|
|
function unreadForUserCondition(companyId: string, userId: string) {
|
|
|
|
|
const touchedCondition = touchedByUserCondition(companyId, userId);
|
|
|
|
|
const myLastTouchAt = myLastTouchAtExpr(companyId, userId);
|
|
|
|
|
return sql<boolean>`
|
|
|
|
|
(
|
|
|
|
|
${touchedCondition}
|
|
|
|
|
AND EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND (
|
|
|
|
|
${issueComments.authorUserId} IS NULL
|
|
|
|
|
OR ${issueComments.authorUserId} <> ${userId}
|
|
|
|
|
)
|
|
|
|
|
AND ${issueComments.createdAt} > ${myLastTouchAt}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 08:19:16 -05:00
|
|
|
function inboxVisibleForUserCondition(companyId: string, userId: string) {
|
|
|
|
|
const issueLastActivityAt = issueLastActivityAtExpr(companyId, userId);
|
|
|
|
|
return sql<boolean>`
|
|
|
|
|
NOT EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueInboxArchives}
|
|
|
|
|
WHERE ${issueInboxArchives.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueInboxArchives.companyId} = ${companyId}
|
|
|
|
|
AND ${issueInboxArchives.userId} = ${userId}
|
|
|
|
|
AND ${issueInboxArchives.archivedAt} >= ${issueLastActivityAt}
|
|
|
|
|
)
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-24 15:22:21 +02:00
|
|
|
/** Named entities commonly emitted in saved issue bodies; unknown `&name;` sequences are left unchanged. */
|
|
|
|
|
const WELL_KNOWN_NAMED_HTML_ENTITIES: Readonly<Record<string, string>> = {
|
|
|
|
|
amp: "&",
|
|
|
|
|
apos: "'",
|
|
|
|
|
copy: "\u00A9",
|
|
|
|
|
gt: ">",
|
|
|
|
|
lt: "<",
|
|
|
|
|
nbsp: "\u00A0",
|
|
|
|
|
quot: '"',
|
|
|
|
|
ensp: "\u2002",
|
|
|
|
|
emsp: "\u2003",
|
|
|
|
|
thinsp: "\u2009",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function decodeNumericHtmlEntity(digits: string, radix: 16 | 10): string | null {
|
|
|
|
|
const n = Number.parseInt(digits, radix);
|
|
|
|
|
if (Number.isNaN(n) || n < 0 || n > 0x10ffff) return null;
|
|
|
|
|
try {
|
|
|
|
|
return String.fromCodePoint(n);
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Decodes HTML character references in a raw @mention capture so UI-encoded bodies match agent names. */
|
2026-03-20 16:38:55 +00:00
|
|
|
export function normalizeAgentMentionToken(raw: string): string {
|
2026-03-24 15:22:21 +02:00
|
|
|
let s = raw.replace(/&#x([0-9a-fA-F]+);/gi, (full, hex: string) => decodeNumericHtmlEntity(hex, 16) ?? full);
|
|
|
|
|
s = s.replace(/&#([0-9]+);/g, (full, dec: string) => decodeNumericHtmlEntity(dec, 10) ?? full);
|
|
|
|
|
s = s.replace(/&([a-z][a-z0-9]*);/gi, (full, name: string) => {
|
|
|
|
|
const decoded = WELL_KNOWN_NAMED_HTML_ENTITIES[name.toLowerCase()];
|
|
|
|
|
return decoded !== undefined ? decoded : full;
|
|
|
|
|
});
|
|
|
|
|
return s.trim();
|
2026-03-20 16:38:55 +00:00
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:21:03 -06:00
|
|
|
export function deriveIssueUserContext(
|
|
|
|
|
issue: IssueUserContextInput,
|
|
|
|
|
userId: string,
|
2026-03-06 08:34:19 -06:00
|
|
|
stats:
|
2026-03-06 09:03:27 -06:00
|
|
|
| {
|
|
|
|
|
myLastCommentAt: Date | string | null;
|
|
|
|
|
myLastReadAt: Date | string | null;
|
|
|
|
|
lastExternalCommentAt: Date | string | null;
|
|
|
|
|
}
|
2026-03-06 08:34:19 -06:00
|
|
|
| null
|
|
|
|
|
| undefined,
|
2026-03-06 08:21:03 -06:00
|
|
|
) {
|
2026-03-06 09:03:27 -06:00
|
|
|
const normalizeDate = (value: Date | string | null | undefined) => {
|
|
|
|
|
if (!value) return null;
|
|
|
|
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
|
|
|
const parsed = new Date(value);
|
|
|
|
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const myLastCommentAt = normalizeDate(stats?.myLastCommentAt);
|
|
|
|
|
const myLastReadAt = normalizeDate(stats?.myLastReadAt);
|
|
|
|
|
const createdTouchAt = issue.createdByUserId === userId ? normalizeDate(issue.createdAt) : null;
|
|
|
|
|
const assignedTouchAt = issue.assigneeUserId === userId ? normalizeDate(issue.updatedAt) : null;
|
2026-03-06 08:34:19 -06:00
|
|
|
const myLastTouchAt = [myLastCommentAt, myLastReadAt, createdTouchAt, assignedTouchAt]
|
|
|
|
|
.filter((value): value is Date => value instanceof Date)
|
|
|
|
|
.sort((a, b) => b.getTime() - a.getTime())[0] ?? null;
|
2026-03-06 09:03:27 -06:00
|
|
|
const lastExternalCommentAt = normalizeDate(stats?.lastExternalCommentAt);
|
2026-03-06 08:21:03 -06:00
|
|
|
const isUnreadForMe = Boolean(
|
|
|
|
|
myLastTouchAt &&
|
|
|
|
|
lastExternalCommentAt &&
|
|
|
|
|
lastExternalCommentAt.getTime() > myLastTouchAt.getTime(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
myLastTouchAt,
|
|
|
|
|
lastExternalCommentAt,
|
|
|
|
|
isUnreadForMe,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 11:38:57 -05:00
|
|
|
function latestIssueActivityAt(...values: Array<Date | string | null | undefined>): Date | null {
|
|
|
|
|
const normalized = values
|
|
|
|
|
.map((value) => {
|
|
|
|
|
if (!value) return null;
|
|
|
|
|
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
|
|
|
const parsed = new Date(value);
|
|
|
|
|
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
|
|
|
})
|
|
|
|
|
.filter((value): value is Date => value instanceof Date)
|
|
|
|
|
.sort((a, b) => b.getTime() - a.getTime());
|
|
|
|
|
return normalized[0] ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
async function labelMapForIssues(dbOrTx: any, issueIds: string[]): Promise<Map<string, IssueLabelRow[]>> {
|
|
|
|
|
const map = new Map<string, IssueLabelRow[]>();
|
|
|
|
|
if (issueIds.length === 0) return map;
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueLabels.issueId,
|
|
|
|
|
label: labels,
|
|
|
|
|
})
|
|
|
|
|
.from(issueLabels)
|
|
|
|
|
.innerJoin(labels, eq(issueLabels.labelId, labels.id))
|
|
|
|
|
.where(inArray(issueLabels.issueId, issueIdChunk))
|
|
|
|
|
.orderBy(asc(labels.name), asc(labels.id));
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const existing = map.get(row.issueId);
|
|
|
|
|
if (existing) existing.push(row.label);
|
|
|
|
|
else map.set(row.issueId, [row.label]);
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
}
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function withIssueLabels(dbOrTx: any, rows: IssueRow[]): Promise<IssueWithLabels[]> {
|
|
|
|
|
if (rows.length === 0) return [];
|
|
|
|
|
const labelsByIssueId = await labelMapForIssues(dbOrTx, rows.map((row) => row.id));
|
|
|
|
|
return rows.map((row) => {
|
|
|
|
|
const issueLabels = labelsByIssueId.get(row.id) ?? [];
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
labels: issueLabels,
|
|
|
|
|
labelIds: issueLabels.map((label) => label.id),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 16:43:59 -06:00
|
|
|
const ACTIVE_RUN_STATUSES = ["queued", "running"];
|
2026-04-24 15:50:32 -05:00
|
|
|
const BLOCKER_ATTENTION_ACTIVE_RUN_STATUSES = ["queued", "running"];
|
|
|
|
|
const BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES = ["queued", "deferred_issue_execution"];
|
2026-04-26 21:17:38 -05:00
|
|
|
const BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES = ["pending"];
|
|
|
|
|
const BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES = ["pending", "revision_requested"];
|
|
|
|
|
const BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND = "harness_liveness_escalation";
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
const PRODUCTIVITY_REVIEW_ORIGIN_KIND = "issue_productivity_review";
|
|
|
|
|
const PRODUCTIVITY_REVIEW_TERMINAL_STATUSES = ["done", "cancelled"];
|
|
|
|
|
const PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS = [
|
|
|
|
|
"issue.productivity_review_created",
|
|
|
|
|
"issue.productivity_review_updated",
|
|
|
|
|
];
|
|
|
|
|
const PRODUCTIVITY_REVIEW_TRIGGERS: readonly IssueProductivityReviewTrigger[] = [
|
|
|
|
|
"no_comment_streak",
|
|
|
|
|
"long_active_duration",
|
|
|
|
|
"high_churn",
|
|
|
|
|
];
|
2026-04-26 21:17:38 -05:00
|
|
|
const BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES = ["done", "cancelled"];
|
2026-04-24 15:50:32 -05:00
|
|
|
const BLOCKER_ATTENTION_MAX_DEPTH = 8;
|
|
|
|
|
const BLOCKER_ATTENTION_MAX_NODES = 2000;
|
|
|
|
|
const BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES = new Set(["active", "idle", "running", "error"]);
|
|
|
|
|
|
|
|
|
|
type IssueBlockerAttentionNode = {
|
|
|
|
|
id: string;
|
|
|
|
|
companyId: string;
|
|
|
|
|
parentId: string | null;
|
|
|
|
|
identifier: string | null;
|
|
|
|
|
title: string;
|
|
|
|
|
status: string;
|
|
|
|
|
executionRunId?: string | null;
|
|
|
|
|
assigneeAgentId: string | null;
|
|
|
|
|
assigneeUserId: string | null;
|
|
|
|
|
};
|
|
|
|
|
type IssueBlockerAttentionInputNode =
|
|
|
|
|
Pick<
|
|
|
|
|
IssueBlockerAttentionNode,
|
|
|
|
|
"id" | "companyId" | "parentId" | "identifier" | "title" | "status" | "assigneeAgentId" | "assigneeUserId"
|
|
|
|
|
>
|
|
|
|
|
& { executionRunId?: string | null };
|
|
|
|
|
|
|
|
|
|
type IssueBlockerAttentionEdge = {
|
|
|
|
|
issueId: string;
|
|
|
|
|
blockerIssueId: string;
|
|
|
|
|
};
|
|
|
|
|
type IssueBlockerAttentionQueryRow = IssueBlockerAttentionNode & {
|
|
|
|
|
issueId: string | null;
|
|
|
|
|
blockerIssueId: string;
|
|
|
|
|
};
|
|
|
|
|
type IssueBlockerAttentionActivePathRow = {
|
|
|
|
|
issueId: string | null;
|
|
|
|
|
};
|
|
|
|
|
type IssueBlockerAttentionAgentRow = {
|
|
|
|
|
id: string;
|
|
|
|
|
companyId: string;
|
|
|
|
|
status: string;
|
|
|
|
|
};
|
2026-03-02 16:43:59 -06:00
|
|
|
|
|
|
|
|
async function activeRunMapForIssues(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
issueRows: IssueWithLabels[],
|
|
|
|
|
): Promise<Map<string, IssueActiveRunRow>> {
|
|
|
|
|
const map = new Map<string, IssueActiveRunRow>();
|
|
|
|
|
const runIds = issueRows
|
|
|
|
|
.map((row) => row.executionRunId)
|
|
|
|
|
.filter((id): id is string => id != null);
|
|
|
|
|
if (runIds.length === 0) return map;
|
|
|
|
|
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
for (const runIdChunk of chunkList([...new Set(runIds)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
id: heartbeatRuns.id,
|
|
|
|
|
status: heartbeatRuns.status,
|
|
|
|
|
agentId: heartbeatRuns.agentId,
|
|
|
|
|
invocationSource: heartbeatRuns.invocationSource,
|
|
|
|
|
triggerDetail: heartbeatRuns.triggerDetail,
|
|
|
|
|
startedAt: heartbeatRuns.startedAt,
|
|
|
|
|
finishedAt: heartbeatRuns.finishedAt,
|
|
|
|
|
createdAt: heartbeatRuns.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
inArray(heartbeatRuns.id, runIdChunk),
|
|
|
|
|
inArray(heartbeatRuns.status, ACTIVE_RUN_STATUSES),
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-03-02 16:43:59 -06:00
|
|
|
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
for (const row of rows) {
|
|
|
|
|
map.set(row.id, row);
|
|
|
|
|
}
|
2026-03-02 16:43:59 -06:00
|
|
|
}
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 15:50:32 -05:00
|
|
|
function createIssueBlockerAttention(input: Partial<IssueBlockerAttention> = {}): IssueBlockerAttention {
|
|
|
|
|
return {
|
|
|
|
|
state: input.state ?? "none",
|
|
|
|
|
reason: input.reason ?? null,
|
|
|
|
|
unresolvedBlockerCount: input.unresolvedBlockerCount ?? 0,
|
|
|
|
|
coveredBlockerCount: input.coveredBlockerCount ?? 0,
|
2026-04-26 21:17:38 -05:00
|
|
|
stalledBlockerCount: input.stalledBlockerCount ?? 0,
|
2026-04-24 15:50:32 -05:00
|
|
|
attentionBlockerCount: input.attentionBlockerCount ?? 0,
|
|
|
|
|
sampleBlockerIdentifier: input.sampleBlockerIdentifier ?? null,
|
2026-04-26 21:17:38 -05:00
|
|
|
sampleStalledBlockerIdentifier: input.sampleStalledBlockerIdentifier ?? null,
|
2026-04-24 15:50:32 -05:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function blockerSampleIdentifier(node: IssueBlockerAttentionNode | null | undefined) {
|
|
|
|
|
return node?.identifier ?? node?.id ?? null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function appendBlockerAttentionEdges(
|
|
|
|
|
edgesByIssueId: Map<string, IssueBlockerAttentionEdge[]>,
|
|
|
|
|
rows: IssueBlockerAttentionEdge[],
|
|
|
|
|
) {
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const existing = edgesByIssueId.get(row.issueId) ?? [];
|
|
|
|
|
if (!existing.some((edge) => edge.blockerIssueId === row.blockerIssueId)) {
|
|
|
|
|
existing.push(row);
|
|
|
|
|
edgesByIssueId.set(row.issueId, existing);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type IssueRelationSummaryRow = {
|
|
|
|
|
relatedId: string;
|
|
|
|
|
identifier: string | null;
|
|
|
|
|
title: string;
|
|
|
|
|
status: string;
|
|
|
|
|
priority: string;
|
|
|
|
|
assigneeAgentId: string | null;
|
|
|
|
|
assigneeUserId: string | null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function summarizeIssueRelationRow(row: IssueRelationSummaryRow): IssueRelationIssueSummary {
|
|
|
|
|
return {
|
|
|
|
|
id: row.relatedId,
|
|
|
|
|
identifier: row.identifier,
|
|
|
|
|
title: row.title,
|
|
|
|
|
status: row.status as IssueRelationIssueSummary["status"],
|
|
|
|
|
priority: row.priority as IssueRelationIssueSummary["priority"],
|
|
|
|
|
assigneeAgentId: row.assigneeAgentId,
|
|
|
|
|
assigneeUserId: row.assigneeUserId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function terminalExplicitBlockersByRoot(
|
|
|
|
|
companyId: string,
|
|
|
|
|
roots: IssueRelationIssueSummary[],
|
|
|
|
|
dbOrTx: DbReader,
|
|
|
|
|
): Promise<Map<string, IssueRelationIssueSummary[]>> {
|
|
|
|
|
const rootIds = [...new Set(roots.map((root) => root.id))];
|
|
|
|
|
const terminalByRoot = new Map<string, IssueRelationIssueSummary[]>();
|
|
|
|
|
if (rootIds.length === 0) return terminalByRoot;
|
|
|
|
|
|
|
|
|
|
const nodesById = new Map<string, IssueRelationIssueSummary>();
|
|
|
|
|
const edgesByIssueId = new Map<string, string[]>();
|
|
|
|
|
for (const root of roots) nodesById.set(root.id, root);
|
|
|
|
|
|
|
|
|
|
let frontier = rootIds;
|
|
|
|
|
for (let depth = 0; frontier.length > 0 && depth < BLOCKER_ATTENTION_MAX_DEPTH; depth += 1) {
|
|
|
|
|
const nextFrontier = new Set<string>();
|
|
|
|
|
for (const chunk of chunkList([...new Set(frontier)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
currentIssueId: issueRelations.relatedIssueId,
|
|
|
|
|
relatedId: issues.id,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, chunk),
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
ne(issues.status, "done"),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const existingEdges = edgesByIssueId.get(row.currentIssueId) ?? [];
|
|
|
|
|
if (!existingEdges.includes(row.relatedId)) {
|
|
|
|
|
existingEdges.push(row.relatedId);
|
|
|
|
|
edgesByIssueId.set(row.currentIssueId, existingEdges);
|
|
|
|
|
}
|
|
|
|
|
if (!nodesById.has(row.relatedId)) {
|
|
|
|
|
nodesById.set(row.relatedId, summarizeIssueRelationRow(row));
|
|
|
|
|
nextFrontier.add(row.relatedId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nodesById.size > BLOCKER_ATTENTION_MAX_NODES) break;
|
|
|
|
|
frontier = [...nextFrontier];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const collectTerminal = (issueId: string, seen: Set<string>): IssueRelationIssueSummary[] => {
|
|
|
|
|
if (seen.has(issueId)) return [];
|
|
|
|
|
const node = nodesById.get(issueId);
|
|
|
|
|
if (!node || node.status === "done") return [];
|
|
|
|
|
const nextSeen = new Set(seen);
|
|
|
|
|
nextSeen.add(issueId);
|
|
|
|
|
const downstreamIds = edgesByIssueId.get(issueId) ?? [];
|
|
|
|
|
if (downstreamIds.length === 0) return [node];
|
|
|
|
|
return downstreamIds.flatMap((downstreamId) => collectTerminal(downstreamId, nextSeen));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const rootId of rootIds) {
|
|
|
|
|
const deduped = new Map<string, IssueRelationIssueSummary>();
|
|
|
|
|
for (const blocker of collectTerminal(rootId, new Set())) {
|
|
|
|
|
if (blocker.id !== rootId) deduped.set(blocker.id, blocker);
|
|
|
|
|
}
|
|
|
|
|
if (deduped.size > 0) {
|
|
|
|
|
terminalByRoot.set(rootId, [...deduped.values()].sort((a, b) => a.title.localeCompare(b.title)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return terminalByRoot;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
function readProductivityReviewTrigger(value: unknown): IssueProductivityReviewTrigger | null {
|
|
|
|
|
if (typeof value !== "string") return null;
|
|
|
|
|
return PRODUCTIVITY_REVIEW_TRIGGERS.includes(value as IssueProductivityReviewTrigger)
|
|
|
|
|
? (value as IssueProductivityReviewTrigger)
|
|
|
|
|
: null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readProductivityReviewStreak(value: unknown): number | null {
|
|
|
|
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return null;
|
|
|
|
|
return Math.floor(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function listIssueProductivityReviewMap(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
sourceIssueIds: string[],
|
|
|
|
|
): Promise<Map<string, IssueProductivityReview>> {
|
|
|
|
|
const map = new Map<string, IssueProductivityReview>();
|
|
|
|
|
if (sourceIssueIds.length === 0) return map;
|
|
|
|
|
|
|
|
|
|
const reviewRows: Array<{
|
|
|
|
|
sourceIssueId: string | null;
|
|
|
|
|
reviewIssueId: string;
|
|
|
|
|
reviewIdentifier: string | null;
|
|
|
|
|
status: string;
|
|
|
|
|
priority: string;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
}> = [];
|
|
|
|
|
for (const chunk of chunkList([...new Set(sourceIssueIds)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
sourceIssueId: issues.originId,
|
|
|
|
|
reviewIssueId: issues.id,
|
|
|
|
|
reviewIdentifier: issues.identifier,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
createdAt: issues.createdAt,
|
|
|
|
|
updatedAt: issues.updatedAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
eq(issues.originKind, PRODUCTIVITY_REVIEW_ORIGIN_KIND),
|
|
|
|
|
inArray(issues.originId, chunk),
|
|
|
|
|
isNull(issues.hiddenAt),
|
|
|
|
|
notInArray(issues.status, PRODUCTIVITY_REVIEW_TERMINAL_STATUSES),
|
|
|
|
|
),
|
[codex] Split PR #4692 UI/QoL updates (#4701)
## Thinking Path
> - Paperclip orchestrates AI agents through a company-scoped control
plane.
> - The affected surface is the board UI for issue threads, issue lists,
routines, dialogs, navigation, and issue review indicators.
> - Closed PR #4692 bundled backend, schema, docs, workflow, and UI/QoL
work into one oversized change set.
> - Greptile could not keep reviewing that broad PR because it exceeded
the 100-file review limit and mixed unrelated concerns.
> - This pull request extracts the UI/QoL slice into a fresh branch
under the review limit while leaving workflow and lockfile churn out.
> - The benefit is a focused review path for the board UI performance
and workflow improvements without reopening the oversized PR.
## What Changed
- Added long issue-thread virtualization, scroll-container binding,
anchor preservation, latest-comment jump targeting, and related
regression/perf fixtures.
- Improved issue list scalability with scroll-based loading, server
offset parameters, and pagination-focused UI tests.
- Reduced new issue dialog typing churn and split dialog action
subscriptions so broad layout/nav surfaces avoid unnecessary renders.
- Added routine variables help and routine description mention options
for users, agents, and projects.
- Added productivity review badge/link UI and fixed the badge to use
Paperclip's company-prefixed router link.
- Kept the split PR below Greptile's review limit and excluded
`.github/workflows/pr.yml` and `pnpm-lock.yaml`.
## Verification
- `pnpm install --no-frozen-lockfile` in the clean worktree to install
`@tanstack/react-virtual` locally without committing lockfile churn.
- `pnpm --filter @paperclipai/ui exec vitest run --config
vitest.config.ts src/components/IssueChatThread.test.tsx
src/components/IssuesList.test.tsx
src/components/NewIssueDialog.test.tsx src/pages/Routines.test.tsx
src/pages/Issues.test.tsx` passed: 5 files, 83 tests.
- `pnpm --filter @paperclipai/ui typecheck` passed.
- `git diff --check origin/master..HEAD` passed.
- Split-scope checks: 53 changed files; no `.github/workflows/pr.yml`;
no `pnpm-lock.yaml`.
- Screenshots were not captured in this heartbeat; the changes are
primarily virtualization, routing, pagination, and editor behavior
covered by focused regression tests.
## Risks
- Moderate UI risk because issue-thread virtualization changes scroll
behavior on long conversations; regression tests cover anchor jumps,
latest-comment targeting, row metadata, and short-thread fallback.
- Moderate integration risk because the issue-list offset parameter and
productivity review field depend on matching API behavior.
- Dependency risk: the UI package adds `@tanstack/react-virtual` while
repository policy keeps `pnpm-lock.yaml` out of PRs, so CI must resolve
dependency changes through the repo's normal lockfile policy.
> 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 coding agent, tool-enabled local repository and
GitHub workflow. Exact runtime context window was not exposed by the
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 17:18:58 -05:00
|
|
|
)
|
|
|
|
|
.orderBy(desc(issues.createdAt), desc(issues.id));
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
reviewRows.push(...rows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reviewRows.length === 0) return map;
|
|
|
|
|
|
|
|
|
|
const reviewIssueIds = reviewRows.map((row) => row.reviewIssueId);
|
|
|
|
|
const triggerByReviewIssueId = new Map<
|
|
|
|
|
string,
|
|
|
|
|
{ trigger: IssueProductivityReviewTrigger | null; noCommentStreak: number | null }
|
|
|
|
|
>();
|
|
|
|
|
for (const chunk of chunkList(reviewIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const detailRows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
entityId: activityLog.entityId,
|
|
|
|
|
details: activityLog.details,
|
|
|
|
|
createdAt: activityLog.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(activityLog)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(activityLog.companyId, companyId),
|
|
|
|
|
eq(activityLog.entityType, "issue"),
|
|
|
|
|
inArray(activityLog.entityId, chunk),
|
|
|
|
|
inArray(activityLog.action, PRODUCTIVITY_REVIEW_ACTIVITY_ACTIONS),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.orderBy(desc(activityLog.createdAt));
|
|
|
|
|
for (const row of detailRows as Array<{
|
|
|
|
|
entityId: string;
|
|
|
|
|
details: Record<string, unknown> | null;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
}>) {
|
|
|
|
|
if (triggerByReviewIssueId.has(row.entityId)) continue;
|
|
|
|
|
triggerByReviewIssueId.set(row.entityId, {
|
|
|
|
|
trigger: readProductivityReviewTrigger(row.details?.trigger),
|
|
|
|
|
noCommentStreak: readProductivityReviewStreak(row.details?.noCommentStreak),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const row of reviewRows) {
|
|
|
|
|
if (!row.sourceIssueId) continue;
|
[codex] Split PR #4692 UI/QoL updates (#4701)
## Thinking Path
> - Paperclip orchestrates AI agents through a company-scoped control
plane.
> - The affected surface is the board UI for issue threads, issue lists,
routines, dialogs, navigation, and issue review indicators.
> - Closed PR #4692 bundled backend, schema, docs, workflow, and UI/QoL
work into one oversized change set.
> - Greptile could not keep reviewing that broad PR because it exceeded
the 100-file review limit and mixed unrelated concerns.
> - This pull request extracts the UI/QoL slice into a fresh branch
under the review limit while leaving workflow and lockfile churn out.
> - The benefit is a focused review path for the board UI performance
and workflow improvements without reopening the oversized PR.
## What Changed
- Added long issue-thread virtualization, scroll-container binding,
anchor preservation, latest-comment jump targeting, and related
regression/perf fixtures.
- Improved issue list scalability with scroll-based loading, server
offset parameters, and pagination-focused UI tests.
- Reduced new issue dialog typing churn and split dialog action
subscriptions so broad layout/nav surfaces avoid unnecessary renders.
- Added routine variables help and routine description mention options
for users, agents, and projects.
- Added productivity review badge/link UI and fixed the badge to use
Paperclip's company-prefixed router link.
- Kept the split PR below Greptile's review limit and excluded
`.github/workflows/pr.yml` and `pnpm-lock.yaml`.
## Verification
- `pnpm install --no-frozen-lockfile` in the clean worktree to install
`@tanstack/react-virtual` locally without committing lockfile churn.
- `pnpm --filter @paperclipai/ui exec vitest run --config
vitest.config.ts src/components/IssueChatThread.test.tsx
src/components/IssuesList.test.tsx
src/components/NewIssueDialog.test.tsx src/pages/Routines.test.tsx
src/pages/Issues.test.tsx` passed: 5 files, 83 tests.
- `pnpm --filter @paperclipai/ui typecheck` passed.
- `git diff --check origin/master..HEAD` passed.
- Split-scope checks: 53 changed files; no `.github/workflows/pr.yml`;
no `pnpm-lock.yaml`.
- Screenshots were not captured in this heartbeat; the changes are
primarily virtualization, routing, pagination, and editor behavior
covered by focused regression tests.
## Risks
- Moderate UI risk because issue-thread virtualization changes scroll
behavior on long conversations; regression tests cover anchor jumps,
latest-comment targeting, row metadata, and short-thread fallback.
- Moderate integration risk because the issue-list offset parameter and
productivity review field depend on matching API behavior.
- Dependency risk: the UI package adds `@tanstack/react-virtual` while
repository policy keeps `pnpm-lock.yaml` out of PRs, so CI must resolve
dependency changes through the repo's normal lockfile policy.
> 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 coding agent, tool-enabled local repository and
GitHub workflow. Exact runtime context window was not exposed by the
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 17:18:58 -05:00
|
|
|
if (map.has(row.sourceIssueId)) continue;
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
const detail = triggerByReviewIssueId.get(row.reviewIssueId);
|
|
|
|
|
map.set(row.sourceIssueId, {
|
|
|
|
|
reviewIssueId: row.reviewIssueId,
|
|
|
|
|
reviewIdentifier: row.reviewIdentifier,
|
|
|
|
|
status: row.status as IssueProductivityReview["status"],
|
|
|
|
|
priority: row.priority as IssueProductivityReview["priority"],
|
|
|
|
|
trigger: detail?.trigger ?? null,
|
|
|
|
|
noCommentStreak: detail?.noCommentStreak ?? null,
|
|
|
|
|
createdAt: row.createdAt,
|
|
|
|
|
updatedAt: row.updatedAt,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 15:50:32 -05:00
|
|
|
async function listIssueBlockerAttentionMap(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueRows: IssueBlockerAttentionInputNode[],
|
|
|
|
|
): Promise<Map<string, IssueBlockerAttention>> {
|
|
|
|
|
const roots = issueRows.filter((row) => row.companyId === companyId && row.status === "blocked");
|
|
|
|
|
const attentionMap = new Map<string, IssueBlockerAttention>();
|
|
|
|
|
for (const row of issueRows) {
|
|
|
|
|
if (row.status !== "blocked") {
|
|
|
|
|
attentionMap.set(row.id, createIssueBlockerAttention());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (roots.length === 0) return attentionMap;
|
|
|
|
|
|
|
|
|
|
const nodesById = new Map<string, IssueBlockerAttentionNode>();
|
|
|
|
|
const edgesByIssueId = new Map<string, IssueBlockerAttentionEdge[]>();
|
|
|
|
|
for (const root of roots) nodesById.set(root.id, { ...root });
|
|
|
|
|
|
|
|
|
|
let frontier = roots.map((root) => root.id);
|
|
|
|
|
let truncated = false;
|
|
|
|
|
for (let depth = 0; frontier.length > 0 && depth < BLOCKER_ATTENTION_MAX_DEPTH; depth += 1) {
|
|
|
|
|
const nextFrontier = new Set<string>();
|
|
|
|
|
|
|
|
|
|
for (const chunk of chunkList([...new Set(frontier)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const explicitBlockerRowsPromise: Promise<IssueBlockerAttentionQueryRow[]> = dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueRelations.relatedIssueId,
|
|
|
|
|
blockerIssueId: issues.id,
|
|
|
|
|
id: issues.id,
|
|
|
|
|
companyId: issues.companyId,
|
|
|
|
|
parentId: issues.parentId,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, chunk),
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
ne(issues.status, "done"),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
const childRowsPromise: Promise<IssueBlockerAttentionQueryRow[]> = dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issues.parentId,
|
|
|
|
|
blockerIssueId: issues.id,
|
|
|
|
|
id: issues.id,
|
|
|
|
|
companyId: issues.companyId,
|
|
|
|
|
parentId: issues.parentId,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
inArray(issues.parentId, chunk),
|
|
|
|
|
ne(issues.status, "done"),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
const [explicitBlockerRows, childRows] = await Promise.all([
|
|
|
|
|
explicitBlockerRowsPromise,
|
|
|
|
|
childRowsPromise,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
appendBlockerAttentionEdges(edgesByIssueId, [
|
|
|
|
|
...explicitBlockerRows
|
|
|
|
|
.filter((row): row is IssueBlockerAttentionQueryRow & { issueId: string } => row.issueId !== null)
|
|
|
|
|
.map((row) => ({ issueId: row.issueId, blockerIssueId: row.blockerIssueId })),
|
|
|
|
|
...childRows
|
|
|
|
|
.filter((row): row is IssueBlockerAttentionQueryRow & { issueId: string } => row.issueId !== null)
|
|
|
|
|
.map((row) => ({ issueId: row.issueId, blockerIssueId: row.blockerIssueId })),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
for (const row of [...explicitBlockerRows, ...childRows]) {
|
|
|
|
|
if (!row.issueId || nodesById.has(row.blockerIssueId)) continue;
|
|
|
|
|
nodesById.set(row.blockerIssueId, {
|
|
|
|
|
id: row.blockerIssueId,
|
|
|
|
|
companyId: row.companyId,
|
|
|
|
|
parentId: row.parentId,
|
|
|
|
|
identifier: row.identifier,
|
|
|
|
|
title: row.title,
|
|
|
|
|
status: row.status,
|
|
|
|
|
executionRunId: row.executionRunId,
|
|
|
|
|
assigneeAgentId: row.assigneeAgentId,
|
|
|
|
|
assigneeUserId: row.assigneeUserId,
|
|
|
|
|
});
|
|
|
|
|
nextFrontier.add(row.blockerIssueId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nodesById.size > BLOCKER_ATTENTION_MAX_NODES) {
|
|
|
|
|
truncated = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
frontier = [...nextFrontier];
|
|
|
|
|
}
|
|
|
|
|
if (frontier.length > 0) truncated = true;
|
|
|
|
|
|
|
|
|
|
const nodeIds = [...nodesById.keys()];
|
|
|
|
|
const activeIssueIds = new Set<string>();
|
|
|
|
|
const agentIds = new Set<string>();
|
|
|
|
|
const issueIdByExecutionRunId = new Map<string, string>();
|
|
|
|
|
for (const node of nodesById.values()) {
|
|
|
|
|
if (node.assigneeAgentId) agentIds.add(node.assigneeAgentId);
|
|
|
|
|
if (node.executionRunId) issueIdByExecutionRunId.set(node.executionRunId, node.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const chunk of chunkList([...issueIdByExecutionRunId.keys()], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const runRows: Array<{ id: string }> = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
id: heartbeatRuns.id,
|
|
|
|
|
})
|
|
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(heartbeatRuns.companyId, companyId),
|
|
|
|
|
inArray(heartbeatRuns.status, BLOCKER_ATTENTION_ACTIVE_RUN_STATUSES),
|
|
|
|
|
inArray(heartbeatRuns.id, chunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const row of runRows) {
|
|
|
|
|
const issueId = issueIdByExecutionRunId.get(row.id);
|
|
|
|
|
if (issueId) activeIssueIds.add(issueId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const chunk of chunkList(nodeIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const wakeRowsPromise: Promise<IssueBlockerAttentionActivePathRow[]> = dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: sql<string | null>`${agentWakeupRequests.payload} ->> 'issueId'`,
|
|
|
|
|
})
|
|
|
|
|
.from(agentWakeupRequests)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(agentWakeupRequests.companyId, companyId),
|
|
|
|
|
inArray(agentWakeupRequests.status, BLOCKER_ATTENTION_ACTIVE_WAKE_STATUSES),
|
|
|
|
|
sql`${agentWakeupRequests.runId} is null`,
|
|
|
|
|
inArray(sql<string>`${agentWakeupRequests.payload} ->> 'issueId'`, chunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
const wakeRows = await wakeRowsPromise;
|
|
|
|
|
for (const row of wakeRows) {
|
|
|
|
|
if (row.issueId) activeIssueIds.add(row.issueId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
const explicitWaitCandidateIds = [...nodesById.values()]
|
|
|
|
|
.filter((node) => node.status !== "done")
|
2026-04-26 21:17:38 -05:00
|
|
|
.map((node) => node.id);
|
|
|
|
|
const explicitWaitingIssueIds = new Set<string>();
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
if (explicitWaitCandidateIds.length > 0) {
|
|
|
|
|
for (const chunk of chunkList(explicitWaitCandidateIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
2026-04-26 21:17:38 -05:00
|
|
|
const interactionRows: Array<{ issueId: string }> = await dbOrTx
|
|
|
|
|
.select({ issueId: issueThreadInteractions.issueId })
|
|
|
|
|
.from(issueThreadInteractions)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueThreadInteractions.companyId, companyId),
|
|
|
|
|
inArray(issueThreadInteractions.status, BLOCKER_ATTENTION_PENDING_INTERACTION_STATUSES),
|
|
|
|
|
inArray(issueThreadInteractions.issueId, chunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
for (const row of interactionRows) explicitWaitingIssueIds.add(row.issueId);
|
|
|
|
|
|
|
|
|
|
const approvalRows: Array<{ issueId: string }> = await dbOrTx
|
|
|
|
|
.select({ issueId: issueApprovals.issueId })
|
|
|
|
|
.from(issueApprovals)
|
|
|
|
|
.innerJoin(approvals, eq(issueApprovals.approvalId, approvals.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueApprovals.companyId, companyId),
|
|
|
|
|
inArray(approvals.status, BLOCKER_ATTENTION_PENDING_APPROVAL_STATUSES),
|
|
|
|
|
inArray(issueApprovals.issueId, chunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
for (const row of approvalRows) explicitWaitingIssueIds.add(row.issueId);
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
}
|
2026-04-26 21:17:38 -05:00
|
|
|
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
// Recovery rows are intentionally company-wide: a liveness escalation for
|
|
|
|
|
// the same leaf blocker represents an active waiting path even when that
|
|
|
|
|
// blocker is reached through another blocked graph.
|
|
|
|
|
const recoveryRows: Array<{ id: string; originId: string | null }> = await dbOrTx
|
|
|
|
|
.select({ id: issues.id, originId: issues.originId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
eq(issues.originKind, BLOCKER_ATTENTION_OPEN_RECOVERY_ORIGIN_KIND),
|
|
|
|
|
isNull(issues.hiddenAt),
|
|
|
|
|
notInArray(issues.status, BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
for (const row of recoveryRows) {
|
|
|
|
|
const parsed = parseIssueGraphLivenessIncidentKey(row.originId);
|
|
|
|
|
if (!parsed || parsed.companyId !== companyId) continue;
|
|
|
|
|
explicitWaitingIssueIds.add(row.id);
|
|
|
|
|
explicitWaitingIssueIds.add(parsed.issueId);
|
|
|
|
|
explicitWaitingIssueIds.add(parsed.leafIssueId);
|
2026-04-26 21:17:38 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-24 15:50:32 -05:00
|
|
|
const agentRows: IssueBlockerAttentionAgentRow[] = agentIds.size > 0
|
|
|
|
|
? await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
id: agents.id,
|
|
|
|
|
companyId: agents.companyId,
|
|
|
|
|
status: agents.status,
|
|
|
|
|
})
|
|
|
|
|
.from(agents)
|
|
|
|
|
.where(and(eq(agents.companyId, companyId), inArray(agents.id, [...agentIds])))
|
|
|
|
|
: [];
|
|
|
|
|
const agentsById = new Map(agentRows.map((agent) => [agent.id, agent]));
|
|
|
|
|
|
2026-04-26 21:17:38 -05:00
|
|
|
type PathClassification = {
|
|
|
|
|
covered: boolean;
|
|
|
|
|
stalled: boolean;
|
|
|
|
|
sampleBlockerIdentifier: string | null;
|
|
|
|
|
sampleStalledBlockerIdentifier: string | null;
|
|
|
|
|
};
|
2026-04-24 15:50:32 -05:00
|
|
|
const classifyPath = (
|
|
|
|
|
nodeId: string,
|
|
|
|
|
seen: Set<string>,
|
|
|
|
|
): PathClassification => {
|
2026-04-26 21:17:38 -05:00
|
|
|
const sample = blockerSampleIdentifier(nodesById.get(nodeId));
|
|
|
|
|
if (truncated || seen.has(nodeId)) {
|
|
|
|
|
return { covered: false, stalled: false, sampleBlockerIdentifier: sample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
2026-04-24 15:50:32 -05:00
|
|
|
const node = nodesById.get(nodeId);
|
2026-04-26 21:17:38 -05:00
|
|
|
if (!node || node.companyId !== companyId) {
|
|
|
|
|
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeId, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
|
|
|
|
const nodeSample = blockerSampleIdentifier(node);
|
|
|
|
|
if (node.status === "done") {
|
|
|
|
|
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
if (explicitWaitingIssueIds.has(node.id)) {
|
|
|
|
|
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
2026-04-26 21:17:38 -05:00
|
|
|
if (node.status === "in_review") {
|
[codex] Harden issue recovery reliability (#4875)
## Thinking Path
> - Paperclip is the control plane for autonomous agent companies, so
non-terminal issue state must always have a clear live, waiting, or
recovery owner.
> - This change stays inside the server reliability and liveness
subsystem for assigned issue recovery, blocker attention, and live-run
polling.
> - Closed PR #4860 mixed this reliability work with separate
mutation-boundary policy changes, which made review and merge risk too
broad.
> - [PAP-2981](/PAP/issues/PAP-2981) asked for a replacement PR
containing only the remaining reliability slice and explicitly excluding
user-assignment and execution-policy restrictions.
> - Follow-up review also split `advanced` run-liveness continuation
behavior out of this PR so it can be reviewed separately.
> - The implementation hardens repeated recovery escalation, expands
blocker-attention coverage for explicit waiting and recovery paths, and
caps company live-run polling defaults.
> - The benefit is a smaller reliability PR that improves liveness
behavior without changing agent/user mutation authorization boundaries
or `advanced` continuation semantics.
## What Changed
- Avoid repeated liveness escalation updates when the source issue is
already blocked by the same open escalation.
- Treat open liveness escalation recovery issues, their source issues,
and their leaf blockers as covered waiting paths in blocker attention.
- Cap default company live-run polling at 50 rows for both `minCount`
and `limit`, including explicit zero values, to avoid unbounded
responses.
- Preserve the existing behavior where succeeded `advanced` runs are
considered productive/healthy for stranded-work recovery and are not
actionable bounded run-liveness continuations.
- Added focused server coverage for recovery dedupe, blocker attention,
liveness escalation, run continuations, and live-run polling.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-issue-liveness-escalation.test.ts
server/src/__tests__/issue-blocker-attention.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/agent-live-run-routes.test.ts`
- Result: 5 files passed, 63 tests passed.
- `pnpm --filter @paperclipai/server typecheck`
- Result: passed.
- No UI changes; screenshots are not applicable.
## Risks
- Recovery and blocker-attention classification changes can affect which
blocked chains are shown as covered versus needing attention.
- Live-run polling now treats omitted, invalid, or non-positive `limit`
/ `minCount` values as the capped default of 50.
- `advanced` run-liveness continuation behavior is intentionally
excluded from this PR and split for separate review.
> 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, code execution and GitHub CLI tool use, medium
reasoning effort.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-30 16:44:28 -05:00
|
|
|
const hasWaitingPath = activeIssueIds.has(node.id) || Boolean(node.assigneeUserId);
|
2026-04-26 21:17:38 -05:00
|
|
|
if (hasWaitingPath) {
|
|
|
|
|
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
|
|
|
|
return { covered: false, stalled: true, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: nodeSample };
|
|
|
|
|
}
|
|
|
|
|
if (activeIssueIds.has(node.id)) {
|
|
|
|
|
return { covered: true, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
|
|
|
|
if (node.status === "cancelled") {
|
|
|
|
|
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
|
|
|
|
}
|
2026-04-24 15:50:32 -05:00
|
|
|
|
|
|
|
|
const downstream = (edgesByIssueId.get(node.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
|
|
|
|
|
if (downstream.length > 0) {
|
|
|
|
|
const nextSeen = new Set(seen);
|
|
|
|
|
nextSeen.add(nodeId);
|
|
|
|
|
const classified = downstream.map((edge) => classifyPath(edge.blockerIssueId, nextSeen));
|
2026-04-26 21:17:38 -05:00
|
|
|
const stalledChild = classified.find((result) => result.stalled || result.sampleStalledBlockerIdentifier);
|
|
|
|
|
const sampleStalled = stalledChild?.sampleStalledBlockerIdentifier ?? null;
|
|
|
|
|
const hardAttention = classified.find((result) => !result.covered && !result.stalled);
|
|
|
|
|
if (hardAttention) {
|
|
|
|
|
return {
|
|
|
|
|
covered: false,
|
|
|
|
|
stalled: false,
|
|
|
|
|
sampleBlockerIdentifier: hardAttention.sampleBlockerIdentifier,
|
|
|
|
|
sampleStalledBlockerIdentifier: sampleStalled,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const stalledEntry = classified.find((result) => result.stalled);
|
|
|
|
|
if (stalledEntry) {
|
|
|
|
|
return {
|
|
|
|
|
covered: false,
|
|
|
|
|
stalled: true,
|
|
|
|
|
sampleBlockerIdentifier: stalledEntry.sampleBlockerIdentifier,
|
|
|
|
|
sampleStalledBlockerIdentifier: sampleStalled,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-04-24 15:50:32 -05:00
|
|
|
return {
|
|
|
|
|
covered: true,
|
2026-04-26 21:17:38 -05:00
|
|
|
stalled: false,
|
|
|
|
|
sampleBlockerIdentifier: classified[0]?.sampleBlockerIdentifier ?? nodeSample,
|
|
|
|
|
sampleStalledBlockerIdentifier: null,
|
2026-04-24 15:50:32 -05:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (node.assigneeAgentId) {
|
|
|
|
|
const assignee = agentsById.get(node.assigneeAgentId);
|
|
|
|
|
if (!assignee || assignee.companyId !== companyId || !BLOCKER_ATTENTION_INVOKABLE_AGENT_STATUSES.has(assignee.status)) {
|
2026-04-26 21:17:38 -05:00
|
|
|
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
2026-04-24 15:50:32 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-26 21:17:38 -05:00
|
|
|
return { covered: false, stalled: false, sampleBlockerIdentifier: nodeSample, sampleStalledBlockerIdentifier: null };
|
2026-04-24 15:50:32 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const root of roots) {
|
|
|
|
|
const topLevelEdges = (edgesByIssueId.get(root.id) ?? []).filter((edge) => nodesById.get(edge.blockerIssueId)?.status !== "done");
|
|
|
|
|
if (topLevelEdges.length === 0) {
|
|
|
|
|
attentionMap.set(root.id, createIssueBlockerAttention({
|
|
|
|
|
state: "needs_attention",
|
|
|
|
|
reason: "attention_required",
|
|
|
|
|
}));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const classified = topLevelEdges.map((edge) => ({
|
|
|
|
|
edge,
|
|
|
|
|
result: classifyPath(edge.blockerIssueId, new Set([root.id])),
|
|
|
|
|
}));
|
|
|
|
|
const coveredBlockerCount = classified.filter((entry) => entry.result.covered).length;
|
2026-04-26 21:17:38 -05:00
|
|
|
const stalledBlockerCount = classified.filter((entry) => entry.result.stalled).length;
|
|
|
|
|
const attentionBlockerCount = classified.length - coveredBlockerCount - stalledBlockerCount;
|
|
|
|
|
const hardAttentionEntry = classified.find((entry) => !entry.result.covered && !entry.result.stalled);
|
|
|
|
|
const stalledEntry = classified.find((entry) => entry.result.stalled);
|
|
|
|
|
const sampleEntry = hardAttentionEntry ?? stalledEntry ?? classified[0] ?? null;
|
2026-04-24 15:50:32 -05:00
|
|
|
const sampleNode = sampleEntry ? nodesById.get(sampleEntry.edge.blockerIssueId) : null;
|
2026-04-26 21:17:38 -05:00
|
|
|
const sampleStalledFromChain = classified
|
|
|
|
|
.map((entry) => entry.result.sampleStalledBlockerIdentifier)
|
|
|
|
|
.find((value) => value);
|
|
|
|
|
|
|
|
|
|
let state: IssueBlockerAttention["state"];
|
|
|
|
|
let reason: IssueBlockerAttention["reason"];
|
|
|
|
|
if (attentionBlockerCount > 0) {
|
|
|
|
|
state = "needs_attention";
|
|
|
|
|
reason = "attention_required";
|
|
|
|
|
} else if (stalledBlockerCount > 0) {
|
|
|
|
|
state = "stalled";
|
|
|
|
|
reason = "stalled_review";
|
|
|
|
|
} else {
|
|
|
|
|
state = "covered";
|
|
|
|
|
reason = topLevelEdges.every((edge) => nodesById.get(edge.blockerIssueId)?.parentId === root.id)
|
|
|
|
|
? "active_child"
|
|
|
|
|
: "active_dependency";
|
|
|
|
|
}
|
2026-04-24 15:50:32 -05:00
|
|
|
|
|
|
|
|
attentionMap.set(root.id, createIssueBlockerAttention({
|
2026-04-26 21:17:38 -05:00
|
|
|
state,
|
|
|
|
|
reason,
|
2026-04-24 15:50:32 -05:00
|
|
|
unresolvedBlockerCount: topLevelEdges.length,
|
|
|
|
|
coveredBlockerCount,
|
2026-04-26 21:17:38 -05:00
|
|
|
stalledBlockerCount,
|
2026-04-24 15:50:32 -05:00
|
|
|
attentionBlockerCount,
|
|
|
|
|
sampleBlockerIdentifier: sampleEntry?.result.sampleBlockerIdentifier ?? blockerSampleIdentifier(sampleNode),
|
2026-04-26 21:17:38 -05:00
|
|
|
sampleStalledBlockerIdentifier:
|
|
|
|
|
stalledEntry?.result.sampleStalledBlockerIdentifier ?? sampleStalledFromChain ?? null,
|
2026-04-24 15:50:32 -05:00
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return attentionMap;
|
|
|
|
|
}
|
|
|
|
|
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
const issueListSelect = {
|
|
|
|
|
id: issues.id,
|
|
|
|
|
companyId: issues.companyId,
|
|
|
|
|
projectId: issues.projectId,
|
|
|
|
|
projectWorkspaceId: issues.projectWorkspaceId,
|
|
|
|
|
goalId: issues.goalId,
|
|
|
|
|
parentId: issues.parentId,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
description: sql<string | null>`
|
|
|
|
|
CASE
|
|
|
|
|
WHEN ${issues.description} IS NULL THEN NULL
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
ELSE encode(
|
|
|
|
|
substring(
|
|
|
|
|
convert_to(${issues.description}, current_setting('server_encoding'))
|
|
|
|
|
FROM 1 FOR ${ISSUE_LIST_DESCRIPTION_MAX_BYTES}
|
|
|
|
|
),
|
|
|
|
|
'base64'
|
|
|
|
|
)
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
END
|
|
|
|
|
`,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
checkoutRunId: issues.checkoutRunId,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
executionAgentNameKey: issues.executionAgentNameKey,
|
|
|
|
|
executionLockedAt: issues.executionLockedAt,
|
|
|
|
|
createdByAgentId: issues.createdByAgentId,
|
|
|
|
|
createdByUserId: issues.createdByUserId,
|
|
|
|
|
issueNumber: issues.issueNumber,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
originKind: issues.originKind,
|
|
|
|
|
originId: issues.originId,
|
|
|
|
|
originRunId: issues.originRunId,
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
originFingerprint: issues.originFingerprint,
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
requestDepth: issues.requestDepth,
|
|
|
|
|
billingCode: issues.billingCode,
|
|
|
|
|
assigneeAdapterOverrides: issues.assigneeAdapterOverrides,
|
|
|
|
|
executionPolicy: sql<null>`null`,
|
|
|
|
|
executionState: sql<null>`null`,
|
[codex] Add issue monitor liveness controls (#4988)
## Thinking Path
> - Paperclip is a control plane for autonomous AI companies where work
must stay observable, governable, and recoverable.
> - The task/heartbeat subsystem owns agent execution continuity, issue
state transitions, and visible recovery behavior.
> - Waiting on an external service is not the same as being blocked when
the assignee still owns a future check.
> - The gap was that agents had no first-class one-shot monitor state
for external-service waits, so recovery could look stalled or require ad
hoc comments.
> - This pull request adds bounded issue monitors that can wake the
owner, clear exhausted waits, and produce explicit recovery behavior.
> - It also surfaces monitor status in the board UI and documents when
to use monitors versus `blocked`.
> - The benefit is clearer liveness semantics for asynchronous waits
without weakening single-assignee task ownership.
## What Changed
- Added issue monitor fields, shared types, validators, constants, and
an idempotent `0075` migration for scheduled monitor state.
- Added server-side monitor scheduling, dispatch, recovery bounds,
activity logging, and external-ref redaction.
- Added board/agent route coverage for monitor permissions and child
monitor scheduling.
- Added issue detail/property UI for monitor state, a monitor activity
card, and Storybook stories for review surfaces.
- Documented monitor semantics and recovery policy behavior in
`doc/execution-semantics.md`.
- Addressed Greptile review feedback by preserving monitor state in
skipped-stage builders and making board monitor saves send `scheduledBy:
"board"`.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issue-execution-policy-routes.test.ts
server/src/__tests__/issue-execution-policy.test.ts
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/lib/activity-format.test.ts`
- First run passed 5 files and failed to collect 2 server suites because
the worktree was missing the optional `acpx/runtime` dependency.
- After `pnpm install --frozen-lockfile`, reran the 2 failed suites
successfully.
- `pnpm exec vitest run
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server typecheck
&& pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run
server/src/__tests__/issue-execution-policy.test.ts
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/server typecheck && pnpm --filter
@paperclipai/ui typecheck`
- `pnpm exec vitest run
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- Storybook screenshot captured from
`http://127.0.0.1:6006/iframe.html?viewMode=story&id=product-issue-monitor-surfaces--monitor-surfaces`
with Playwright.
## Screenshots

## Risks
- Medium: this changes heartbeat recovery behavior for scheduled
external-service waits, so regressions could affect wake timing or
recovery issue creation.
- Migration risk is reduced by using `IF NOT EXISTS` for the new issue
monitor columns and index.
- External monitor references are treated as secret-adjacent and are
intentionally omitted from visible activity/wake payloads.
> 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 coding agent with repository tool use and terminal
execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots or Storybook review surfaces
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-03 08:58:53 -05:00
|
|
|
monitorNextCheckAt: issues.monitorNextCheckAt,
|
|
|
|
|
monitorWakeRequestedAt: issues.monitorWakeRequestedAt,
|
|
|
|
|
monitorLastTriggeredAt: issues.monitorLastTriggeredAt,
|
|
|
|
|
monitorAttemptCount: issues.monitorAttemptCount,
|
|
|
|
|
monitorNotes: issues.monitorNotes,
|
|
|
|
|
monitorScheduledBy: issues.monitorScheduledBy,
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
executionWorkspaceId: issues.executionWorkspaceId,
|
|
|
|
|
executionWorkspacePreference: issues.executionWorkspacePreference,
|
|
|
|
|
executionWorkspaceSettings: sql<null>`null`,
|
|
|
|
|
startedAt: issues.startedAt,
|
|
|
|
|
completedAt: issues.completedAt,
|
|
|
|
|
cancelledAt: issues.cancelledAt,
|
|
|
|
|
hiddenAt: issues.hiddenAt,
|
|
|
|
|
createdAt: issues.createdAt,
|
|
|
|
|
updatedAt: issues.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-02 16:43:59 -06:00
|
|
|
function withActiveRuns(
|
|
|
|
|
issueRows: IssueWithLabels[],
|
|
|
|
|
runMap: Map<string, IssueActiveRunRow>,
|
|
|
|
|
): IssueWithLabelsAndRun[] {
|
|
|
|
|
return issueRows.map((row) => ({
|
|
|
|
|
...row,
|
|
|
|
|
activeRun: row.executionRunId ? (runMap.get(row.executionRunId) ?? null) : null,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
async function userCommentStatsForIssues(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
userId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
): Promise<IssueUserCommentStats[]> {
|
|
|
|
|
const stats: IssueUserCommentStats[] = [];
|
|
|
|
|
for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueComments.issueId,
|
|
|
|
|
myLastCommentAt: sql<Date | null>`
|
|
|
|
|
MAX(CASE WHEN ${issueComments.authorUserId} = ${userId} THEN ${issueComments.createdAt} END)
|
|
|
|
|
`,
|
|
|
|
|
lastExternalCommentAt: sql<Date | null>`
|
|
|
|
|
MAX(
|
|
|
|
|
CASE
|
|
|
|
|
WHEN ${issueComments.authorUserId} IS NULL OR ${issueComments.authorUserId} <> ${userId}
|
|
|
|
|
THEN ${issueComments.createdAt}
|
|
|
|
|
END
|
|
|
|
|
)
|
|
|
|
|
`,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueComments.companyId, companyId),
|
|
|
|
|
inArray(issueComments.issueId, issueIdChunk),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.groupBy(issueComments.issueId);
|
|
|
|
|
stats.push(...rows);
|
|
|
|
|
}
|
|
|
|
|
return stats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function userReadStatsForIssues(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
userId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
): Promise<IssueReadStat[]> {
|
|
|
|
|
const stats: IssueReadStat[] = [];
|
|
|
|
|
for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueReadStates.issueId,
|
|
|
|
|
myLastReadAt: issueReadStates.lastReadAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueReadStates)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueReadStates.companyId, companyId),
|
|
|
|
|
eq(issueReadStates.userId, userId),
|
|
|
|
|
inArray(issueReadStates.issueId, issueIdChunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
stats.push(...rows);
|
|
|
|
|
}
|
|
|
|
|
return stats;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function lastActivityStatsForIssues(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
): Promise<IssueLastActivityStat[]> {
|
|
|
|
|
const byIssueId = new Map<string, IssueLastActivityStat>();
|
|
|
|
|
for (const issueIdChunk of chunkList(issueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const [commentRows, logRows] = await Promise.all([
|
|
|
|
|
dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueComments.issueId,
|
|
|
|
|
latestCommentAt: sql<Date | null>`MAX(${issueComments.createdAt})`,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueComments.companyId, companyId),
|
|
|
|
|
inArray(issueComments.issueId, issueIdChunk),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.groupBy(issueComments.issueId),
|
|
|
|
|
dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
issueId: activityLog.entityId,
|
|
|
|
|
latestLogAt: sql<Date | null>`MAX(${activityLog.createdAt})`,
|
|
|
|
|
})
|
|
|
|
|
.from(activityLog)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(activityLog.companyId, companyId),
|
|
|
|
|
eq(activityLog.entityType, "issue"),
|
|
|
|
|
inArray(activityLog.entityId, issueIdChunk),
|
|
|
|
|
sql`${activityLog.action} NOT IN (${sql.join(
|
|
|
|
|
ISSUE_LOCAL_INBOX_ACTIVITY_ACTIONS.map((action) => sql`${action}`),
|
|
|
|
|
sql`, `,
|
|
|
|
|
)})`,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.groupBy(activityLog.entityId),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
for (const row of commentRows) {
|
|
|
|
|
byIssueId.set(row.issueId, {
|
|
|
|
|
issueId: row.issueId,
|
|
|
|
|
latestCommentAt: row.latestCommentAt,
|
|
|
|
|
latestLogAt: null,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
for (const row of logRows) {
|
|
|
|
|
const existing = byIssueId.get(row.issueId);
|
|
|
|
|
if (existing) existing.latestLogAt = row.latestLogAt;
|
|
|
|
|
else {
|
|
|
|
|
byIssueId.set(row.issueId, {
|
|
|
|
|
issueId: row.issueId,
|
|
|
|
|
latestCommentAt: null,
|
|
|
|
|
latestLogAt: row.latestLogAt,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [...byIssueId.values()];
|
|
|
|
|
}
|
|
|
|
|
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
async function blockedByMapForIssues(
|
|
|
|
|
dbOrTx: any,
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
): Promise<Map<string, IssueRelationIssueSummary[]>> {
|
|
|
|
|
const map = new Map<string, IssueRelationIssueSummary[]>();
|
|
|
|
|
const uniqueIssueIds = [...new Set(issueIds)];
|
|
|
|
|
if (uniqueIssueIds.length === 0) return map;
|
|
|
|
|
|
|
|
|
|
for (const issueId of uniqueIssueIds) {
|
|
|
|
|
map.set(issueId, []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const issueIdChunk of chunkList(uniqueIssueIds, ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
currentIssueId: issueRelations.relatedIssueId,
|
|
|
|
|
relatedId: issues.id,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, issueIdChunk),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const blockedBy = map.get(row.currentIssueId);
|
|
|
|
|
if (!blockedBy) continue;
|
|
|
|
|
blockedBy.push({
|
|
|
|
|
id: row.relatedId,
|
|
|
|
|
identifier: row.identifier,
|
|
|
|
|
title: row.title,
|
|
|
|
|
status: row.status as IssueRelationIssueSummary["status"],
|
|
|
|
|
priority: row.priority as IssueRelationIssueSummary["priority"],
|
|
|
|
|
assigneeAgentId: row.assigneeAgentId,
|
|
|
|
|
assigneeUserId: row.assigneeUserId,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const blockedBy of map.values()) {
|
|
|
|
|
blockedBy.sort((a, b) => a.title.localeCompare(b.title));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return map;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 13:31:58 -06:00
|
|
|
export function issueService(db: Db) {
|
2026-03-17 09:24:28 -05:00
|
|
|
const instanceSettings = instanceSettingsService(db);
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
const treeControlSvc = issueTreeControlService(db);
|
2026-03-17 09:24:28 -05:00
|
|
|
|
2026-04-02 09:11:49 -05:00
|
|
|
async function getIssueByUuid(id: string) {
|
|
|
|
|
const row = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) return null;
|
|
|
|
|
const [enriched] = await withIssueLabels(db, [row]);
|
|
|
|
|
return enriched;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getIssueByIdentifier(identifier: string) {
|
|
|
|
|
const row = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.identifier, identifier.toUpperCase()))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) return null;
|
|
|
|
|
const [enriched] = await withIssueLabels(db, [row]);
|
|
|
|
|
return enriched;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-20 08:00:39 -05:00
|
|
|
function redactIssueComment<T extends { body: string }>(comment: T, censorUsernameInLogs: boolean): T {
|
|
|
|
|
return {
|
|
|
|
|
...comment,
|
|
|
|
|
body: redactCurrentUserText(comment.body, { enabled: censorUsernameInLogs }),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
async function assertAssignableAgent(companyId: string, agentId: string) {
|
|
|
|
|
const assignee = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: agents.id,
|
|
|
|
|
companyId: agents.companyId,
|
|
|
|
|
status: agents.status,
|
|
|
|
|
})
|
|
|
|
|
.from(agents)
|
|
|
|
|
.where(eq(agents.id, agentId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!assignee) throw notFound("Assignee agent not found");
|
|
|
|
|
if (assignee.companyId !== companyId) {
|
|
|
|
|
throw unprocessable("Assignee must belong to same company");
|
|
|
|
|
}
|
|
|
|
|
if (assignee.status === "pending_approval") {
|
|
|
|
|
throw conflict("Cannot assign work to pending approval agents");
|
|
|
|
|
}
|
|
|
|
|
if (assignee.status === "terminated") {
|
|
|
|
|
throw conflict("Cannot assign work to terminated agents");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
async function isTreeHoldInteractionCheckoutAllowed(
|
|
|
|
|
companyId: string,
|
|
|
|
|
checkoutRunId: string | null,
|
|
|
|
|
_gate: ActiveIssueTreePauseHoldGate,
|
|
|
|
|
) {
|
|
|
|
|
if (!checkoutRunId) return false;
|
|
|
|
|
const run = await db
|
2026-04-24 15:50:32 -05:00
|
|
|
.select({
|
|
|
|
|
id: heartbeatRuns.id,
|
|
|
|
|
agentId: heartbeatRuns.agentId,
|
|
|
|
|
wakeupRequestId: heartbeatRuns.wakeupRequestId,
|
|
|
|
|
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
|
|
|
})
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(and(eq(heartbeatRuns.id, checkoutRunId), eq(heartbeatRuns.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
2026-04-24 15:50:32 -05:00
|
|
|
const issueId = readStringFromRecord(run?.contextSnapshot, "issueId");
|
|
|
|
|
if (!run || !issueId) return false;
|
|
|
|
|
return isVerifiedIssueTreeControlInteractionWake(db, {
|
|
|
|
|
companyId,
|
|
|
|
|
issueId,
|
|
|
|
|
agentId: run.agentId,
|
|
|
|
|
runId: run.id,
|
|
|
|
|
wakeupRequestId: run.wakeupRequestId,
|
|
|
|
|
contextSnapshot: run.contextSnapshot as Record<string, unknown> | null | undefined,
|
|
|
|
|
});
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 14:40:32 -06:00
|
|
|
async function assertAssignableUser(companyId: string, userId: string) {
|
|
|
|
|
const membership = await db
|
|
|
|
|
.select({ id: companyMemberships.id })
|
|
|
|
|
.from(companyMemberships)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(companyMemberships.companyId, companyId),
|
|
|
|
|
eq(companyMemberships.principalType, "user"),
|
|
|
|
|
eq(companyMemberships.principalId, userId),
|
|
|
|
|
eq(companyMemberships.status, "active"),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!membership) {
|
|
|
|
|
throw notFound("Assignee user not found");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 14:08:44 -05:00
|
|
|
async function assertValidProjectWorkspace(
|
|
|
|
|
companyId: string,
|
|
|
|
|
projectId: string | null | undefined,
|
|
|
|
|
projectWorkspaceId: string,
|
|
|
|
|
dbOrTx: DbReader = db,
|
|
|
|
|
) {
|
|
|
|
|
const workspace = await dbOrTx
|
2026-03-13 17:12:25 -05:00
|
|
|
.select({
|
|
|
|
|
id: projectWorkspaces.id,
|
|
|
|
|
companyId: projectWorkspaces.companyId,
|
|
|
|
|
projectId: projectWorkspaces.projectId,
|
|
|
|
|
})
|
|
|
|
|
.from(projectWorkspaces)
|
|
|
|
|
.where(eq(projectWorkspaces.id, projectWorkspaceId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!workspace) throw notFound("Project workspace not found");
|
|
|
|
|
if (workspace.companyId !== companyId) throw unprocessable("Project workspace must belong to same company");
|
|
|
|
|
if (projectId && workspace.projectId !== projectId) {
|
|
|
|
|
throw unprocessable("Project workspace must belong to the selected project");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 14:08:44 -05:00
|
|
|
async function assertValidExecutionWorkspace(
|
|
|
|
|
companyId: string,
|
|
|
|
|
projectId: string | null | undefined,
|
|
|
|
|
executionWorkspaceId: string,
|
|
|
|
|
dbOrTx: DbReader = db,
|
|
|
|
|
) {
|
|
|
|
|
const workspace = await dbOrTx
|
2026-03-13 17:12:25 -05:00
|
|
|
.select({
|
|
|
|
|
id: executionWorkspaces.id,
|
|
|
|
|
companyId: executionWorkspaces.companyId,
|
|
|
|
|
projectId: executionWorkspaces.projectId,
|
|
|
|
|
})
|
|
|
|
|
.from(executionWorkspaces)
|
|
|
|
|
.where(eq(executionWorkspaces.id, executionWorkspaceId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!workspace) throw notFound("Execution workspace not found");
|
|
|
|
|
if (workspace.companyId !== companyId) throw unprocessable("Execution workspace must belong to same company");
|
|
|
|
|
if (projectId && workspace.projectId !== projectId) {
|
|
|
|
|
throw unprocessable("Execution workspace must belong to the selected project");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
async function assertValidLabelIds(companyId: string, labelIds: string[], dbOrTx: any = db) {
|
|
|
|
|
if (labelIds.length === 0) return;
|
|
|
|
|
const existing = await dbOrTx
|
|
|
|
|
.select({ id: labels.id })
|
|
|
|
|
.from(labels)
|
|
|
|
|
.where(and(eq(labels.companyId, companyId), inArray(labels.id, labelIds)));
|
|
|
|
|
if (existing.length !== new Set(labelIds).size) {
|
|
|
|
|
throw unprocessable("One or more labels are invalid for this company");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function syncIssueLabels(
|
|
|
|
|
issueId: string,
|
|
|
|
|
companyId: string,
|
|
|
|
|
labelIds: string[],
|
|
|
|
|
dbOrTx: any = db,
|
|
|
|
|
) {
|
|
|
|
|
const deduped = [...new Set(labelIds)];
|
|
|
|
|
await assertValidLabelIds(companyId, deduped, dbOrTx);
|
|
|
|
|
await dbOrTx.delete(issueLabels).where(eq(issueLabels.issueId, issueId));
|
|
|
|
|
if (deduped.length === 0) return;
|
|
|
|
|
await dbOrTx.insert(issueLabels).values(
|
|
|
|
|
deduped.map((labelId) => ({
|
|
|
|
|
issueId,
|
|
|
|
|
labelId,
|
|
|
|
|
companyId,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
async function getIssueRelationSummaryMap(
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueIds: string[],
|
|
|
|
|
dbOrTx: DbReader = db,
|
|
|
|
|
): Promise<Map<string, IssueRelationSummaryMap>> {
|
|
|
|
|
const uniqueIssueIds = [...new Set(issueIds)];
|
|
|
|
|
const empty = new Map<string, IssueRelationSummaryMap>();
|
|
|
|
|
for (const issueId of uniqueIssueIds) {
|
|
|
|
|
empty.set(issueId, { blockedBy: [], blocks: [] });
|
|
|
|
|
}
|
|
|
|
|
if (uniqueIssueIds.length === 0) return empty;
|
|
|
|
|
|
|
|
|
|
const [blockedByRows, blockingRows] = await Promise.all([
|
|
|
|
|
dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
currentIssueId: issueRelations.relatedIssueId,
|
|
|
|
|
relatedId: issues.id,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, uniqueIssueIds),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
currentIssueId: issueRelations.issueId,
|
|
|
|
|
relatedId: issues.id,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.relatedIssueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.issueId, uniqueIssueIds),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
for (const row of blockedByRows) {
|
2026-04-24 15:50:32 -05:00
|
|
|
empty.get(row.currentIssueId)?.blockedBy.push(summarizeIssueRelationRow(row));
|
2026-04-04 13:56:04 -05:00
|
|
|
}
|
|
|
|
|
for (const row of blockingRows) {
|
2026-04-24 15:50:32 -05:00
|
|
|
empty.get(row.currentIssueId)?.blocks.push(summarizeIssueRelationRow(row));
|
2026-04-04 13:56:04 -05:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 15:50:32 -05:00
|
|
|
const terminalByRoot = await terminalExplicitBlockersByRoot(
|
|
|
|
|
companyId,
|
|
|
|
|
[...empty.values()].flatMap((relations) => relations.blockedBy),
|
|
|
|
|
dbOrTx,
|
|
|
|
|
);
|
|
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
for (const relations of empty.values()) {
|
|
|
|
|
relations.blockedBy.sort((a, b) => a.title.localeCompare(b.title));
|
2026-04-24 15:50:32 -05:00
|
|
|
for (const blocker of relations.blockedBy) {
|
|
|
|
|
const terminalBlockers = terminalByRoot.get(blocker.id);
|
|
|
|
|
if (terminalBlockers && terminalBlockers.length > 0) {
|
|
|
|
|
blocker.terminalBlockers = terminalBlockers;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-04 13:56:04 -05:00
|
|
|
relations.blocks.sort((a, b) => a.title.localeCompare(b.title));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return empty;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function assertNoBlockingCycles(
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueId: string,
|
|
|
|
|
blockerIssueIds: string[],
|
|
|
|
|
dbOrTx: DbReader = db,
|
|
|
|
|
) {
|
|
|
|
|
if (blockerIssueIds.length === 0) return;
|
|
|
|
|
|
|
|
|
|
const rows = await dbOrTx
|
|
|
|
|
.select({
|
|
|
|
|
blockerIssueId: issueRelations.issueId,
|
|
|
|
|
blockedIssueId: issueRelations.relatedIssueId,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.where(and(eq(issueRelations.companyId, companyId), eq(issueRelations.type, "blocks")));
|
|
|
|
|
|
|
|
|
|
const adjacency = new Map<string, string[]>();
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const list = adjacency.get(row.blockerIssueId) ?? [];
|
|
|
|
|
list.push(row.blockedIssueId);
|
|
|
|
|
adjacency.set(row.blockerIssueId, list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const blockerIssueId of blockerIssueIds) {
|
|
|
|
|
const queue = [...(adjacency.get(issueId) ?? [])];
|
|
|
|
|
const visited = new Set<string>([issueId]);
|
|
|
|
|
while (queue.length > 0) {
|
|
|
|
|
const current = queue.shift()!;
|
|
|
|
|
if (current === blockerIssueId) {
|
|
|
|
|
throw unprocessable("Blocking relations cannot contain cycles");
|
|
|
|
|
}
|
|
|
|
|
if (visited.has(current)) continue;
|
|
|
|
|
visited.add(current);
|
|
|
|
|
queue.push(...(adjacency.get(current) ?? []));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function syncBlockedByIssueIds(
|
|
|
|
|
issueId: string,
|
|
|
|
|
companyId: string,
|
|
|
|
|
blockedByIssueIds: string[],
|
|
|
|
|
actor: { agentId?: string | null; userId?: string | null } = {},
|
|
|
|
|
dbOrTx: any = db,
|
|
|
|
|
) {
|
|
|
|
|
const deduped = [...new Set(blockedByIssueIds)];
|
|
|
|
|
if (deduped.some((candidate) => candidate === issueId)) {
|
|
|
|
|
throw unprocessable("Issue cannot be blocked by itself");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (deduped.length > 0) {
|
2026-04-06 08:29:23 -05:00
|
|
|
const lockedIssueIds = [issueId, ...deduped].sort();
|
|
|
|
|
await dbOrTx.execute(
|
|
|
|
|
sql`SELECT ${issues.id} FROM ${issues}
|
|
|
|
|
WHERE ${and(eq(issues.companyId, companyId), inArray(issues.id, lockedIssueIds))}
|
|
|
|
|
ORDER BY ${issues.id}
|
|
|
|
|
FOR UPDATE`,
|
|
|
|
|
);
|
2026-04-04 13:56:04 -05:00
|
|
|
const relatedIssues = await dbOrTx
|
|
|
|
|
.select({ id: issues.id })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(and(eq(issues.companyId, companyId), inArray(issues.id, deduped)));
|
|
|
|
|
if (relatedIssues.length !== deduped.length) {
|
|
|
|
|
throw unprocessable("Blocked-by issues must belong to the same company");
|
|
|
|
|
}
|
|
|
|
|
await assertNoBlockingCycles(companyId, issueId, deduped, dbOrTx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await dbOrTx
|
|
|
|
|
.delete(issueRelations)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, companyId),
|
|
|
|
|
eq(issueRelations.relatedIssueId, issueId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (deduped.length === 0) return;
|
|
|
|
|
|
|
|
|
|
await dbOrTx.insert(issueRelations).values(
|
|
|
|
|
deduped.map((blockerIssueId) => ({
|
|
|
|
|
companyId,
|
|
|
|
|
issueId: blockerIssueId,
|
|
|
|
|
relatedIssueId: issueId,
|
|
|
|
|
type: "blocks",
|
|
|
|
|
createdByAgentId: actor.agentId ?? null,
|
|
|
|
|
createdByUserId: actor.userId ?? null,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 08:23:44 -06:00
|
|
|
async function isTerminalOrMissingHeartbeatRun(runId: string) {
|
|
|
|
|
const run = await db
|
|
|
|
|
.select({ status: heartbeatRuns.status })
|
|
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(eq(heartbeatRuns.id, runId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!run) return true;
|
|
|
|
|
return TERMINAL_HEARTBEAT_RUN_STATUSES.has(run.status);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function adoptStaleCheckoutRun(input: {
|
|
|
|
|
issueId: string;
|
|
|
|
|
actorAgentId: string;
|
|
|
|
|
actorRunId: string;
|
|
|
|
|
expectedCheckoutRunId: string;
|
|
|
|
|
}) {
|
|
|
|
|
const stale = await isTerminalOrMissingHeartbeatRun(input.expectedCheckoutRunId);
|
|
|
|
|
if (!stale) return null;
|
|
|
|
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const adopted = await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
checkoutRunId: input.actorRunId,
|
|
|
|
|
executionRunId: input.actorRunId,
|
|
|
|
|
executionLockedAt: now,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.id, input.issueId),
|
|
|
|
|
eq(issues.status, "in_progress"),
|
|
|
|
|
eq(issues.assigneeAgentId, input.actorAgentId),
|
|
|
|
|
eq(issues.checkoutRunId, input.expectedCheckoutRunId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
checkoutRunId: issues.checkoutRunId,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
})
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
return adopted;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
async function adoptUnownedCheckoutRun(input: {
|
|
|
|
|
issueId: string;
|
|
|
|
|
actorAgentId: string;
|
|
|
|
|
actorRunId: string;
|
|
|
|
|
}) {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const adopted = await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
checkoutRunId: input.actorRunId,
|
|
|
|
|
executionRunId: input.actorRunId,
|
|
|
|
|
executionLockedAt: now,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.id, input.issueId),
|
|
|
|
|
eq(issues.status, "in_progress"),
|
|
|
|
|
eq(issues.assigneeAgentId, input.actorAgentId),
|
|
|
|
|
isNull(issues.checkoutRunId),
|
|
|
|
|
or(isNull(issues.executionRunId), eq(issues.executionRunId, input.actorRunId)),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
checkoutRunId: issues.checkoutRunId,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
})
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
return adopted;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function clearExecutionRunIfTerminal(issueId: string): Promise<boolean> {
|
|
|
|
|
return db.transaction(async (tx) => {
|
|
|
|
|
await tx.execute(
|
|
|
|
|
sql`select ${issues.id} from ${issues} where ${issues.id} = ${issueId} for update`,
|
|
|
|
|
);
|
|
|
|
|
const issue = await tx
|
|
|
|
|
.select({ executionRunId: issues.executionRunId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issue?.executionRunId) return false;
|
|
|
|
|
|
|
|
|
|
await tx.execute(
|
|
|
|
|
sql`select ${heartbeatRuns.id} from ${heartbeatRuns} where ${heartbeatRuns.id} = ${issue.executionRunId} for update`,
|
|
|
|
|
);
|
|
|
|
|
const run = await tx
|
|
|
|
|
.select({ status: heartbeatRuns.status })
|
|
|
|
|
.from(heartbeatRuns)
|
|
|
|
|
.where(eq(heartbeatRuns.id, issue.executionRunId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (run && !TERMINAL_HEARTBEAT_RUN_STATUSES.has(run.status)) return false;
|
|
|
|
|
|
|
|
|
|
const updated = await tx
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
executionRunId: null,
|
|
|
|
|
executionAgentNameKey: null,
|
|
|
|
|
executionLockedAt: null,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.id, issueId),
|
|
|
|
|
eq(issues.executionRunId, issue.executionRunId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning({ id: issues.id })
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
return Boolean(updated);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-16 13:31:58 -06:00
|
|
|
return {
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
clearExecutionRunIfTerminal,
|
|
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
list: async (companyId: string, filters?: IssueFilters) => {
|
|
|
|
|
const conditions = [eq(issues.companyId, companyId)];
|
2026-04-06 20:30:50 -05:00
|
|
|
const limit = typeof filters?.limit === "number" && Number.isFinite(filters.limit)
|
|
|
|
|
? Math.max(1, Math.floor(filters.limit))
|
|
|
|
|
: undefined;
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
const offset = typeof filters?.offset === "number" && Number.isFinite(filters.offset)
|
|
|
|
|
? Math.max(0, Math.floor(filters.offset))
|
|
|
|
|
: 0;
|
2026-03-06 08:21:03 -06:00
|
|
|
const touchedByUserId = filters?.touchedByUserId?.trim() || undefined;
|
2026-03-26 08:19:16 -05:00
|
|
|
const inboxArchivedByUserId = filters?.inboxArchivedByUserId?.trim() || undefined;
|
2026-03-06 08:21:03 -06:00
|
|
|
const unreadForUserId = filters?.unreadForUserId?.trim() || undefined;
|
2026-03-26 08:19:16 -05:00
|
|
|
const contextUserId = unreadForUserId ?? touchedByUserId ?? inboxArchivedByUserId;
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
const includeBlockedBy = filters?.includeBlockedBy === true;
|
2026-02-26 16:33:39 -06:00
|
|
|
const rawSearch = filters?.q?.trim() ?? "";
|
|
|
|
|
const hasSearch = rawSearch.length > 0;
|
|
|
|
|
const escapedSearch = hasSearch ? escapeLikePattern(rawSearch) : "";
|
|
|
|
|
const startsWithPattern = `${escapedSearch}%`;
|
|
|
|
|
const containsPattern = `%${escapedSearch}%`;
|
|
|
|
|
const titleStartsWithMatch = sql<boolean>`${issues.title} ILIKE ${startsWithPattern} ESCAPE '\\'`;
|
|
|
|
|
const titleContainsMatch = sql<boolean>`${issues.title} ILIKE ${containsPattern} ESCAPE '\\'`;
|
|
|
|
|
const identifierStartsWithMatch = sql<boolean>`${issues.identifier} ILIKE ${startsWithPattern} ESCAPE '\\'`;
|
|
|
|
|
const identifierContainsMatch = sql<boolean>`${issues.identifier} ILIKE ${containsPattern} ESCAPE '\\'`;
|
|
|
|
|
const descriptionContainsMatch = sql<boolean>`${issues.description} ILIKE ${containsPattern} ESCAPE '\\'`;
|
|
|
|
|
const commentContainsMatch = sql<boolean>`
|
|
|
|
|
EXISTS (
|
|
|
|
|
SELECT 1
|
|
|
|
|
FROM ${issueComments}
|
|
|
|
|
WHERE ${issueComments.issueId} = ${issues.id}
|
|
|
|
|
AND ${issueComments.companyId} = ${companyId}
|
|
|
|
|
AND ${issueComments.body} ILIKE ${containsPattern} ESCAPE '\\'
|
|
|
|
|
)
|
|
|
|
|
`;
|
[codex] Improve issue thread review flow (#4381)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Issue detail is where operators coordinate review, approvals, and
follow-up work with active runs
> - That thread UI needs to surface blockers, descendants, review
handoffs, and reply ergonomics clearly enough for humans to guide agent
work
> - Several small gaps in the issue-thread flow were making review and
navigation clunkier than necessary
> - This pull request improves the reply composer, descendant/blocker
presentation, interaction folding, and review-request handoff plumbing
together as one cohesive issue-thread workflow slice
> - The benefit is a cleaner operator review loop without changing the
broader task model
## What Changed
- restored and refined the floating reply composer behavior in the issue
thread
- folded expired confirmation interactions and improved post-submit
thread scrolling behavior
- surfaced descendant issue context and inline blocker/paused-assignee
notices on the issue detail view
- tightened large-board first paint behavior in `IssuesList`
- added loose review-request handoffs through the issue
execution-policy/update path and covered them with tests
## Verification
- `pnpm vitest run ui/src/pages/IssueDetail.test.tsx`
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-execution-policy.test.ts`
- `pnpm exec vitest run --project @paperclipai/ui
ui/src/components/IssueChatThread.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/lib/issue-tree.test.ts
ui/src/api/issues.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-utils
packages/adapter-utils/src/server-utils.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/issue-comment-reopen-routes.test.ts -t "coerces
executor handoff patches into workflow-controlled review wakes|wakes the
return assignee with execution_changes_requested"`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/issue-execution-policy.test.ts
server/src/__tests__/issues-service.test.ts`
## Visual Evidence
- UI layout changes are covered by the focused issue-thread component
and issue-detail tests listed above. Browser screenshots were not
attachable from this automated greploop environment, so reviewers should
use the running preview for final visual confirmation.
## Risks
- Moderate UI-flow risk: these changes touch the issue detail experience
in multiple spots, so regressions would most likely show up as
thread-layout quirks or incorrect review-handoff behavior
> 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-based coding agent with tool use and code execution
in the Codex CLI environment
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots or documented the visual verification path
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-24 08:02:45 -05:00
|
|
|
if (filters?.descendantOf) {
|
|
|
|
|
conditions.push(sql<boolean>`
|
|
|
|
|
${issues.id} IN (
|
|
|
|
|
WITH RECURSIVE descendants(id) AS (
|
|
|
|
|
SELECT ${issues.id}
|
|
|
|
|
FROM ${issues}
|
|
|
|
|
WHERE ${issues.companyId} = ${companyId}
|
|
|
|
|
AND ${issues.parentId} = ${filters.descendantOf}
|
|
|
|
|
UNION
|
|
|
|
|
SELECT ${issues.id}
|
|
|
|
|
FROM ${issues}
|
|
|
|
|
JOIN descendants ON ${issues.parentId} = descendants.id
|
|
|
|
|
WHERE ${issues.companyId} = ${companyId}
|
|
|
|
|
)
|
|
|
|
|
SELECT id FROM descendants
|
|
|
|
|
)
|
|
|
|
|
`);
|
|
|
|
|
}
|
2026-02-18 16:46:45 -06:00
|
|
|
if (filters?.status) {
|
|
|
|
|
const statuses = filters.status.split(",").map((s) => s.trim());
|
|
|
|
|
conditions.push(statuses.length === 1 ? eq(issues.status, statuses[0]) : inArray(issues.status, statuses));
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (filters?.assigneeAgentId) {
|
|
|
|
|
conditions.push(eq(issues.assigneeAgentId, filters.assigneeAgentId));
|
|
|
|
|
}
|
2026-03-21 12:20:48 -05:00
|
|
|
if (filters?.participantAgentId) {
|
|
|
|
|
conditions.push(participatedByAgentCondition(companyId, filters.participantAgentId));
|
|
|
|
|
}
|
2026-02-26 16:33:39 -06:00
|
|
|
if (filters?.assigneeUserId) {
|
|
|
|
|
conditions.push(eq(issues.assigneeUserId, filters.assigneeUserId));
|
|
|
|
|
}
|
2026-03-06 08:21:03 -06:00
|
|
|
if (touchedByUserId) {
|
|
|
|
|
conditions.push(touchedByUserCondition(companyId, touchedByUserId));
|
|
|
|
|
}
|
2026-03-26 08:19:16 -05:00
|
|
|
if (inboxArchivedByUserId) {
|
|
|
|
|
conditions.push(inboxVisibleForUserCondition(companyId, inboxArchivedByUserId));
|
|
|
|
|
}
|
2026-03-06 08:21:03 -06:00
|
|
|
if (unreadForUserId) {
|
|
|
|
|
conditions.push(unreadForUserCondition(companyId, unreadForUserId));
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (filters?.projectId) conditions.push(eq(issues.projectId, filters.projectId));
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
if (filters?.workspaceId) {
|
|
|
|
|
conditions.push(or(
|
|
|
|
|
eq(issues.executionWorkspaceId, filters.workspaceId),
|
|
|
|
|
eq(issues.projectWorkspaceId, filters.workspaceId),
|
|
|
|
|
)!);
|
|
|
|
|
}
|
2026-03-28 22:21:24 -05:00
|
|
|
if (filters?.executionWorkspaceId) {
|
|
|
|
|
conditions.push(eq(issues.executionWorkspaceId, filters.executionWorkspaceId));
|
|
|
|
|
}
|
2026-03-10 15:54:31 +02:00
|
|
|
if (filters?.parentId) conditions.push(eq(issues.parentId, filters.parentId));
|
2026-03-19 08:39:24 -05:00
|
|
|
if (filters?.originKind) conditions.push(eq(issues.originKind, filters.originKind));
|
|
|
|
|
if (filters?.originId) conditions.push(eq(issues.originId, filters.originId));
|
2026-02-25 08:38:37 -06:00
|
|
|
if (filters?.labelId) {
|
|
|
|
|
const labeledIssueIds = await db
|
|
|
|
|
.select({ issueId: issueLabels.issueId })
|
|
|
|
|
.from(issueLabels)
|
|
|
|
|
.where(and(eq(issueLabels.companyId, companyId), eq(issueLabels.labelId, filters.labelId)));
|
|
|
|
|
if (labeledIssueIds.length === 0) return [];
|
|
|
|
|
conditions.push(inArray(issues.id, labeledIssueIds.map((row) => row.issueId)));
|
|
|
|
|
}
|
2026-02-26 16:33:39 -06:00
|
|
|
if (hasSearch) {
|
|
|
|
|
conditions.push(
|
|
|
|
|
or(
|
|
|
|
|
titleContainsMatch,
|
|
|
|
|
identifierContainsMatch,
|
|
|
|
|
descriptionContainsMatch,
|
|
|
|
|
commentContainsMatch,
|
|
|
|
|
)!,
|
|
|
|
|
);
|
|
|
|
|
}
|
[codex] improve issue and routine UI responsiveness (#3744)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Operators rely on issue, inbox, and routine views to understand what
the company is doing in real time
> - Those views need to stay fast and readable even when issue lists,
markdown comments, and run metadata get large
> - The current branch had a coherent set of UI and live-update
improvements spread across issue search, issue detail rendering, routine
affordances, and workspace lookups
> - This pull request groups those board-facing changes into one
standalone branch that can merge independently of the heartbeat/runtime
work
> - The benefit is a faster, clearer issue and routine workflow without
changing the underlying task model
## What Changed
- Show routine execution issues by default and rename the filter to
`Hide routine runs` so the default state no longer looks like an active
filter.
- Show the routine name in the run dialog and tighten the issue
properties pane with a workspace link, copy-on-click behavior, and an
inline parent arrow.
- Reduce issue detail rerenders, keep queued issue chat mounted, improve
issues page search responsiveness, and speed up issues first paint.
- Add inbox "other search results", refresh visible issue runs after
status updates, and optimize workspace lookups through summary-mode
execution workspace queries.
- Improve markdown wrapping and scrolling behavior for long strings and
self-comment code blocks.
- Relax the markdown sanitizer assertion so the test still validates
safety after the new wrap-friendly inline styles.
## Verification
- `pnpm vitest run ui/src/components/IssuesList.test.tsx
ui/src/lib/inbox.test.ts ui/src/pages/Issues.test.tsx
ui/src/context/BreadcrumbContext.test.tsx
ui/src/context/LiveUpdatesProvider.test.ts
ui/src/components/MarkdownBody.test.tsx
ui/src/api/execution-workspaces.test.ts
server/src/__tests__/execution-workspaces-routes.test.ts`
## Risks
- This touches several issue-facing UI surfaces at once, so regressions
would most likely show up as stale rendering, search result mismatches,
or small markdown presentation differences.
- The workspace lookup optimization depends on the summary-mode route
shape staying aligned between server and UI.
## Model Used
- OpenAI Codex, GPT-5-based coding agent in the Codex CLI environment.
Exact backend model deployment ID was not exposed in-session.
Tool-assisted editing and shell execution were 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 15:54:05 -05:00
|
|
|
if (filters?.excludeRoutineExecutions && !filters?.originKind && !filters?.originId) {
|
2026-03-19 08:39:24 -05:00
|
|
|
conditions.push(ne(issues.originKind, "routine_execution"));
|
|
|
|
|
}
|
2026-02-19 15:43:52 -06:00
|
|
|
conditions.push(isNull(issues.hiddenAt));
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-02-17 20:46:12 -06:00
|
|
|
const priorityOrder = sql`CASE ${issues.priority} WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END`;
|
2026-02-26 16:33:39 -06:00
|
|
|
const searchOrder = sql<number>`
|
|
|
|
|
CASE
|
|
|
|
|
WHEN ${titleStartsWithMatch} THEN 0
|
|
|
|
|
WHEN ${titleContainsMatch} THEN 1
|
|
|
|
|
WHEN ${identifierStartsWithMatch} THEN 2
|
|
|
|
|
WHEN ${identifierContainsMatch} THEN 3
|
2026-04-11 06:57:49 -05:00
|
|
|
WHEN ${commentContainsMatch} THEN 4
|
|
|
|
|
WHEN ${descriptionContainsMatch} THEN 5
|
2026-02-26 16:33:39 -06:00
|
|
|
ELSE 6
|
|
|
|
|
END
|
|
|
|
|
`;
|
2026-04-02 11:38:57 -05:00
|
|
|
const canonicalLastActivityAt = issueCanonicalLastActivityAtExpr(companyId);
|
2026-04-06 20:30:50 -05:00
|
|
|
const baseQuery = db
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
.select(issueListSelect)
|
2026-02-25 08:38:37 -06:00
|
|
|
.from(issues)
|
|
|
|
|
.where(and(...conditions))
|
2026-04-02 11:38:57 -05:00
|
|
|
.orderBy(
|
|
|
|
|
hasSearch ? asc(searchOrder) : asc(priorityOrder),
|
|
|
|
|
asc(priorityOrder),
|
|
|
|
|
desc(canonicalLastActivityAt),
|
|
|
|
|
desc(issues.updatedAt),
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
desc(issues.id),
|
2026-04-02 11:38:57 -05:00
|
|
|
);
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
const pageQuery = offset > 0
|
|
|
|
|
? (limit === undefined ? baseQuery.offset(offset) : baseQuery.limit(limit).offset(offset))
|
|
|
|
|
: (limit === undefined ? baseQuery : baseQuery.limit(limit));
|
|
|
|
|
const rows = (await pageQuery).map((row) => ({
|
[codex] Harden heartbeat scheduling and runtime controls (#4223)
## Thinking Path
> - Paperclip orchestrates AI agents through issue checkout, heartbeat
runs, routines, and auditable control-plane state
> - The runtime path has to recover from lost local processes, transient
adapter failures, blocked dependencies, and routine coalescing without
stranding work
> - The existing branch carried several reliability fixes across
heartbeat scheduling, issue runtime controls, routine dispatch, and
operator-facing run state
> - These changes belong together because they share backend contracts,
migrations, and runtime status semantics
> - This pull request groups the control-plane/runtime slice so it can
merge independently from board UI polish and adapter sandbox work
> - The benefit is safer heartbeat recovery, clearer runtime controls,
and more predictable recurring execution behavior
## What Changed
- Adds bounded heartbeat retry scheduling, scheduled retry state, and
Codex transient failure recovery handling.
- Tightens heartbeat process recovery, blocker wake behavior, issue
comment wake handling, routine dispatch coalescing, and
activity/dashboard bounds.
- Adds runtime-control MCP tools and Paperclip skill docs for issue
workspace runtime management.
- Adds migrations `0061_lively_thor_girl.sql` and
`0062_routine_run_dispatch_fingerprint.sql`.
- Surfaces retry state in run ledger/agent UI and keeps related shared
types synchronized.
## Verification
- `pnpm exec vitest run
server/src/__tests__/heartbeat-retry-scheduling.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/routines-service.test.ts`
- `pnpm exec vitest run src/tools.test.ts` from `packages/mcp-server`
## Risks
- Medium risk: this touches heartbeat recovery and routine dispatch,
which are central execution paths.
- Migration order matters if split branches land out of order: merge
this PR before branches that assume the new runtime/routine fields.
- Runtime retry behavior should be watched in CI and in local operator
smoke tests because it changes how transient failures are resumed.
> 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-based coding agent runtime, shell/git tool use
enabled. Exact hosted model build and context window are not exposed in
this Paperclip heartbeat environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-21 12:24:11 -05:00
|
|
|
...row,
|
|
|
|
|
description: decodeDatabaseTextPreview(row.description, ISSUE_LIST_DESCRIPTION_MAX_CHARS),
|
|
|
|
|
}));
|
2026-03-02 16:43:59 -06:00
|
|
|
const withLabels = await withIssueLabels(db, rows);
|
|
|
|
|
const runMap = await activeRunMapForIssues(db, withLabels);
|
2026-03-06 08:21:03 -06:00
|
|
|
const withRuns = withActiveRuns(withLabels, runMap);
|
2026-04-02 11:38:57 -05:00
|
|
|
if (withRuns.length === 0) {
|
2026-03-06 08:21:03 -06:00
|
|
|
return withRuns;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const issueIds = withRuns.map((row) => row.id);
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
const [statsRows, readRows, lastActivityRows, blockedByMap] = await Promise.all([
|
2026-04-02 11:38:57 -05:00
|
|
|
contextUserId
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
? userCommentStatsForIssues(db, companyId, contextUserId, issueIds)
|
2026-04-02 11:38:57 -05:00
|
|
|
: Promise.resolve([]),
|
|
|
|
|
contextUserId
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
? userReadStatsForIssues(db, companyId, contextUserId, issueIds)
|
2026-04-02 11:38:57 -05:00
|
|
|
: Promise.resolve([]),
|
[codex] Improve agent runtime recovery and governance (#4086)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The heartbeat runtime, agent import path, and agent configuration
defaults determine whether work is dispatched safely and predictably.
> - Several accumulated fixes all touched agent execution recovery, wake
routing, import behavior, and runtime concurrency defaults.
> - Those changes need to land together so the heartbeat service and
agent creation defaults stay internally consistent.
> - This pull request groups the runtime/governance changes from the
split branch into one standalone branch.
> - The benefit is safer recovery for stranded runs, bounded high-volume
reads, imported-agent approval correctness, skill-template support, and
a clearer default concurrency policy.
## What Changed
- Fixed stranded continuation recovery so successful automatic retries
are requeued instead of incorrectly blocking the issue.
- Bounded high-volume issue/log reads across issue, heartbeat, agent,
project, and workspace paths.
- Fixed imported-agent approval and instruction-path permission
handling.
- Quarantined seeded worktree execution state during worktree
provisioning.
- Queued approval follow-up wakes and hardened SQL_ASCII heartbeat
output handling.
- Added reusable agent instruction templates for hiring flows.
- Set the default max concurrent agent runs to five and updated related
UI/tests/docs.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm exec vitest run server/src/__tests__/company-portability.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-comment-wake-batching.test.ts
server/src/__tests__/heartbeat-list.test.ts
server/src/__tests__/issues-service.test.ts
server/src/__tests__/agent-permissions-routes.test.ts
packages/adapter-utils/src/server-utils.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- Split integration check: merged this branch first, followed by the
other [PAP-1614](/PAP/issues/PAP-1614) branches, with no merge
conflicts.
- Confirmed this branch does not include `pnpm-lock.yaml`.
## Risks
- Medium risk: touches heartbeat recovery, queueing, and issue list
bounds in central runtime paths.
- Imported-agent and concurrency default behavior changes may affect
existing automation that assumes one-at-a-time default runs.
- No database migrations are included.
> 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 tool-enabled coding model, agentic
code-editing/runtime with local shell and GitHub CLI access; exact
context window and reasoning mode are not exposed by the Paperclip
harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:19:48 -05:00
|
|
|
lastActivityStatsForIssues(db, companyId, issueIds),
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
includeBlockedBy
|
|
|
|
|
? blockedByMapForIssues(db, companyId, issueIds)
|
|
|
|
|
: Promise.resolve(new Map<string, IssueRelationIssueSummary[]>()),
|
2026-04-02 11:38:57 -05:00
|
|
|
]);
|
2026-03-06 08:21:03 -06:00
|
|
|
const statsByIssueId = new Map(statsRows.map((row) => [row.issueId, row]));
|
2026-04-02 11:38:57 -05:00
|
|
|
const lastActivityByIssueId = new Map(lastActivityRows.map((row) => [row.issueId, row]));
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
const [blockerAttentionByIssueId, productivityReviewByIssueId] = await Promise.all([
|
|
|
|
|
listIssueBlockerAttentionMap(db, companyId, withRuns),
|
|
|
|
|
listIssueProductivityReviewMap(db, companyId, issueIds),
|
|
|
|
|
]);
|
2026-04-02 11:38:57 -05:00
|
|
|
|
|
|
|
|
if (!contextUserId) {
|
|
|
|
|
return withRuns.map((row) => {
|
|
|
|
|
const activity = lastActivityByIssueId.get(row.id);
|
|
|
|
|
const lastActivityAt = latestIssueActivityAt(
|
|
|
|
|
row.updatedAt,
|
|
|
|
|
activity?.latestCommentAt ?? null,
|
|
|
|
|
activity?.latestLogAt ?? null,
|
|
|
|
|
) ?? row.updatedAt;
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
2026-04-02 11:38:57 -05:00
|
|
|
lastActivityAt,
|
2026-04-24 15:50:32 -05:00
|
|
|
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
...(productivityReviewByIssueId.has(row.id)
|
|
|
|
|
? { productivityReview: productivityReviewByIssueId.get(row.id) }
|
|
|
|
|
: {}),
|
2026-04-02 11:38:57 -05:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 08:34:19 -06:00
|
|
|
const readByIssueId = new Map(readRows.map((row) => [row.issueId, row.myLastReadAt]));
|
2026-03-06 08:21:03 -06:00
|
|
|
|
2026-04-02 11:38:57 -05:00
|
|
|
return withRuns.map((row) => {
|
|
|
|
|
const activity = lastActivityByIssueId.get(row.id);
|
|
|
|
|
const lastActivityAt = latestIssueActivityAt(
|
|
|
|
|
row.updatedAt,
|
|
|
|
|
activity?.latestCommentAt ?? null,
|
|
|
|
|
activity?.latestLogAt ?? null,
|
|
|
|
|
) ?? row.updatedAt;
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
Present ordered sub-issues as a workflow checklist (#4523)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Operators use issue detail pages and child issue lists to understand
multi-step execution plans.
> - Ordered sub-issues currently read like a flat table, so dependency
chains and current next steps are harder to scan.
> - The branch work adds a workflow-oriented presentation for child
issues without changing the single-assignee task model.
> - This pull request makes ordered sub-issues read more like a progress
checklist while preserving normal issue list controls.
> - The benefit is that operators can see completed steps, active work,
blocked follow-ups, and dependency order at a glance.
## What Changed
- Added workflow sorting utilities and tests for dependency-aware child
issue ordering.
- Added sub-issue progress summary, checklist numbering, current-step
affordances, blocker context, and done-state de-emphasis in the issue
list UI.
- Wired issue detail sub-issue panels to use the workflow sort/progress
checklist presentation.
- Updated issue service behavior/tests for child issue ordering inputs
used by the UI.
- Added a Storybook visual review fixture and screenshot helper for the
sub-issue workflow checklist surface.
## Verification
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issues-service.test.ts
ui/src/components/IssueRow.test.tsx
ui/src/components/IssuesList.test.tsx ui/src/pages/IssueDetail.test.tsx
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/workflow-sort.test.ts`
- Result: 6 test files passed, 55 tests passed, 34 embedded Postgres
issue-service tests skipped because `@embedded-postgres/darwin-x64` is
unavailable on this host.
- Visual review: generated Storybook screenshots from the existing local
Storybook server on port 6006 with `node
scripts/screenshot-subissues.mjs /tmp/pap-2189-subissues-screens
http://localhost:6006`.
- Screenshot artifacts:
- Desktop dark: 
- Desktop light: 
- Mobile dark: 
- Mobile light: 
- Local Storybook note: starting a second Storybook process selected
port 6008 because 6006 was occupied, then Vite failed with an esbuild
host/binary version mismatch (`0.25.12` host vs `0.27.3` binary). The
already-running Storybook server on 6006 served the fixture successfully
for screenshots.
## Risks
- Medium UI risk: the issue list now has additional sub-issue-specific
visual states, so dense lists should be checked for spacing and
scanability.
- Low ordering risk: workflow sorting is covered by focused unit tests,
but unusual dependency topologies may still need reviewer attention.
- No migration risk: this PR does not add database migrations or touch
`pnpm-lock.yaml`.
> 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 coding agent, tool-enabled shell/git/GitHub
workflow. Context window is runtime-provided and not exposed in this
environment.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-26 07:36:49 -05:00
|
|
|
...(includeBlockedBy ? { blockedBy: blockedByMap.get(row.id) ?? [] } : {}),
|
2026-04-02 11:38:57 -05:00
|
|
|
lastActivityAt,
|
2026-04-24 15:50:32 -05:00
|
|
|
...(blockerAttentionByIssueId.has(row.id) ? { blockerAttention: blockerAttentionByIssueId.get(row.id) } : {}),
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
...(productivityReviewByIssueId.has(row.id)
|
|
|
|
|
? { productivityReview: productivityReviewByIssueId.get(row.id) }
|
|
|
|
|
: {}),
|
2026-04-02 11:38:57 -05:00
|
|
|
...deriveIssueUserContext(row, contextUserId, {
|
|
|
|
|
myLastCommentAt: statsByIssueId.get(row.id)?.myLastCommentAt ?? null,
|
|
|
|
|
myLastReadAt: readByIssueId.get(row.id) ?? null,
|
|
|
|
|
lastExternalCommentAt: statsByIssueId.get(row.id)?.lastExternalCommentAt ?? null,
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
});
|
2026-03-06 08:21:03 -06:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
countUnreadTouchedByUser: async (companyId: string, userId: string, status?: string) => {
|
|
|
|
|
const conditions = [
|
|
|
|
|
eq(issues.companyId, companyId),
|
|
|
|
|
isNull(issues.hiddenAt),
|
|
|
|
|
unreadForUserCondition(companyId, userId),
|
|
|
|
|
];
|
|
|
|
|
if (status) {
|
|
|
|
|
const statuses = status.split(",").map((s) => s.trim()).filter(Boolean);
|
|
|
|
|
if (statuses.length === 1) {
|
|
|
|
|
conditions.push(eq(issues.status, statuses[0]));
|
|
|
|
|
} else if (statuses.length > 1) {
|
|
|
|
|
conditions.push(inArray(issues.status, statuses));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const [row] = await db
|
|
|
|
|
.select({ count: sql<number>`count(*)` })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(and(...conditions));
|
|
|
|
|
return Number(row?.count ?? 0);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
},
|
2026-02-16 13:31:58 -06:00
|
|
|
|
2026-03-06 08:34:19 -06:00
|
|
|
markRead: async (companyId: string, issueId: string, userId: string, readAt: Date = new Date()) => {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const [row] = await db
|
|
|
|
|
.insert(issueReadStates)
|
|
|
|
|
.values({
|
|
|
|
|
companyId,
|
|
|
|
|
issueId,
|
|
|
|
|
userId,
|
|
|
|
|
lastReadAt: readAt,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: [issueReadStates.companyId, issueReadStates.issueId, issueReadStates.userId],
|
|
|
|
|
set: {
|
|
|
|
|
lastReadAt: readAt,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return row;
|
|
|
|
|
},
|
|
|
|
|
|
2026-03-26 16:49:11 -05:00
|
|
|
markUnread: async (companyId: string, issueId: string, userId: string) => {
|
|
|
|
|
const deleted = await db
|
|
|
|
|
.delete(issueReadStates)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueReadStates.companyId, companyId),
|
|
|
|
|
eq(issueReadStates.issueId, issueId),
|
|
|
|
|
eq(issueReadStates.userId, userId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning();
|
|
|
|
|
return deleted.length > 0;
|
|
|
|
|
},
|
|
|
|
|
|
2026-03-26 08:19:16 -05:00
|
|
|
archiveInbox: async (companyId: string, issueId: string, userId: string, archivedAt: Date = new Date()) => {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const [row] = await db
|
|
|
|
|
.insert(issueInboxArchives)
|
|
|
|
|
.values({
|
|
|
|
|
companyId,
|
|
|
|
|
issueId,
|
|
|
|
|
userId,
|
|
|
|
|
archivedAt,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: [issueInboxArchives.companyId, issueInboxArchives.issueId, issueInboxArchives.userId],
|
|
|
|
|
set: {
|
|
|
|
|
archivedAt,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return row;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
unarchiveInbox: async (companyId: string, issueId: string, userId: string) => {
|
|
|
|
|
const [row] = await db
|
|
|
|
|
.delete(issueInboxArchives)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueInboxArchives.companyId, companyId),
|
|
|
|
|
eq(issueInboxArchives.issueId, issueId),
|
|
|
|
|
eq(issueInboxArchives.userId, userId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning();
|
|
|
|
|
return row ?? null;
|
|
|
|
|
},
|
|
|
|
|
|
2026-04-02 09:11:49 -05:00
|
|
|
getById: async (raw: string) => {
|
|
|
|
|
const id = raw.trim();
|
|
|
|
|
if (/^[A-Z]+-\d+$/i.test(id)) {
|
|
|
|
|
return getIssueByIdentifier(id);
|
|
|
|
|
}
|
|
|
|
|
if (!isUuidLike(id)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return getIssueByUuid(id);
|
2026-02-25 08:38:37 -06:00
|
|
|
},
|
2026-02-16 13:31:58 -06:00
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
getByIdentifier: async (identifier: string) => {
|
2026-04-02 09:11:49 -05:00
|
|
|
return getIssueByIdentifier(identifier);
|
2026-02-25 08:38:37 -06:00
|
|
|
},
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
getRelationSummaries: async (issueId: string) => {
|
|
|
|
|
const issue = await db
|
|
|
|
|
.select({ id: issues.id, companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issue) throw notFound("Issue not found");
|
|
|
|
|
const relations = await getIssueRelationSummaryMap(issue.companyId, [issueId], db);
|
|
|
|
|
return relations.get(issueId) ?? { blockedBy: [], blocks: [] };
|
|
|
|
|
},
|
|
|
|
|
|
2026-04-20 16:03:57 -05:00
|
|
|
getDependencyReadiness: async (issueId: string, dbOrTx: any = db) => {
|
|
|
|
|
const issue = await dbOrTx
|
|
|
|
|
.select({ id: issues.id, companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, issueId))
|
|
|
|
|
.then((rows: Array<{ id: string; companyId: string }>) => rows[0] ?? null);
|
|
|
|
|
if (!issue) throw notFound("Issue not found");
|
|
|
|
|
const readiness = await listIssueDependencyReadinessMap(dbOrTx, issue.companyId, [issueId]);
|
|
|
|
|
return readiness.get(issueId) ?? createIssueDependencyReadiness(issueId);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
listDependencyReadiness: async (companyId: string, issueIds: string[], dbOrTx: any = db) => {
|
|
|
|
|
return listIssueDependencyReadinessMap(dbOrTx, companyId, issueIds);
|
|
|
|
|
},
|
|
|
|
|
|
2026-04-24 15:50:32 -05:00
|
|
|
listBlockerAttention: async (
|
|
|
|
|
companyId: string,
|
|
|
|
|
issueRows: IssueBlockerAttentionInputNode[],
|
|
|
|
|
dbOrTx: any = db,
|
|
|
|
|
) => {
|
|
|
|
|
return listIssueBlockerAttentionMap(dbOrTx, companyId, issueRows);
|
|
|
|
|
},
|
|
|
|
|
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
listProductivityReviews: async (
|
|
|
|
|
companyId: string,
|
|
|
|
|
sourceIssueIds: string[],
|
|
|
|
|
dbOrTx: any = db,
|
|
|
|
|
) => {
|
|
|
|
|
return listIssueProductivityReviewMap(dbOrTx, companyId, sourceIssueIds);
|
|
|
|
|
},
|
|
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
listWakeableBlockedDependents: async (blockerIssueId: string) => {
|
|
|
|
|
const blockerIssue = await db
|
|
|
|
|
.select({ id: issues.id, companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, blockerIssueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!blockerIssue) return [];
|
|
|
|
|
|
|
|
|
|
const candidates = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.relatedIssueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, blockerIssue.companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
eq(issueRelations.issueId, blockerIssueId),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
if (candidates.length === 0) return [];
|
|
|
|
|
|
|
|
|
|
const candidateIds = candidates.map((candidate) => candidate.id);
|
|
|
|
|
const blockerRows = await db
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueRelations.relatedIssueId,
|
|
|
|
|
blockerIssueId: issueRelations.issueId,
|
|
|
|
|
blockerStatus: issues.status,
|
|
|
|
|
})
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.innerJoin(issues, eq(issueRelations.issueId, issues.id))
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issueRelations.companyId, blockerIssue.companyId),
|
|
|
|
|
eq(issueRelations.type, "blocks"),
|
|
|
|
|
inArray(issueRelations.relatedIssueId, candidateIds),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const blockersByIssueId = new Map<string, Array<{ blockerIssueId: string; blockerStatus: string }>>();
|
|
|
|
|
for (const row of blockerRows) {
|
|
|
|
|
const list = blockersByIssueId.get(row.issueId) ?? [];
|
|
|
|
|
list.push({ blockerIssueId: row.blockerIssueId, blockerStatus: row.blockerStatus });
|
|
|
|
|
blockersByIssueId.set(row.issueId, list);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return candidates
|
|
|
|
|
.filter((candidate) => candidate.assigneeAgentId && !["backlog", "done", "cancelled"].includes(candidate.status))
|
|
|
|
|
.map((candidate) => {
|
|
|
|
|
const blockers = blockersByIssueId.get(candidate.id) ?? [];
|
|
|
|
|
return {
|
|
|
|
|
...candidate,
|
|
|
|
|
blockerIssueIds: blockers.map((blocker) => blocker.blockerIssueId),
|
|
|
|
|
allBlockersDone: blockers.length > 0 && blockers.every((blocker) => blocker.blockerStatus === "done"),
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
.filter((candidate) => candidate.allBlockersDone)
|
|
|
|
|
.map((candidate) => ({
|
|
|
|
|
id: candidate.id,
|
|
|
|
|
assigneeAgentId: candidate.assigneeAgentId!,
|
|
|
|
|
blockerIssueIds: candidate.blockerIssueIds,
|
|
|
|
|
}));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getWakeableParentAfterChildCompletion: async (parentIssueId: string) => {
|
|
|
|
|
const parent = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
companyId: issues.companyId,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, parentIssueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!parent || !parent.assigneeAgentId || ["backlog", "done", "cancelled"].includes(parent.status)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const children = await db
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
identifier: issues.identifier,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
assigneeUserId: issues.assigneeUserId,
|
|
|
|
|
updatedAt: issues.updatedAt,
|
|
|
|
|
})
|
2026-04-04 13:56:04 -05:00
|
|
|
.from(issues)
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
.where(and(eq(issues.companyId, parent.companyId), eq(issues.parentId, parentIssueId)))
|
|
|
|
|
.orderBy(asc(issues.issueNumber), asc(issues.createdAt));
|
2026-04-04 13:56:04 -05:00
|
|
|
if (children.length === 0) return null;
|
|
|
|
|
if (!children.every((child) => child.status === "done" || child.status === "cancelled")) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
const childIdsForSummaries = children.slice(0, MAX_CHILD_COMPLETION_SUMMARIES).map((child) => child.id);
|
|
|
|
|
const commentRows = childIdsForSummaries.length > 0
|
|
|
|
|
? await db
|
|
|
|
|
.select({
|
|
|
|
|
issueId: issueComments.issueId,
|
|
|
|
|
body: issueComments.body,
|
|
|
|
|
createdAt: issueComments.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(and(eq(issueComments.companyId, parent.companyId), inArray(issueComments.issueId, childIdsForSummaries)))
|
|
|
|
|
.orderBy(desc(issueComments.createdAt), desc(issueComments.id))
|
|
|
|
|
: [];
|
|
|
|
|
const latestCommentByIssueId = new Map<string, string>();
|
|
|
|
|
for (const comment of commentRows) {
|
|
|
|
|
if (!latestCommentByIssueId.has(comment.issueId)) {
|
|
|
|
|
latestCommentByIssueId.set(comment.issueId, comment.body);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const childIssueSummaries: ChildIssueCompletionSummary[] = children
|
|
|
|
|
.slice(0, MAX_CHILD_COMPLETION_SUMMARIES)
|
|
|
|
|
.map((child) => ({
|
|
|
|
|
...child,
|
|
|
|
|
summary: truncateInlineSummary(latestCommentByIssueId.get(child.id)),
|
|
|
|
|
}));
|
|
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
return {
|
|
|
|
|
id: parent.id,
|
|
|
|
|
assigneeAgentId: parent.assigneeAgentId,
|
|
|
|
|
childIssueIds: children.map((child) => child.id),
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
childIssueSummaries,
|
|
|
|
|
childIssueSummaryTruncated: children.length > childIssueSummaries.length,
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
createChild: async (
|
|
|
|
|
parentIssueId: string,
|
|
|
|
|
data: IssueChildCreateInput,
|
|
|
|
|
) => {
|
|
|
|
|
const parent = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, parentIssueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!parent) throw notFound("Parent issue not found");
|
|
|
|
|
|
|
|
|
|
const [{ childCount }] = await db
|
|
|
|
|
.select({ childCount: sql<number>`count(*)::int` })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(and(eq(issues.companyId, parent.companyId), eq(issues.parentId, parent.id)));
|
|
|
|
|
if (childCount >= MAX_CHILD_ISSUES_CREATED_BY_HELPER) {
|
|
|
|
|
throw unprocessable(`Parent issue already has the maximum ${MAX_CHILD_ISSUES_CREATED_BY_HELPER} child issues for this helper`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
acceptanceCriteria,
|
|
|
|
|
blockParentUntilDone,
|
|
|
|
|
actorAgentId,
|
|
|
|
|
actorUserId,
|
|
|
|
|
...issueData
|
|
|
|
|
} = data;
|
|
|
|
|
const child = await issueService(db).create(parent.companyId, {
|
|
|
|
|
...issueData,
|
|
|
|
|
parentId: parent.id,
|
|
|
|
|
projectId: issueData.projectId ?? parent.projectId,
|
|
|
|
|
goalId: issueData.goalId ?? parent.goalId,
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
requestDepth: clampIssueRequestDepth(
|
|
|
|
|
Math.max(clampIssueRequestDepth(parent.requestDepth) + 1, issueData.requestDepth ?? 0),
|
|
|
|
|
),
|
[codex] Add run liveness continuations (#4083)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - Heartbeat runs are the control-plane record of each agent execution
window.
> - Long-running local agents can exhaust context or stop while still
holding useful next-step state.
> - Operators need that stop reason, next action, and continuation path
to be durable and visible.
> - This pull request adds run liveness metadata, continuation
summaries, and UI surfaces for issue run ledgers.
> - The benefit is that interrupted or long-running work can resume with
clearer context instead of losing the agent's last useful handoff.
## What Changed
- Added heartbeat-run liveness fields, continuation attempt tracking,
and an idempotent `0058` migration.
- Added server services and tests for run liveness, continuation
summaries, stop metadata, and activity backfill.
- Wired local and HTTP adapters to surface continuation/liveness context
through shared adapter utilities.
- Added shared constants, validators, and heartbeat types for liveness
continuation state.
- Added issue-detail UI surfaces for continuation handoffs and the run
ledger, with component tests.
- Updated agent runtime docs, heartbeat protocol docs, prompt guidance,
onboarding assets, and skills instructions to explain continuation
behavior.
- Addressed Greptile feedback by scoping document evidence by run,
excluding system continuation-summary documents from liveness evidence,
importing shared liveness types, surfacing hidden ledger run counts,
documenting bounded retry behavior, and moving run-ledger liveness
backfill off the request path.
## Verification
- `pnpm exec vitest run packages/adapter-utils/src/server-utils.test.ts
server/src/__tests__/run-continuations.test.ts
server/src/__tests__/run-liveness.test.ts
server/src/__tests__/activity-service.test.ts
server/src/__tests__/documents-service.test.ts
server/src/__tests__/issue-continuation-summary.test.ts
server/src/services/heartbeat-stop-metadata.test.ts
ui/src/components/IssueRunLedger.test.tsx
ui/src/components/IssueContinuationHandoff.test.tsx
ui/src/components/IssueDocumentsSection.test.tsx`
- `pnpm --filter @paperclipai/db build`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/run-continuations.test.ts
ui/src/components/IssueRunLedger.test.tsx`
- `pnpm exec vitest run
server/src/__tests__/heartbeat-process-recovery.test.ts -t "treats a
plan document update"`
- `pnpm exec vitest run server/src/__tests__/activity-service.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts -t "activity
service|treats a plan document update"`
- Remote PR checks on head `e53b1a1d`: `verify`, `e2e`, `policy`, and
Snyk all passed.
- Confirmed `public-gh/master` is an ancestor of this branch after
fetching `public-gh master`.
- Confirmed `pnpm-lock.yaml` is not included in the branch diff.
- Confirmed migration `0058_wealthy_starbolt.sql` is ordered after
`0057` and uses `IF NOT EXISTS` guards for repeat application.
- Greptile inline review threads are resolved.
## Risks
- Medium risk: this touches heartbeat execution, liveness recovery,
activity rendering, issue routes, shared contracts, docs, and UI.
- Migration risk is mitigated by additive columns/indexes and idempotent
guards.
- Run-ledger liveness backfill is now asynchronous, so the first ledger
response can briefly show historical missing liveness until the
background backfill completes.
- UI screenshot coverage is not included in this packaging pass;
validation is currently through focused component tests.
> 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, local tool-use coding agent with terminal, git,
GitHub connector, GitHub CLI, and Paperclip API access.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
Screenshot note: no before/after screenshots were captured in this PR
packaging pass; the UI changes are covered by focused component tests
listed above.
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-20 06:01:49 -05:00
|
|
|
description: appendAcceptanceCriteriaToDescription(issueData.description, acceptanceCriteria),
|
|
|
|
|
inheritExecutionWorkspaceFromIssueId: parent.id,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (blockParentUntilDone) {
|
|
|
|
|
const existingBlockers = await db
|
|
|
|
|
.select({ blockerIssueId: issueRelations.issueId })
|
|
|
|
|
.from(issueRelations)
|
|
|
|
|
.where(and(eq(issueRelations.companyId, parent.companyId), eq(issueRelations.relatedIssueId, parent.id), eq(issueRelations.type, "blocks")));
|
|
|
|
|
await syncBlockedByIssueIds(
|
|
|
|
|
parent.id,
|
|
|
|
|
parent.companyId,
|
|
|
|
|
[...new Set([...existingBlockers.map((row) => row.blockerIssueId), child.id])],
|
|
|
|
|
{ agentId: actorAgentId ?? null, userId: actorUserId ?? null },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
issue: child,
|
|
|
|
|
parentBlockerAdded: Boolean(blockParentUntilDone),
|
2026-04-04 13:56:04 -05:00
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
create: async (
|
|
|
|
|
companyId: string,
|
2026-03-30 14:08:44 -05:00
|
|
|
data: IssueCreateInput,
|
2026-02-25 08:38:37 -06:00
|
|
|
) => {
|
2026-04-04 13:56:04 -05:00
|
|
|
const {
|
|
|
|
|
labelIds: inputLabelIds,
|
|
|
|
|
blockedByIssueIds,
|
|
|
|
|
inheritExecutionWorkspaceFromIssueId,
|
|
|
|
|
...issueData
|
|
|
|
|
} = data;
|
2026-03-17 09:24:28 -05:00
|
|
|
const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces;
|
|
|
|
|
if (!isolatedWorkspacesEnabled) {
|
|
|
|
|
delete issueData.executionWorkspaceId;
|
|
|
|
|
delete issueData.executionWorkspacePreference;
|
|
|
|
|
delete issueData.executionWorkspaceSettings;
|
|
|
|
|
}
|
2026-02-23 14:40:32 -06:00
|
|
|
if (data.assigneeAgentId && data.assigneeUserId) {
|
|
|
|
|
throw unprocessable("Issue can only have one assignee");
|
|
|
|
|
}
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
if (data.assigneeAgentId) {
|
|
|
|
|
await assertAssignableAgent(companyId, data.assigneeAgentId);
|
|
|
|
|
}
|
2026-02-23 14:40:32 -06:00
|
|
|
if (data.assigneeUserId) {
|
|
|
|
|
await assertAssignableUser(companyId, data.assigneeUserId);
|
|
|
|
|
}
|
|
|
|
|
if (data.status === "in_progress" && !data.assigneeAgentId && !data.assigneeUserId) {
|
|
|
|
|
throw unprocessable("in_progress issues require an assignee");
|
|
|
|
|
}
|
2026-02-19 09:09:40 -06:00
|
|
|
return db.transaction(async (tx) => {
|
2026-03-12 08:50:31 -05:00
|
|
|
const defaultCompanyGoal = await getDefaultCompanyGoal(tx, companyId);
|
2026-03-24 08:11:09 -05:00
|
|
|
const projectGoalId = await getProjectDefaultGoalId(tx, companyId, issueData.projectId);
|
2026-03-30 14:08:44 -05:00
|
|
|
let projectWorkspaceId = issueData.projectWorkspaceId ?? null;
|
|
|
|
|
let executionWorkspaceId = issueData.executionWorkspaceId ?? null;
|
|
|
|
|
let executionWorkspacePreference = issueData.executionWorkspacePreference ?? null;
|
2026-03-10 09:03:31 -05:00
|
|
|
let executionWorkspaceSettings =
|
|
|
|
|
(issueData.executionWorkspaceSettings as Record<string, unknown> | null | undefined) ?? null;
|
2026-03-30 14:08:44 -05:00
|
|
|
const workspaceInheritanceIssueId = inheritExecutionWorkspaceFromIssueId ?? issueData.parentId ?? null;
|
|
|
|
|
const hasExplicitExecutionWorkspaceOverride =
|
|
|
|
|
issueData.executionWorkspaceId !== undefined ||
|
|
|
|
|
issueData.executionWorkspacePreference !== undefined ||
|
|
|
|
|
issueData.executionWorkspaceSettings !== undefined;
|
|
|
|
|
if (workspaceInheritanceIssueId) {
|
|
|
|
|
const workspaceSource = await getWorkspaceInheritanceIssue(tx, companyId, workspaceInheritanceIssueId);
|
|
|
|
|
if (projectWorkspaceId == null && workspaceSource.projectWorkspaceId) {
|
|
|
|
|
projectWorkspaceId = workspaceSource.projectWorkspaceId;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
isolatedWorkspacesEnabled &&
|
|
|
|
|
!hasExplicitExecutionWorkspaceOverride &&
|
|
|
|
|
workspaceSource.executionWorkspaceId
|
|
|
|
|
) {
|
|
|
|
|
const sourceWorkspace = await tx
|
|
|
|
|
.select({
|
|
|
|
|
id: executionWorkspaces.id,
|
|
|
|
|
mode: executionWorkspaces.mode,
|
|
|
|
|
})
|
|
|
|
|
.from(executionWorkspaces)
|
|
|
|
|
.where(eq(executionWorkspaces.id, workspaceSource.executionWorkspaceId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (sourceWorkspace) {
|
|
|
|
|
executionWorkspaceId = sourceWorkspace.id;
|
|
|
|
|
executionWorkspacePreference = "reuse_existing";
|
|
|
|
|
executionWorkspaceSettings = {
|
|
|
|
|
...((workspaceSource.executionWorkspaceSettings as Record<string, unknown> | null | undefined) ?? {}),
|
|
|
|
|
mode: issueExecutionWorkspaceModeForPersistedWorkspace(sourceWorkspace.mode),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
Honor reuse-existing preference and assignee default environment in issue runs (#5139)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside execution workspaces (a per-issue cwd + env), and
an issue
> can prefer to reuse an existing workspace or get a fresh one each time
> - The heartbeat service was reading the existing workspace's config to
derive
> environment selection regardless of whether the issue actually wanted
to reuse
> it. So fresh-run issues were inheriting stale config from a workspace
that was
> about to be discarded
> - Separately, when an issue is assigned to an agent, the issue's
execution
> workspace settings weren't picking up the agent's
`defaultEnvironmentId`,
> even though the agent's choice is the natural default for that issue
> - This PR makes both selection paths honor the obvious source of
truth:
> workspace config flows only when the issue actually wants
`reuse_existing`,
> and the assignee agent's default environment is applied at assignment
time if
> nothing else is set on the issue
> - The benefit is that re-running a flaky issue picks up the right
environment
> instead of inheriting the previous run's config, and assigning an
agent to an
> issue does the obvious thing without operator intervention
## What Changed
- `server/src/services/heartbeat.ts`: introduce
`reusableExecutionWorkspaceConfig`
that is non-null only when `shouldReuseExisting` is true. Both
`resolveExecutionWorkspaceEnvironmentId(...)` and
`applyPersistedExecutionWorkspaceConfig(...)` now read from it instead
of
unconditionally consulting `existingExecutionWorkspace?.config`.
Fresh-run
issues no longer inherit stale environment config from an in-flight
workspace
about to be discarded.
- `server/src/services/issues.ts`: when an issue update sets a new
`assigneeAgentId` and isolated workspaces are enabled, populate
`executionWorkspaceSettings.environmentId` from the assignee agent's
`defaultEnvironmentId` if the issue doesn't have an explicit
`environmentId` set yet.
- Tests added in `heartbeat-plugin-environment.test.ts` (~216 lines) and
`issues-service.test.ts` (~85 lines) covering both paths.
## Verification
- `pnpm --filter @paperclipai/server test --
heartbeat-plugin-environment issues-service`
- Manual QA: assign an issue to an agent that has a non-default
`defaultEnvironmentId`, confirm the issue's workspace settings now
include that
environment id without operator intervention. Trigger a rerun on an
issue
whose existing workspace points at a stale environment, confirm the
rerun uses
the freshly-resolved environment.
## Risks
- Behavioural shift on assignment: previously assigning an agent didn't
propagate the agent's default environment to the issue. Now it does.
Callers
that explicitly want the issue to keep its existing/null environment
must set
`executionWorkspaceSettings.environmentId` themselves; the new logic
only
fires when no explicit value is set.
- Behavioural shift on rerun: stale workspace config is no longer
applied to
fresh runs. Operators who relied on this implicit inheritance may see
different environment selection on the first rerun after deploy.
Mitigation:
the explicit isssue settings and project policy are still honored as
before.
## Model Used
- OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI
- Provider: OpenAI
- Used to author the code changes in this PR
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A (no UI changes)
- [ ] I have updated relevant documentation to reflect my changes — N/A
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-03 18:33:55 -07:00
|
|
|
// Cache the project policy lookup for this insert. Both the
|
|
|
|
|
// default-settings block and the assignee-environment-promotion block
|
|
|
|
|
// need the same row; without caching they'd issue two round-trips.
|
|
|
|
|
let projectPolicyCached: ReturnType<typeof parseProjectExecutionWorkspacePolicy> | null = null;
|
|
|
|
|
let projectPolicyLoaded = false;
|
|
|
|
|
const loadProjectPolicyOnce = async () => {
|
|
|
|
|
if (projectPolicyLoaded) return projectPolicyCached;
|
|
|
|
|
projectPolicyLoaded = true;
|
|
|
|
|
if (!issueData.projectId) return null;
|
|
|
|
|
const projectRow = await tx
|
|
|
|
|
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
projectPolicyCached = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
|
|
|
|
|
return projectPolicyCached;
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-30 14:08:44 -05:00
|
|
|
if (
|
|
|
|
|
executionWorkspaceSettings == null &&
|
|
|
|
|
executionWorkspaceId == null &&
|
|
|
|
|
issueData.projectId
|
|
|
|
|
) {
|
2026-03-10 09:03:31 -05:00
|
|
|
executionWorkspaceSettings =
|
|
|
|
|
defaultIssueExecutionWorkspaceSettingsForProject(
|
2026-03-17 09:24:28 -05:00
|
|
|
gateProjectExecutionWorkspacePolicy(
|
Honor reuse-existing preference and assignee default environment in issue runs (#5139)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside execution workspaces (a per-issue cwd + env), and
an issue
> can prefer to reuse an existing workspace or get a fresh one each time
> - The heartbeat service was reading the existing workspace's config to
derive
> environment selection regardless of whether the issue actually wanted
to reuse
> it. So fresh-run issues were inheriting stale config from a workspace
that was
> about to be discarded
> - Separately, when an issue is assigned to an agent, the issue's
execution
> workspace settings weren't picking up the agent's
`defaultEnvironmentId`,
> even though the agent's choice is the natural default for that issue
> - This PR makes both selection paths honor the obvious source of
truth:
> workspace config flows only when the issue actually wants
`reuse_existing`,
> and the assignee agent's default environment is applied at assignment
time if
> nothing else is set on the issue
> - The benefit is that re-running a flaky issue picks up the right
environment
> instead of inheriting the previous run's config, and assigning an
agent to an
> issue does the obvious thing without operator intervention
## What Changed
- `server/src/services/heartbeat.ts`: introduce
`reusableExecutionWorkspaceConfig`
that is non-null only when `shouldReuseExisting` is true. Both
`resolveExecutionWorkspaceEnvironmentId(...)` and
`applyPersistedExecutionWorkspaceConfig(...)` now read from it instead
of
unconditionally consulting `existingExecutionWorkspace?.config`.
Fresh-run
issues no longer inherit stale environment config from an in-flight
workspace
about to be discarded.
- `server/src/services/issues.ts`: when an issue update sets a new
`assigneeAgentId` and isolated workspaces are enabled, populate
`executionWorkspaceSettings.environmentId` from the assignee agent's
`defaultEnvironmentId` if the issue doesn't have an explicit
`environmentId` set yet.
- Tests added in `heartbeat-plugin-environment.test.ts` (~216 lines) and
`issues-service.test.ts` (~85 lines) covering both paths.
## Verification
- `pnpm --filter @paperclipai/server test --
heartbeat-plugin-environment issues-service`
- Manual QA: assign an issue to an agent that has a non-default
`defaultEnvironmentId`, confirm the issue's workspace settings now
include that
environment id without operator intervention. Trigger a rerun on an
issue
whose existing workspace points at a stale environment, confirm the
rerun uses
the freshly-resolved environment.
## Risks
- Behavioural shift on assignment: previously assigning an agent didn't
propagate the agent's default environment to the issue. Now it does.
Callers
that explicitly want the issue to keep its existing/null environment
must set
`executionWorkspaceSettings.environmentId` themselves; the new logic
only
fires when no explicit value is set.
- Behavioural shift on rerun: stale workspace config is no longer
applied to
fresh runs. Operators who relied on this implicit inheritance may see
different environment selection on the first rerun after deploy.
Mitigation:
the explicit isssue settings and project policy are still honored as
before.
## Model Used
- OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI
- Provider: OpenAI
- Used to author the code changes in this PR
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A (no UI changes)
- [ ] I have updated relevant documentation to reflect my changes — N/A
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-03 18:33:55 -07:00
|
|
|
await loadProjectPolicyOnce(),
|
2026-03-17 09:24:28 -05:00
|
|
|
isolatedWorkspacesEnabled,
|
|
|
|
|
),
|
2026-03-10 09:03:31 -05:00
|
|
|
) as Record<string, unknown> | null;
|
|
|
|
|
}
|
Honor reuse-existing preference and assignee default environment in issue runs (#5139)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside execution workspaces (a per-issue cwd + env), and
an issue
> can prefer to reuse an existing workspace or get a fresh one each time
> - The heartbeat service was reading the existing workspace's config to
derive
> environment selection regardless of whether the issue actually wanted
to reuse
> it. So fresh-run issues were inheriting stale config from a workspace
that was
> about to be discarded
> - Separately, when an issue is assigned to an agent, the issue's
execution
> workspace settings weren't picking up the agent's
`defaultEnvironmentId`,
> even though the agent's choice is the natural default for that issue
> - This PR makes both selection paths honor the obvious source of
truth:
> workspace config flows only when the issue actually wants
`reuse_existing`,
> and the assignee agent's default environment is applied at assignment
time if
> nothing else is set on the issue
> - The benefit is that re-running a flaky issue picks up the right
environment
> instead of inheriting the previous run's config, and assigning an
agent to an
> issue does the obvious thing without operator intervention
## What Changed
- `server/src/services/heartbeat.ts`: introduce
`reusableExecutionWorkspaceConfig`
that is non-null only when `shouldReuseExisting` is true. Both
`resolveExecutionWorkspaceEnvironmentId(...)` and
`applyPersistedExecutionWorkspaceConfig(...)` now read from it instead
of
unconditionally consulting `existingExecutionWorkspace?.config`.
Fresh-run
issues no longer inherit stale environment config from an in-flight
workspace
about to be discarded.
- `server/src/services/issues.ts`: when an issue update sets a new
`assigneeAgentId` and isolated workspaces are enabled, populate
`executionWorkspaceSettings.environmentId` from the assignee agent's
`defaultEnvironmentId` if the issue doesn't have an explicit
`environmentId` set yet.
- Tests added in `heartbeat-plugin-environment.test.ts` (~216 lines) and
`issues-service.test.ts` (~85 lines) covering both paths.
## Verification
- `pnpm --filter @paperclipai/server test --
heartbeat-plugin-environment issues-service`
- Manual QA: assign an issue to an agent that has a non-default
`defaultEnvironmentId`, confirm the issue's workspace settings now
include that
environment id without operator intervention. Trigger a rerun on an
issue
whose existing workspace points at a stale environment, confirm the
rerun uses
the freshly-resolved environment.
## Risks
- Behavioural shift on assignment: previously assigning an agent didn't
propagate the agent's default environment to the issue. Now it does.
Callers
that explicitly want the issue to keep its existing/null environment
must set
`executionWorkspaceSettings.environmentId` themselves; the new logic
only
fires when no explicit value is set.
- Behavioural shift on rerun: stale workspace config is no longer
applied to
fresh runs. Operators who relied on this implicit inheritance may see
different environment selection on the first rerun after deploy.
Mitigation:
the explicit isssue settings and project policy are still honored as
before.
## Model Used
- OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI
- Provider: OpenAI
- Used to author the code changes in this PR
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A (no UI changes)
- [ ] I have updated relevant documentation to reflect my changes — N/A
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-03 18:33:55 -07:00
|
|
|
if (data.assigneeAgentId && isolatedWorkspacesEnabled) {
|
|
|
|
|
const currentWorkspaceSettings = executionWorkspaceSettings == null
|
|
|
|
|
? {}
|
|
|
|
|
: parseObject(executionWorkspaceSettings);
|
|
|
|
|
const issueHasEnvironmentSelection =
|
|
|
|
|
Object.prototype.hasOwnProperty.call(currentWorkspaceSettings, "environmentId");
|
|
|
|
|
// Don't promote the assignee agent's defaultEnvironmentId if either
|
|
|
|
|
// the issue or the project policy already specifies an environment.
|
|
|
|
|
// resolveExecutionWorkspaceEnvironmentId treats issue settings as
|
|
|
|
|
// higher priority than project policy, so promoting the agent's
|
|
|
|
|
// default to issue settings would invert the documented priority
|
|
|
|
|
// (project policy must win over agent default when explicitly set).
|
|
|
|
|
let projectHasEnvironmentSelection = false;
|
|
|
|
|
if (!issueHasEnvironmentSelection && issueData.projectId) {
|
|
|
|
|
const projectPolicy = await loadProjectPolicyOnce();
|
|
|
|
|
projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
|
|
|
|
|
}
|
|
|
|
|
if (!issueHasEnvironmentSelection && !projectHasEnvironmentSelection) {
|
|
|
|
|
const assigneeAgent = await tx
|
|
|
|
|
.select({ defaultEnvironmentId: agents.defaultEnvironmentId })
|
|
|
|
|
.from(agents)
|
|
|
|
|
.where(and(eq(agents.id, data.assigneeAgentId), eq(agents.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (typeof assigneeAgent?.defaultEnvironmentId === "string" && assigneeAgent.defaultEnvironmentId.length > 0) {
|
|
|
|
|
executionWorkspaceSettings = {
|
|
|
|
|
...currentWorkspaceSettings,
|
|
|
|
|
environmentId: assigneeAgent.defaultEnvironmentId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-13 17:12:25 -05:00
|
|
|
if (!projectWorkspaceId && issueData.projectId) {
|
|
|
|
|
const project = await tx
|
|
|
|
|
.select({
|
|
|
|
|
executionWorkspacePolicy: projects.executionWorkspacePolicy,
|
|
|
|
|
})
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(and(eq(projects.id, issueData.projectId), eq(projects.companyId, companyId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
const projectPolicy = parseProjectExecutionWorkspacePolicy(project?.executionWorkspacePolicy);
|
|
|
|
|
projectWorkspaceId = projectPolicy?.defaultProjectWorkspaceId ?? null;
|
|
|
|
|
if (!projectWorkspaceId) {
|
|
|
|
|
projectWorkspaceId = await tx
|
|
|
|
|
.select({ id: projectWorkspaces.id })
|
|
|
|
|
.from(projectWorkspaces)
|
|
|
|
|
.where(and(eq(projectWorkspaces.projectId, issueData.projectId), eq(projectWorkspaces.companyId, companyId)))
|
|
|
|
|
.orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
|
|
|
|
|
.then((rows) => rows[0]?.id ?? null);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-30 14:08:44 -05:00
|
|
|
if (projectWorkspaceId) {
|
|
|
|
|
await assertValidProjectWorkspace(companyId, issueData.projectId, projectWorkspaceId, tx);
|
|
|
|
|
}
|
|
|
|
|
if (executionWorkspaceId) {
|
|
|
|
|
await assertValidExecutionWorkspace(companyId, issueData.projectId, executionWorkspaceId, tx);
|
|
|
|
|
}
|
2026-04-04 22:57:25 -07:00
|
|
|
// Self-correcting counter: use MAX(issue_number) + 1 if the counter
|
|
|
|
|
// has drifted below the actual max, preventing identifier collisions.
|
|
|
|
|
const [maxRow] = await tx
|
|
|
|
|
.select({ maxNum: sql<number>`coalesce(max(${issues.issueNumber}), 0)` })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.companyId, companyId));
|
|
|
|
|
const currentMax = maxRow?.maxNum ?? 0;
|
|
|
|
|
|
2026-02-19 09:09:40 -06:00
|
|
|
const [company] = await tx
|
|
|
|
|
.update(companies)
|
2026-04-04 22:57:25 -07:00
|
|
|
.set({
|
|
|
|
|
issueCounter: sql`greatest(${companies.issueCounter}, ${currentMax}) + 1`,
|
|
|
|
|
})
|
2026-02-19 09:09:40 -06:00
|
|
|
.where(eq(companies.id, companyId))
|
|
|
|
|
.returning({ issueCounter: companies.issueCounter, issuePrefix: companies.issuePrefix });
|
|
|
|
|
|
|
|
|
|
const issueNumber = company.issueCounter;
|
|
|
|
|
const identifier = `${company.issuePrefix}-${issueNumber}`;
|
|
|
|
|
|
2026-03-10 09:03:31 -05:00
|
|
|
const values = {
|
|
|
|
|
...issueData,
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
requestDepth: clampIssueRequestDepth(issueData.requestDepth),
|
2026-03-19 08:39:24 -05:00
|
|
|
originKind: issueData.originKind ?? "manual",
|
2026-03-12 08:50:31 -05:00
|
|
|
goalId: resolveIssueGoalId({
|
|
|
|
|
projectId: issueData.projectId,
|
|
|
|
|
goalId: issueData.goalId,
|
2026-03-24 08:11:09 -05:00
|
|
|
projectGoalId,
|
2026-03-12 08:50:31 -05:00
|
|
|
defaultGoalId: defaultCompanyGoal?.id ?? null,
|
|
|
|
|
}),
|
2026-03-13 17:12:25 -05:00
|
|
|
...(projectWorkspaceId ? { projectWorkspaceId } : {}),
|
2026-03-30 14:08:44 -05:00
|
|
|
...(executionWorkspaceId ? { executionWorkspaceId } : {}),
|
|
|
|
|
...(executionWorkspacePreference ? { executionWorkspacePreference } : {}),
|
2026-03-10 09:03:31 -05:00
|
|
|
...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}),
|
|
|
|
|
companyId,
|
|
|
|
|
issueNumber,
|
|
|
|
|
identifier,
|
|
|
|
|
} as typeof issues.$inferInsert;
|
2026-02-19 09:09:40 -06:00
|
|
|
if (values.status === "in_progress" && !values.startedAt) {
|
|
|
|
|
values.startedAt = new Date();
|
|
|
|
|
}
|
|
|
|
|
if (values.status === "done") {
|
|
|
|
|
values.completedAt = new Date();
|
|
|
|
|
}
|
|
|
|
|
if (values.status === "cancelled") {
|
|
|
|
|
values.cancelledAt = new Date();
|
|
|
|
|
}
|
[codex] Add issue monitor liveness controls (#4988)
## Thinking Path
> - Paperclip is a control plane for autonomous AI companies where work
must stay observable, governable, and recoverable.
> - The task/heartbeat subsystem owns agent execution continuity, issue
state transitions, and visible recovery behavior.
> - Waiting on an external service is not the same as being blocked when
the assignee still owns a future check.
> - The gap was that agents had no first-class one-shot monitor state
for external-service waits, so recovery could look stalled or require ad
hoc comments.
> - This pull request adds bounded issue monitors that can wake the
owner, clear exhausted waits, and produce explicit recovery behavior.
> - It also surfaces monitor status in the board UI and documents when
to use monitors versus `blocked`.
> - The benefit is clearer liveness semantics for asynchronous waits
without weakening single-assignee task ownership.
## What Changed
- Added issue monitor fields, shared types, validators, constants, and
an idempotent `0075` migration for scheduled monitor state.
- Added server-side monitor scheduling, dispatch, recovery bounds,
activity logging, and external-ref redaction.
- Added board/agent route coverage for monitor permissions and child
monitor scheduling.
- Added issue detail/property UI for monitor state, a monitor activity
card, and Storybook stories for review surfaces.
- Documented monitor semantics and recovery policy behavior in
`doc/execution-semantics.md`.
- Addressed Greptile review feedback by preserving monitor state in
skipped-stage builders and making board monitor saves send `scheduledBy:
"board"`.
## Verification
- `pnpm install --frozen-lockfile`
- `pnpm run preflight:workspace-links && pnpm exec vitest run
server/src/__tests__/issue-execution-policy-routes.test.ts
server/src/__tests__/issue-execution-policy.test.ts
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx
ui/src/lib/activity-format.test.ts`
- First run passed 5 files and failed to collect 2 server suites because
the worktree was missing the optional `acpx/runtime` dependency.
- After `pnpm install --frozen-lockfile`, reran the 2 failed suites
successfully.
- `pnpm exec vitest run
server/src/__tests__/issue-monitor-scheduler.test.ts
server/src/__tests__/recovery-classifiers.test.ts`
- `pnpm --filter @paperclipai/shared typecheck && pnpm --filter
@paperclipai/db typecheck && pnpm --filter @paperclipai/server typecheck
&& pnpm --filter @paperclipai/ui typecheck`
- `pnpm exec vitest run
server/src/__tests__/issue-execution-policy.test.ts
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/server typecheck && pnpm --filter
@paperclipai/ui typecheck`
- `pnpm exec vitest run
ui/src/components/IssueMonitorActivityCard.test.tsx
ui/src/components/IssueProperties.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- Storybook screenshot captured from
`http://127.0.0.1:6006/iframe.html?viewMode=story&id=product-issue-monitor-surfaces--monitor-surfaces`
with Playwright.
## Screenshots

## Risks
- Medium: this changes heartbeat recovery behavior for scheduled
external-service waits, so regressions could affect wake timing or
recovery issue creation.
- Migration risk is reduced by using `IF NOT EXISTS` for the new issue
monitor columns and index.
- External monitor references are treated as secret-adjacent and are
intentionally omitted from visible activity/wake payloads.
> 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 coding agent with repository tool use and terminal
execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots or Storybook review surfaces
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-03 08:58:53 -05:00
|
|
|
Object.assign(
|
|
|
|
|
values,
|
|
|
|
|
buildInitialIssueMonitorFields({
|
|
|
|
|
policy: normalizeIssueExecutionPolicy(issueData.executionPolicy ?? null),
|
|
|
|
|
status: values.status ?? "backlog",
|
|
|
|
|
assigneeAgentId: values.assigneeAgentId ?? null,
|
|
|
|
|
assigneeUserId: values.assigneeUserId ?? null,
|
|
|
|
|
}),
|
|
|
|
|
);
|
2026-02-19 09:09:40 -06:00
|
|
|
|
|
|
|
|
const [issue] = await tx.insert(issues).values(values).returning();
|
2026-02-25 08:38:37 -06:00
|
|
|
if (inputLabelIds) {
|
|
|
|
|
await syncIssueLabels(issue.id, companyId, inputLabelIds, tx);
|
|
|
|
|
}
|
2026-04-04 13:56:04 -05:00
|
|
|
if (blockedByIssueIds !== undefined) {
|
|
|
|
|
await syncBlockedByIssueIds(
|
|
|
|
|
issue.id,
|
|
|
|
|
companyId,
|
|
|
|
|
blockedByIssueIds,
|
|
|
|
|
{
|
|
|
|
|
agentId: issueData.createdByAgentId ?? null,
|
|
|
|
|
userId: issueData.createdByUserId ?? null,
|
|
|
|
|
},
|
|
|
|
|
tx,
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
const [enriched] = await withIssueLabels(tx, [issue]);
|
|
|
|
|
return enriched;
|
2026-02-19 09:09:40 -06:00
|
|
|
});
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
},
|
2026-02-16 13:31:58 -06:00
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
update: async (
|
|
|
|
|
id: string,
|
|
|
|
|
data: Partial<typeof issues.$inferInsert> & {
|
|
|
|
|
labelIds?: string[];
|
|
|
|
|
blockedByIssueIds?: string[];
|
|
|
|
|
actorAgentId?: string | null;
|
|
|
|
|
actorUserId?: string | null;
|
|
|
|
|
},
|
2026-04-07 17:07:10 -05:00
|
|
|
dbOrTx: any = db,
|
2026-04-04 13:56:04 -05:00
|
|
|
) => {
|
2026-04-07 17:07:10 -05:00
|
|
|
const existing = await dbOrTx
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
.select()
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
2026-04-07 17:07:10 -05:00
|
|
|
.then((rows: Array<typeof issues.$inferSelect>) => rows[0] ?? null);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
if (!existing) return null;
|
|
|
|
|
|
2026-04-04 13:56:04 -05:00
|
|
|
const {
|
|
|
|
|
labelIds: nextLabelIds,
|
|
|
|
|
blockedByIssueIds,
|
|
|
|
|
actorAgentId,
|
|
|
|
|
actorUserId,
|
|
|
|
|
...issueData
|
|
|
|
|
} = data;
|
2026-03-17 09:24:28 -05:00
|
|
|
const isolatedWorkspacesEnabled = (await instanceSettings.getExperimental()).enableIsolatedWorkspaces;
|
|
|
|
|
if (!isolatedWorkspacesEnabled) {
|
|
|
|
|
delete issueData.executionWorkspaceId;
|
|
|
|
|
delete issueData.executionWorkspacePreference;
|
|
|
|
|
delete issueData.executionWorkspaceSettings;
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
|
|
|
|
|
if (issueData.status) {
|
|
|
|
|
assertTransition(existing.status, issueData.status);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const patch: Partial<typeof issues.$inferInsert> = {
|
2026-02-25 08:38:37 -06:00
|
|
|
...issueData,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
updatedAt: new Date(),
|
|
|
|
|
};
|
[codex] Split backend control-plane QoL slice (#4700)
## Thinking Path
> - Paperclip is the control plane for autonomous AI companies, so
backend task ownership, recovery, review visibility, and company-scoped
limits need to stay enforceable without UI-only coupling.
> - Closed PR #4692 bundled those backend changes with UI workflow,
docs, skills, workflow, and lockfile churn.
> - PAP-2694 asks for a clean backend/control-plane slice from that
closed branch.
> - This branch starts from current `master` and mines only the `cli`,
`packages/db`, `packages/shared`, and `server` contracts/tests needed
for the backend behavior.
> - It explicitly excludes UI workflow/performance work,
`.github/workflows/pr.yml`, `pnpm-lock.yaml`, docs, skills,
package-script, adapter UI build-config, and perf fixture script
changes; the only UI files are fixture/test updates required by the
tightened shared `Company` contract.
> - The benefit is a smaller reviewable PR that preserves the
control-plane fixes while staying under Greptile s 100-file review
limit.
## What Changed
- Added company-scoped attachment-size limits through DB
schema/migrations, shared company portability contracts, CLI
import/export coverage, and server attachment upload enforcement.
- Added productivity review service/API behavior for no-comment streak,
long-active, and high-churn review issues, including request-depth
clamping and issue summary exposure.
- Hardened issue ownership and recovery/control-plane paths: peer-agent
mutation denial, issue tree pause/resume behavior, stranded recovery
origins, and related activity/test coverage.
- Preserved related backend contract updates for routine timestamp
variables and managed agent instruction bundles because they live in
shared/server contracts from the source branch.
- Addressed Greptile feedback by making `Company.attachmentMaxBytes`
non-optional, simplifying review request-depth clamping, fixing the
migration final newline, and enforcing the process-level attachment cap
as the final ceiling for uploads.
- Added minimal company fixtures needed for repo-wide typecheck/build
and kept the PR to 66 changed files with forbidden/non-slice paths
excluded.
## Verification
- `pnpm install --frozen-lockfile`
- `git diff --check origin/master..HEAD`
- `git diff --name-only origin/master..HEAD | wc -l` -> 66 files
- `git diff --name-only origin/master..HEAD -- .github/workflows/pr.yml
pnpm-lock.yaml package.json doc skills .agents scripts
packages/adapters` -> no output
- `pnpm exec vitest run --config vitest.config.ts
packages/shared/src/validators/issue.test.ts
packages/shared/src/routine-variables.test.ts
packages/shared/src/adapter-types.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
cli/src/__tests__/company.test.ts
server/src/__tests__/productivity-review-service.test.ts
server/src/__tests__/issue-tree-control-service.test.ts
server/src/__tests__/issue-tree-control-routes.test.ts
server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts
server/src/__tests__/issue-attachment-routes.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/issues-service.test.ts` -> 12 files, 147 tests
passed
- `pnpm exec vitest run --config vitest.config.ts
cli/src/__tests__/company-delete.test.ts
cli/src/__tests__/company-import-export-e2e.test.ts
server/src/__tests__/productivity-review-service.test.ts` -> 3 files, 18
tests passed
- `pnpm exec vitest run --config vitest.config.ts
server/src/__tests__/issue-attachment-routes.test.ts` -> 1 file, 6 tests
passed
- `pnpm --filter @paperclipai/db typecheck && pnpm --filter
@paperclipai/shared typecheck && pnpm --filter @paperclipai/server
typecheck && pnpm --filter paperclipai typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck && pnpm --filter
@paperclipai/ui build`
## Risks
- Includes migrations `0073_shiny_salo.sql` and
`0074_striped_genesis.sql`; merge ordering matters if another PR adds
migrations first.
- This is intentionally backend-only apart from fixture/test updates
forced by shared type correctness; UI affordances from PR #4692 are not
present here and should land in separate UI slices.
- The worktree install emitted plugin SDK bin-link warnings for unbuilt
plugin packages, but the targeted tests and package typechecks completed
successfully.
> 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 coding agent, tool-enabled terminal/GitHub
workflow. Exact runtime context window was not exposed by the harness.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-28 16:46:45 -05:00
|
|
|
if (issueData.requestDepth !== undefined) {
|
|
|
|
|
patch.requestDepth = clampIssueRequestDepth(issueData.requestDepth);
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-02-23 14:40:32 -06:00
|
|
|
const nextAssigneeAgentId =
|
2026-02-25 08:38:37 -06:00
|
|
|
issueData.assigneeAgentId !== undefined ? issueData.assigneeAgentId : existing.assigneeAgentId;
|
2026-02-23 14:40:32 -06:00
|
|
|
const nextAssigneeUserId =
|
2026-02-25 08:38:37 -06:00
|
|
|
issueData.assigneeUserId !== undefined ? issueData.assigneeUserId : existing.assigneeUserId;
|
2026-02-23 14:40:32 -06:00
|
|
|
|
|
|
|
|
if (nextAssigneeAgentId && nextAssigneeUserId) {
|
|
|
|
|
throw unprocessable("Issue can only have one assignee");
|
|
|
|
|
}
|
|
|
|
|
if (patch.status === "in_progress" && !nextAssigneeAgentId && !nextAssigneeUserId) {
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
throw unprocessable("in_progress issues require an assignee");
|
|
|
|
|
}
|
2026-04-20 16:03:57 -05:00
|
|
|
if (patch.status === "in_progress") {
|
|
|
|
|
const unresolvedBlockerIssueIds = blockedByIssueIds !== undefined
|
|
|
|
|
? await listUnresolvedBlockerIssueIds(dbOrTx, existing.companyId, blockedByIssueIds)
|
|
|
|
|
: (
|
|
|
|
|
await listIssueDependencyReadinessMap(dbOrTx, existing.companyId, [id])
|
|
|
|
|
).get(id)?.unresolvedBlockerIssueIds ?? [];
|
|
|
|
|
if (unresolvedBlockerIssueIds.length > 0) {
|
|
|
|
|
throw unprocessable("Issue is blocked by unresolved blockers", { unresolvedBlockerIssueIds });
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
if (issueData.assigneeAgentId) {
|
|
|
|
|
await assertAssignableAgent(existing.companyId, issueData.assigneeAgentId);
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
if (issueData.assigneeUserId) {
|
|
|
|
|
await assertAssignableUser(existing.companyId, issueData.assigneeUserId);
|
2026-02-23 14:40:32 -06:00
|
|
|
}
|
2026-03-13 17:12:25 -05:00
|
|
|
const nextProjectId = issueData.projectId !== undefined ? issueData.projectId : existing.projectId;
|
|
|
|
|
const nextProjectWorkspaceId =
|
|
|
|
|
issueData.projectWorkspaceId !== undefined ? issueData.projectWorkspaceId : existing.projectWorkspaceId;
|
|
|
|
|
const nextExecutionWorkspaceId =
|
|
|
|
|
issueData.executionWorkspaceId !== undefined ? issueData.executionWorkspaceId : existing.executionWorkspaceId;
|
Fix runtime state race, workspace sync, plugin startup, and orphaned leases (#4804)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments that are leased, and the server
manages runtime state, workspace configuration, and plugin lifecycle
> - Several edge cases caused failures during concurrent operations: a
race condition in runtime state insertion could produce duplicate-key
errors, reused workspaces didn't sync their configuration when the
parent issue was updated, sandbox provider plugins could be queried
before registration completed, and orphaned environment leases from
failed runs were never released
> - This PR fixes these four runtime/environment issues
> - The benefit is more reliable concurrent agent execution and proper
resource cleanup
## What Changed
- `services/heartbeat.ts`: Fixed a race condition where concurrent
runtime state inserts could fail with a duplicate-key error by using an
upsert pattern
- `services/issues.ts`: Sync reused workspace configuration when an
issue is updated, so the workspace reflects the latest issue state
- `services/environment-runtime.ts`: Fixed a startup race where sandbox
provider plugins could be queried before registration completed, by
awaiting plugin readiness before resolving environment drivers
- `services/heartbeat.ts`: Release environment leases for orphaned runs
that lost their process without cleanup
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
runtime state upsert and process recovery lease cleanup
- `pnpm typecheck` — clean
- Manual: trigger concurrent agent runs to verify no duplicate-key
failures; verify orphaned leases are released after process loss
## Risks
- Low risk. The runtime state upsert changes insert-to-upsert behavior,
which could mask a legitimate duplicate if two different runs produce
the same key — but this is prevented by the run ID being part of the
key. The plugin startup await is bounded by the existing registration
timeout.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-29 16:37:10 -07:00
|
|
|
const nextExecutionWorkspacePreference =
|
|
|
|
|
issueData.executionWorkspacePreference !== undefined
|
|
|
|
|
? issueData.executionWorkspacePreference
|
|
|
|
|
: existing.executionWorkspacePreference;
|
|
|
|
|
const nextExecutionWorkspaceSettings =
|
|
|
|
|
issueData.executionWorkspaceSettings !== undefined
|
|
|
|
|
? parseIssueExecutionWorkspaceSettings(issueData.executionWorkspaceSettings)
|
|
|
|
|
: parseIssueExecutionWorkspaceSettings(existing.executionWorkspaceSettings);
|
2026-03-13 17:12:25 -05:00
|
|
|
if (nextProjectWorkspaceId) {
|
|
|
|
|
await assertValidProjectWorkspace(existing.companyId, nextProjectId, nextProjectWorkspaceId);
|
|
|
|
|
}
|
|
|
|
|
if (nextExecutionWorkspaceId) {
|
|
|
|
|
await assertValidExecutionWorkspace(existing.companyId, nextProjectId, nextExecutionWorkspaceId);
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
applyStatusSideEffects(issueData.status, patch);
|
|
|
|
|
if (issueData.status && issueData.status !== "done") {
|
2026-02-19 09:09:40 -06:00
|
|
|
patch.completedAt = null;
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
if (issueData.status && issueData.status !== "cancelled") {
|
2026-02-19 09:09:40 -06:00
|
|
|
patch.cancelledAt = null;
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
if (issueData.status && issueData.status !== "in_progress") {
|
2026-02-20 15:48:22 -06:00
|
|
|
patch.checkoutRunId = null;
|
2026-04-03 10:03:43 +02:00
|
|
|
// Fix B: also clear the execution lock when leaving in_progress
|
|
|
|
|
patch.executionRunId = null;
|
2026-04-03 15:11:42 +02:00
|
|
|
patch.executionAgentNameKey = null;
|
2026-04-03 10:03:43 +02:00
|
|
|
patch.executionLockedAt = null;
|
2026-02-20 15:48:22 -06:00
|
|
|
}
|
2026-02-23 14:40:32 -06:00
|
|
|
if (
|
2026-02-25 08:38:37 -06:00
|
|
|
(issueData.assigneeAgentId !== undefined && issueData.assigneeAgentId !== existing.assigneeAgentId) ||
|
|
|
|
|
(issueData.assigneeUserId !== undefined && issueData.assigneeUserId !== existing.assigneeUserId)
|
2026-02-23 14:40:32 -06:00
|
|
|
) {
|
2026-02-20 15:48:22 -06:00
|
|
|
patch.checkoutRunId = null;
|
2026-04-03 10:03:43 +02:00
|
|
|
// Fix B: clear execution lock on reassignment, matching checkoutRunId clear
|
|
|
|
|
patch.executionRunId = null;
|
2026-04-03 15:11:42 +02:00
|
|
|
patch.executionAgentNameKey = null;
|
2026-04-03 10:03:43 +02:00
|
|
|
patch.executionLockedAt = null;
|
2026-02-20 15:48:22 -06:00
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-04-07 17:07:10 -05:00
|
|
|
const runUpdate = async (tx: any) => {
|
2026-03-12 08:50:31 -05:00
|
|
|
const defaultCompanyGoal = await getDefaultCompanyGoal(tx, existing.companyId);
|
2026-03-24 08:11:09 -05:00
|
|
|
const [currentProjectGoalId, nextProjectGoalId] = await Promise.all([
|
|
|
|
|
getProjectDefaultGoalId(tx, existing.companyId, existing.projectId),
|
|
|
|
|
getProjectDefaultGoalId(
|
|
|
|
|
tx,
|
|
|
|
|
existing.companyId,
|
|
|
|
|
issueData.projectId !== undefined ? issueData.projectId : existing.projectId,
|
|
|
|
|
),
|
|
|
|
|
]);
|
Honor reuse-existing preference and assignee default environment in issue runs (#5139)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside execution workspaces (a per-issue cwd + env), and
an issue
> can prefer to reuse an existing workspace or get a fresh one each time
> - The heartbeat service was reading the existing workspace's config to
derive
> environment selection regardless of whether the issue actually wanted
to reuse
> it. So fresh-run issues were inheriting stale config from a workspace
that was
> about to be discarded
> - Separately, when an issue is assigned to an agent, the issue's
execution
> workspace settings weren't picking up the agent's
`defaultEnvironmentId`,
> even though the agent's choice is the natural default for that issue
> - This PR makes both selection paths honor the obvious source of
truth:
> workspace config flows only when the issue actually wants
`reuse_existing`,
> and the assignee agent's default environment is applied at assignment
time if
> nothing else is set on the issue
> - The benefit is that re-running a flaky issue picks up the right
environment
> instead of inheriting the previous run's config, and assigning an
agent to an
> issue does the obvious thing without operator intervention
## What Changed
- `server/src/services/heartbeat.ts`: introduce
`reusableExecutionWorkspaceConfig`
that is non-null only when `shouldReuseExisting` is true. Both
`resolveExecutionWorkspaceEnvironmentId(...)` and
`applyPersistedExecutionWorkspaceConfig(...)` now read from it instead
of
unconditionally consulting `existingExecutionWorkspace?.config`.
Fresh-run
issues no longer inherit stale environment config from an in-flight
workspace
about to be discarded.
- `server/src/services/issues.ts`: when an issue update sets a new
`assigneeAgentId` and isolated workspaces are enabled, populate
`executionWorkspaceSettings.environmentId` from the assignee agent's
`defaultEnvironmentId` if the issue doesn't have an explicit
`environmentId` set yet.
- Tests added in `heartbeat-plugin-environment.test.ts` (~216 lines) and
`issues-service.test.ts` (~85 lines) covering both paths.
## Verification
- `pnpm --filter @paperclipai/server test --
heartbeat-plugin-environment issues-service`
- Manual QA: assign an issue to an agent that has a non-default
`defaultEnvironmentId`, confirm the issue's workspace settings now
include that
environment id without operator intervention. Trigger a rerun on an
issue
whose existing workspace points at a stale environment, confirm the
rerun uses
the freshly-resolved environment.
## Risks
- Behavioural shift on assignment: previously assigning an agent didn't
propagate the agent's default environment to the issue. Now it does.
Callers
that explicitly want the issue to keep its existing/null environment
must set
`executionWorkspaceSettings.environmentId` themselves; the new logic
only
fires when no explicit value is set.
- Behavioural shift on rerun: stale workspace config is no longer
applied to
fresh runs. Operators who relied on this implicit inheritance may see
different environment selection on the first rerun after deploy.
Mitigation:
the explicit isssue settings and project policy are still honored as
before.
## Model Used
- OpenAI GPT-5.4 (reasoning effort: high) via Codex CLI
- Provider: OpenAI
- Used to author the code changes in this PR
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots — N/A (no UI changes)
- [ ] I have updated relevant documentation to reflect my changes — N/A
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-03 18:33:55 -07:00
|
|
|
|
|
|
|
|
// Mirror the create() path: when the assignee changes to a non-null
|
|
|
|
|
// agent, default the issue's executionWorkspaceSettings.environmentId
|
|
|
|
|
// to the new agent's defaultEnvironmentId. Skip when:
|
|
|
|
|
// - this update explicitly sets executionWorkspaceSettings.environmentId
|
|
|
|
|
// (caller is making a deliberate override; respect it), OR
|
|
|
|
|
// - the project policy already specifies an environmentId (project
|
|
|
|
|
// policy must win over agent default per the documented priority
|
|
|
|
|
// order in resolveExecutionWorkspaceEnvironmentId), OR
|
|
|
|
|
// - the issue already has an environmentId that was *not* the prior
|
|
|
|
|
// assignee's default (i.e., the operator set it explicitly in an
|
|
|
|
|
// earlier update; preserve their choice). When the existing
|
|
|
|
|
// environmentId matches the prior assignee's default, treat it as
|
|
|
|
|
// auto-promoted and refresh it to the new assignee's default.
|
|
|
|
|
const assigneeChanged =
|
|
|
|
|
issueData.assigneeAgentId !== undefined &&
|
|
|
|
|
issueData.assigneeAgentId !== null &&
|
|
|
|
|
issueData.assigneeAgentId !== existing.assigneeAgentId;
|
|
|
|
|
const explicitEnvInThisUpdate =
|
|
|
|
|
issueData.executionWorkspaceSettings !== undefined &&
|
|
|
|
|
Object.prototype.hasOwnProperty.call(
|
|
|
|
|
parseObject(issueData.executionWorkspaceSettings),
|
|
|
|
|
"environmentId",
|
|
|
|
|
);
|
|
|
|
|
if (assigneeChanged && isolatedWorkspacesEnabled && !explicitEnvInThisUpdate) {
|
|
|
|
|
let projectHasEnvironmentSelection = false;
|
|
|
|
|
if (nextProjectId) {
|
|
|
|
|
const projectRow = await tx
|
|
|
|
|
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(and(eq(projects.id, nextProjectId), eq(projects.companyId, existing.companyId)))
|
|
|
|
|
.then((rows: Array<{ executionWorkspacePolicy: unknown }>) => rows[0] ?? null);
|
|
|
|
|
const projectPolicy = parseProjectExecutionWorkspacePolicy(projectRow?.executionWorkspacePolicy);
|
|
|
|
|
projectHasEnvironmentSelection = projectPolicy?.environmentId !== undefined;
|
|
|
|
|
}
|
|
|
|
|
if (!projectHasEnvironmentSelection) {
|
|
|
|
|
const baseSettings = nextExecutionWorkspaceSettings == null
|
|
|
|
|
? {}
|
|
|
|
|
: parseObject(nextExecutionWorkspaceSettings);
|
|
|
|
|
const existingEnvId = typeof baseSettings.environmentId === "string"
|
|
|
|
|
? baseSettings.environmentId
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
// Look up both the prior assignee (to detect auto-promoted env)
|
|
|
|
|
// and the new assignee in a single query.
|
|
|
|
|
type AgentRow = { id: string; defaultEnvironmentId: string | null };
|
|
|
|
|
const agentRows: AgentRow[] = await tx
|
|
|
|
|
.select({ id: agents.id, defaultEnvironmentId: agents.defaultEnvironmentId })
|
|
|
|
|
.from(agents)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(agents.companyId, existing.companyId),
|
|
|
|
|
inArray(
|
|
|
|
|
agents.id,
|
|
|
|
|
[issueData.assigneeAgentId!, existing.assigneeAgentId].filter(
|
|
|
|
|
(value): value is string => typeof value === "string",
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const newAssignee = agentRows.find((row: AgentRow) => row.id === issueData.assigneeAgentId);
|
|
|
|
|
const previousAssignee = existing.assigneeAgentId
|
|
|
|
|
? agentRows.find((row: AgentRow) => row.id === existing.assigneeAgentId)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const newDefaultEnvId =
|
|
|
|
|
typeof newAssignee?.defaultEnvironmentId === "string" && newAssignee.defaultEnvironmentId.length > 0
|
|
|
|
|
? newAssignee.defaultEnvironmentId
|
|
|
|
|
: null;
|
|
|
|
|
const previousDefaultEnvId =
|
|
|
|
|
typeof previousAssignee?.defaultEnvironmentId === "string" && previousAssignee.defaultEnvironmentId.length > 0
|
|
|
|
|
? previousAssignee.defaultEnvironmentId
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const existingEnvWasAutoPromoted =
|
|
|
|
|
existingEnvId === null ||
|
|
|
|
|
(previousDefaultEnvId !== null && existingEnvId === previousDefaultEnvId);
|
|
|
|
|
|
|
|
|
|
if (newDefaultEnvId && existingEnvWasAutoPromoted) {
|
|
|
|
|
patch.executionWorkspaceSettings = {
|
|
|
|
|
...baseSettings,
|
|
|
|
|
environmentId: newDefaultEnvId,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 08:50:31 -05:00
|
|
|
patch.goalId = resolveNextIssueGoalId({
|
|
|
|
|
currentProjectId: existing.projectId,
|
|
|
|
|
currentGoalId: existing.goalId,
|
2026-03-24 08:11:09 -05:00
|
|
|
currentProjectGoalId,
|
2026-03-12 08:50:31 -05:00
|
|
|
projectId: issueData.projectId,
|
|
|
|
|
goalId: issueData.goalId,
|
2026-03-24 08:11:09 -05:00
|
|
|
projectGoalId: nextProjectGoalId,
|
2026-03-12 08:50:31 -05:00
|
|
|
defaultGoalId: defaultCompanyGoal?.id ?? null,
|
|
|
|
|
});
|
2026-02-25 08:38:37 -06:00
|
|
|
const updated = await tx
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set(patch)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.returning()
|
2026-04-07 17:07:10 -05:00
|
|
|
.then((rows: Array<typeof issues.$inferSelect>) => rows[0] ?? null);
|
2026-02-25 08:38:37 -06:00
|
|
|
if (!updated) return null;
|
|
|
|
|
if (nextLabelIds !== undefined) {
|
|
|
|
|
await syncIssueLabels(updated.id, existing.companyId, nextLabelIds, tx);
|
|
|
|
|
}
|
2026-04-04 13:56:04 -05:00
|
|
|
if (blockedByIssueIds !== undefined) {
|
|
|
|
|
await syncBlockedByIssueIds(
|
|
|
|
|
updated.id,
|
|
|
|
|
existing.companyId,
|
|
|
|
|
blockedByIssueIds,
|
|
|
|
|
{
|
|
|
|
|
agentId: actorAgentId ?? null,
|
|
|
|
|
userId: actorUserId ?? null,
|
|
|
|
|
},
|
|
|
|
|
tx,
|
|
|
|
|
);
|
|
|
|
|
}
|
Fix runtime state race, workspace sync, plugin startup, and orphaned leases (#4804)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Agents run inside environments that are leased, and the server
manages runtime state, workspace configuration, and plugin lifecycle
> - Several edge cases caused failures during concurrent operations: a
race condition in runtime state insertion could produce duplicate-key
errors, reused workspaces didn't sync their configuration when the
parent issue was updated, sandbox provider plugins could be queried
before registration completed, and orphaned environment leases from
failed runs were never released
> - This PR fixes these four runtime/environment issues
> - The benefit is more reliable concurrent agent execution and proper
resource cleanup
## What Changed
- `services/heartbeat.ts`: Fixed a race condition where concurrent
runtime state inserts could fail with a duplicate-key error by using an
upsert pattern
- `services/issues.ts`: Sync reused workspace configuration when an
issue is updated, so the workspace reflects the latest issue state
- `services/environment-runtime.ts`: Fixed a startup race where sandbox
provider plugins could be queried before registration completed, by
awaiting plugin readiness before resolving environment drivers
- `services/heartbeat.ts`: Release environment leases for orphaned runs
that lost their process without cleanup
## Verification
- `pnpm test` — all existing and new tests pass, including new tests for
runtime state upsert and process recovery lease cleanup
- `pnpm typecheck` — clean
- Manual: trigger concurrent agent runs to verify no duplicate-key
failures; verify orphaned leases are released after process loss
## Risks
- Low risk. The runtime state upsert changes insert-to-upsert behavior,
which could mask a legitimate duplicate if two different runs produce
the same key — but this is prevented by the run ID being part of the
key. The plugin startup await is bounded by the existing registration
timeout.
## Model Used
Codex GPT 5.4 high via Paperclip.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-29 16:37:10 -07:00
|
|
|
if (
|
|
|
|
|
issueData.executionWorkspaceSettings !== undefined &&
|
|
|
|
|
nextExecutionWorkspaceId &&
|
|
|
|
|
nextExecutionWorkspacePreference === "reuse_existing"
|
|
|
|
|
) {
|
|
|
|
|
const workspace = await tx
|
|
|
|
|
.select({
|
|
|
|
|
id: executionWorkspaces.id,
|
|
|
|
|
metadata: executionWorkspaces.metadata,
|
|
|
|
|
})
|
|
|
|
|
.from(executionWorkspaces)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(executionWorkspaces.id, nextExecutionWorkspaceId),
|
|
|
|
|
eq(executionWorkspaces.companyId, existing.companyId),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.then((rows: Array<{ id: string; metadata: unknown }>) => rows[0] ?? null);
|
|
|
|
|
if (workspace) {
|
|
|
|
|
await tx
|
|
|
|
|
.update(executionWorkspaces)
|
|
|
|
|
.set({
|
|
|
|
|
metadata: mergeExecutionWorkspaceConfig(
|
|
|
|
|
(workspace.metadata as Record<string, unknown> | null) ?? null,
|
|
|
|
|
buildReusedExecutionWorkspaceConfigPatchFromIssueSettings(nextExecutionWorkspaceSettings),
|
|
|
|
|
),
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(executionWorkspaces.id, workspace.id));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-25 08:38:37 -06:00
|
|
|
const [enriched] = await withIssueLabels(tx, [updated]);
|
|
|
|
|
return enriched;
|
2026-04-07 17:07:10 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return dbOrTx === db ? db.transaction(runUpdate) : runUpdate(dbOrTx);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
},
|
2026-02-16 13:31:58 -06:00
|
|
|
|
Add SSH environment support (#4358)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The environments subsystem already models execution environments,
but before this branch there was no end-to-end SSH-backed runtime path
for agents to actually run work against a remote box
> - That meant agents could be configured around environment concepts
without a reliable way to execute adapter sessions remotely, sync
workspace state, and preserve run context across supported adapters
> - We also need environment selection to participate in normal
Paperclip control-plane behavior: agent defaults, project/issue
selection, route validation, and environment probing
> - Because this capability is still experimental, the UI surface should
be easy to hide and easy to remove later without undoing the underlying
implementation
> - This pull request adds SSH environment execution support across the
runtime, adapters, routes, schema, and tests, then puts the visible
environment-management UI behind an experimental flag
> - The benefit is that we can validate real SSH-backed agent execution
now while keeping the user-facing controls safely gated until the
feature is ready to come out of experimentation
## What Changed
- Added SSH-backed execution target support in the shared adapter
runtime, including remote workspace preparation, skill/runtime asset
sync, remote session handling, and workspace restore behavior after
runs.
- Added SSH execution coverage for supported local adapters, plus remote
execution tests across Claude, Codex, Cursor, Gemini, OpenCode, and Pi.
- Added environment selection and environment-management backend support
needed for SSH execution, including route/service work, validation,
probing, and agent default environment persistence.
- Added CLI support for SSH environment lab verification and updated
related docs/tests.
- Added the `enableEnvironments` experimental flag and gated the
environment UI behind it on company settings, agent configuration, and
project configuration surfaces.
## Verification
- `pnpm exec vitest run
packages/adapters/claude-local/src/server/execute.remote.test.ts
packages/adapters/cursor-local/src/server/execute.remote.test.ts
packages/adapters/gemini-local/src/server/execute.remote.test.ts
packages/adapters/opencode-local/src/server/execute.remote.test.ts
packages/adapters/pi-local/src/server/execute.remote.test.ts`
- `pnpm exec vitest run server/src/__tests__/environment-routes.test.ts`
- `pnpm exec vitest run
server/src/__tests__/instance-settings-routes.test.ts`
- `pnpm exec vitest run ui/src/lib/new-agent-hire-payload.test.ts
ui/src/lib/new-agent-runtime-config.test.ts`
- `pnpm -r typecheck`
- `pnpm build`
- Manual verification on a branch-local dev server:
- enabled the experimental flag
- created an SSH environment
- created a Linux Claude agent using that environment
- confirmed a run executed on the Linux box and synced workspace changes
back
## Risks
- Medium: this touches runtime execution flow across multiple adapters,
so regressions would likely show up in remote session setup, workspace
sync, or environment selection precedence.
- The UI flag reduces exposure, but the underlying runtime and route
changes are still substantial and rely on migration correctness.
- The change set is broad across adapters, control-plane services,
migrations, and UI gating, so review should pay close attention to
environment-selection precedence and remote workspace lifecycle
behavior.
## Model Used
- OpenAI Codex via Paperclip's local Codex adapter, GPT-5-class coding
model with tool use and code execution in the local repo workspace. The
local adapter does not surface a more specific public model version
string in this branch workflow.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-23 19:15:22 -07:00
|
|
|
clearExecutionWorkspaceEnvironmentSelection: async (companyId: string, environmentId: string) => {
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
executionWorkspaceSettings: issues.executionWorkspaceSettings,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.companyId, companyId));
|
|
|
|
|
|
|
|
|
|
let cleared = 0;
|
|
|
|
|
for (const row of rows) {
|
|
|
|
|
const settings = parseIssueExecutionWorkspaceSettings(row.executionWorkspaceSettings);
|
|
|
|
|
if (settings?.environmentId !== environmentId) continue;
|
|
|
|
|
|
|
|
|
|
await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
executionWorkspaceSettings: {
|
|
|
|
|
...settings,
|
|
|
|
|
environmentId: null,
|
|
|
|
|
},
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(issues.id, row.id));
|
|
|
|
|
cleared += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cleared;
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-16 13:31:58 -06:00
|
|
|
remove: (id: string) =>
|
2026-02-20 10:31:56 -06:00
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
const attachmentAssetIds = await tx
|
|
|
|
|
.select({ assetId: issueAttachments.assetId })
|
|
|
|
|
.from(issueAttachments)
|
|
|
|
|
.where(eq(issueAttachments.issueId, id));
|
2026-03-13 22:17:49 -05:00
|
|
|
const issueDocumentIds = await tx
|
|
|
|
|
.select({ documentId: issueDocuments.documentId })
|
|
|
|
|
.from(issueDocuments)
|
|
|
|
|
.where(eq(issueDocuments.issueId, id));
|
2026-02-20 10:31:56 -06:00
|
|
|
|
|
|
|
|
const removedIssue = await tx
|
|
|
|
|
.delete(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (removedIssue && attachmentAssetIds.length > 0) {
|
|
|
|
|
await tx
|
|
|
|
|
.delete(assets)
|
|
|
|
|
.where(inArray(assets.id, attachmentAssetIds.map((row) => row.assetId)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 22:17:49 -05:00
|
|
|
if (removedIssue && issueDocumentIds.length > 0) {
|
|
|
|
|
await tx
|
|
|
|
|
.delete(documents)
|
|
|
|
|
.where(inArray(documents.id, issueDocumentIds.map((row) => row.documentId)));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
if (!removedIssue) return null;
|
|
|
|
|
const [enriched] = await withIssueLabels(tx, [removedIssue]);
|
|
|
|
|
return enriched;
|
2026-02-20 10:31:56 -06:00
|
|
|
}),
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
checkout: async (id: string, agentId: string, expectedStatuses: string[], checkoutRunId: string | null) => {
|
Implement agent hiring, approval workflows, config revisions, LLM reflection, and sidebar badges
Agent management: hire endpoint with permission gates and pending_approval status,
config revision tracking with rollback, agent duplicate route, permission CRUD.
Block pending_approval agents from auth, heartbeat, and assignments.
Approvals: revision request/resubmit flow, approval comments CRUD, issue-approval
linking, auto-wake agents on approval decisions with context snapshot.
Costs: per-agent breakdown, period filtering (month/week/day/all), cost by agent
list endpoint.
Adapters: agentConfigurationDoc on all adapters, /llms/agent-configuration.txt
reflection routes. Inject PAPERCLIP_APPROVAL_ID, PAPERCLIP_APPROVAL_STATUS,
PAPERCLIP_LINKED_ISSUE_IDS into adapter environments.
Sidebar badges endpoint for pending approval/inbox counts. Dashboard and company
settings extensions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 13:02:41 -06:00
|
|
|
const issueCompany = await db
|
|
|
|
|
.select({ companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issueCompany) throw notFound("Issue not found");
|
|
|
|
|
await assertAssignableAgent(issueCompany.companyId, agentId);
|
|
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
const now = new Date();
|
[codex] Add issue subtree pause, cancel, and restore controls (#4332)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - This branch extends the issue control-plane so board operators can
pause, cancel, and later restore whole issue subtrees while keeping
descendant execution and wake behavior coherent.
> - That required new hold state in the database, shared contracts,
server routes/services, and issue detail UI controls so subtree actions
are durable and auditable instead of ad hoc.
> - While this branch was in flight, `master` advanced with new
environment lifecycle work, including a new `0065_environments`
migration.
> - Before opening the PR, this branch had to be rebased onto
`paperclipai/paperclip:master` without losing the existing
subtree-control work or leaving conflicting migration numbering behind.
> - This pull request rebases the subtree pause/cancel/restore feature
cleanly onto current `master`, renumbers the hold migration to
`0066_issue_tree_holds`, and preserves the full branch diff in a single
PR.
> - The benefit is that reviewers get one clean, mergeable PR for the
subtree-control feature instead of stale branch history with migration
conflicts.
## What Changed
- Added durable issue subtree hold data structures, shared
API/types/validators, server routes/services, and UI flows for subtree
pause, cancel, and restore operations.
- Added server and UI coverage for subtree previewing, hold
creation/release, dependency-aware scheduling under holds, and issue
detail subtree controls.
- Rebased the branch onto current `paperclipai/paperclip:master` and
renumbered the branch migration from `0065_issue_tree_holds` to
`0066_issue_tree_holds` so it no longer conflicts with upstream
`0065_environments`.
- Added a small follow-up commit that makes restore requests return `200
OK` explicitly while keeping pause/cancel hold creation at `201
Created`, and updated the route test to match that contract.
## Verification
- `pnpm --filter @paperclipai/db typecheck`
- `pnpm --filter @paperclipai/shared typecheck`
- `pnpm --filter @paperclipai/server typecheck`
- `pnpm --filter @paperclipai/ui typecheck`
- `cd server && pnpm exec vitest run
src/__tests__/issue-tree-control-routes.test.ts
src/__tests__/issue-tree-control-service.test.ts
src/__tests__/issue-tree-control-service-unit.test.ts
src/__tests__/heartbeat-dependency-scheduling.test.ts`
- `cd ui && pnpm exec vitest run src/components/IssueChatThread.test.tsx
src/pages/IssueDetail.test.tsx`
## Risks
- This is a broad cross-layer change touching DB/schema, shared
contracts, server orchestration, and UI; regressions are most likely
around subtree status restoration or wake suppression/resume edge cases.
- The migration was renumbered during PR prep to avoid the new upstream
`0065_environments` conflict. Reviewers should confirm the final
`0066_issue_tree_holds` ordering is the only hold-related migration that
lands.
- The issue-tree restore endpoint now responds with `200` instead of
relying on implicit behavior, which is semantically better for a restore
operation but still changes an API detail that clients or tests could
have assumed.
> 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 coding agent in the Paperclip Codex runtime (GPT-5-class
tool-using coding model; exact deployment ID/context window is not
exposed inside this session).
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-23 14:51:46 -05:00
|
|
|
const activePauseHold = await treeControlSvc.getActivePauseHoldGate(issueCompany.companyId, id);
|
|
|
|
|
if (
|
|
|
|
|
activePauseHold &&
|
|
|
|
|
!(await isTreeHoldInteractionCheckoutAllowed(issueCompany.companyId, checkoutRunId, activePauseHold))
|
|
|
|
|
) {
|
|
|
|
|
throw conflict("Issue checkout blocked by active subtree pause hold", {
|
|
|
|
|
issueId: id,
|
|
|
|
|
holdId: activePauseHold.holdId,
|
|
|
|
|
rootIssueId: activePauseHold.rootIssueId,
|
|
|
|
|
mode: activePauseHold.mode,
|
|
|
|
|
securityPrinciples: ["Complete Mediation", "Fail Securely", "Secure Defaults"],
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-04-03 10:03:43 +02:00
|
|
|
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
await clearExecutionRunIfTerminal(id);
|
2026-04-03 10:03:43 +02:00
|
|
|
|
2026-04-20 16:03:57 -05:00
|
|
|
const dependencyReadiness = await listIssueDependencyReadinessMap(db, issueCompany.companyId, [id]);
|
|
|
|
|
const unresolvedBlockerIssueIds = dependencyReadiness.get(id)?.unresolvedBlockerIssueIds ?? [];
|
|
|
|
|
if (unresolvedBlockerIssueIds.length > 0) {
|
|
|
|
|
throw unprocessable("Issue is blocked by unresolved blockers", { unresolvedBlockerIssueIds });
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
const sameRunAssigneeCondition = checkoutRunId
|
|
|
|
|
? and(
|
|
|
|
|
eq(issues.assigneeAgentId, agentId),
|
|
|
|
|
or(isNull(issues.checkoutRunId), eq(issues.checkoutRunId, checkoutRunId)),
|
|
|
|
|
)
|
|
|
|
|
: and(eq(issues.assigneeAgentId, agentId), isNull(issues.checkoutRunId));
|
|
|
|
|
const executionLockCondition = checkoutRunId
|
|
|
|
|
? or(isNull(issues.executionRunId), eq(issues.executionRunId, checkoutRunId))
|
|
|
|
|
: isNull(issues.executionRunId);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
const updated = await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
assigneeAgentId: agentId,
|
2026-02-23 14:40:32 -06:00
|
|
|
assigneeUserId: null,
|
2026-02-20 15:48:22 -06:00
|
|
|
checkoutRunId,
|
|
|
|
|
executionRunId: checkoutRunId,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
status: "in_progress",
|
|
|
|
|
startedAt: now,
|
|
|
|
|
updatedAt: now,
|
|
|
|
|
})
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.id, id),
|
|
|
|
|
inArray(issues.status, expectedStatuses),
|
2026-02-20 15:48:22 -06:00
|
|
|
or(isNull(issues.assigneeAgentId), sameRunAssigneeCondition),
|
|
|
|
|
executionLockCondition,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
if (updated) {
|
|
|
|
|
const [enriched] = await withIssueLabels(db, [updated]);
|
|
|
|
|
return enriched;
|
|
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
|
|
|
|
const current = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
2026-02-20 15:48:22 -06:00
|
|
|
checkoutRunId: issues.checkoutRunId,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!current) throw notFound("Issue not found");
|
|
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
if (
|
|
|
|
|
current.assigneeAgentId === agentId &&
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
current.checkoutRunId == null &&
|
|
|
|
|
(current.executionRunId == null || current.executionRunId === checkoutRunId) &&
|
|
|
|
|
checkoutRunId
|
|
|
|
|
) {
|
|
|
|
|
const adopted = await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
checkoutRunId,
|
|
|
|
|
executionRunId: checkoutRunId,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(issues.id, id),
|
|
|
|
|
eq(issues.status, "in_progress"),
|
|
|
|
|
eq(issues.assigneeAgentId, agentId),
|
|
|
|
|
isNull(issues.checkoutRunId),
|
|
|
|
|
or(isNull(issues.executionRunId), eq(issues.executionRunId, checkoutRunId)),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (adopted) return adopted;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 08:23:44 -06:00
|
|
|
if (
|
|
|
|
|
checkoutRunId &&
|
|
|
|
|
current.assigneeAgentId === agentId &&
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
current.checkoutRunId &&
|
|
|
|
|
current.checkoutRunId !== checkoutRunId
|
|
|
|
|
) {
|
|
|
|
|
const adopted = await adoptStaleCheckoutRun({
|
|
|
|
|
issueId: id,
|
|
|
|
|
actorAgentId: agentId,
|
|
|
|
|
actorRunId: checkoutRunId,
|
|
|
|
|
expectedCheckoutRunId: current.checkoutRunId,
|
|
|
|
|
});
|
2026-02-25 08:38:37 -06:00
|
|
|
if (adopted) {
|
2026-04-03 09:56:23 +09:00
|
|
|
const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) throw notFound("Issue not found");
|
2026-02-25 08:38:37 -06:00
|
|
|
const [enriched] = await withIssueLabels(db, [row]);
|
|
|
|
|
return enriched;
|
|
|
|
|
}
|
2026-02-21 08:23:44 -06:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
// If this run already owns it and it's in_progress, return it (no self-409)
|
|
|
|
|
if (
|
|
|
|
|
current.assigneeAgentId === agentId &&
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
sameRunLock(current.checkoutRunId, checkoutRunId)
|
|
|
|
|
) {
|
2026-04-03 09:56:23 +09:00
|
|
|
const row = await db.select().from(issues).where(eq(issues.id, id)).then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) throw notFound("Issue not found");
|
2026-02-25 08:38:37 -06:00
|
|
|
const [enriched] = await withIssueLabels(db, [row]);
|
|
|
|
|
return enriched;
|
2026-02-17 20:46:12 -06:00
|
|
|
}
|
|
|
|
|
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
throw conflict("Issue checkout conflict", {
|
|
|
|
|
issueId: current.id,
|
|
|
|
|
status: current.status,
|
|
|
|
|
assigneeAgentId: current.assigneeAgentId,
|
2026-02-20 15:48:22 -06:00
|
|
|
checkoutRunId: current.checkoutRunId,
|
|
|
|
|
executionRunId: current.executionRunId,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
assertCheckoutOwner: async (id: string, actorAgentId: string, actorRunId: string | null) => {
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
await clearExecutionRunIfTerminal(id);
|
2026-02-20 15:48:22 -06:00
|
|
|
const current = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
status: issues.status,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId,
|
|
|
|
|
checkoutRunId: issues.checkoutRunId,
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
executionRunId: issues.executionRunId,
|
2026-02-20 15:48:22 -06:00
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!current) throw notFound("Issue not found");
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
current.assigneeAgentId === actorAgentId &&
|
|
|
|
|
sameRunLock(current.checkoutRunId, actorRunId)
|
|
|
|
|
) {
|
2026-02-21 08:23:44 -06:00
|
|
|
return { ...current, adoptedFromRunId: null as string | null };
|
|
|
|
|
}
|
|
|
|
|
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
if (
|
|
|
|
|
actorRunId &&
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
current.assigneeAgentId === actorAgentId &&
|
|
|
|
|
current.checkoutRunId == null &&
|
|
|
|
|
(current.executionRunId == null || current.executionRunId === actorRunId)
|
|
|
|
|
) {
|
|
|
|
|
const adopted = await adoptUnownedCheckoutRun({
|
|
|
|
|
issueId: id,
|
|
|
|
|
actorAgentId,
|
|
|
|
|
actorRunId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (adopted) {
|
|
|
|
|
return {
|
|
|
|
|
...adopted,
|
|
|
|
|
adoptedFromRunId: null as string | null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 08:23:44 -06:00
|
|
|
if (
|
|
|
|
|
actorRunId &&
|
|
|
|
|
current.status === "in_progress" &&
|
|
|
|
|
current.assigneeAgentId === actorAgentId &&
|
|
|
|
|
current.checkoutRunId &&
|
|
|
|
|
current.checkoutRunId !== actorRunId
|
|
|
|
|
) {
|
|
|
|
|
const adopted = await adoptStaleCheckoutRun({
|
|
|
|
|
issueId: id,
|
|
|
|
|
actorAgentId,
|
|
|
|
|
actorRunId,
|
|
|
|
|
expectedCheckoutRunId: current.checkoutRunId,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (adopted) {
|
|
|
|
|
return {
|
|
|
|
|
...adopted,
|
|
|
|
|
adoptedFromRunId: current.checkoutRunId,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-02-20 15:48:22 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
throw conflict("Issue run ownership conflict", {
|
|
|
|
|
issueId: current.id,
|
|
|
|
|
status: current.status,
|
|
|
|
|
assigneeAgentId: current.assigneeAgentId,
|
|
|
|
|
checkoutRunId: current.checkoutRunId,
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
executionRunId: current.executionRunId,
|
2026-02-20 15:48:22 -06:00
|
|
|
actorAgentId,
|
|
|
|
|
actorRunId,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-20 15:48:22 -06:00
|
|
|
release: async (id: string, actorAgentId?: string, actorRunId?: string | null) => {
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
await clearExecutionRunIfTerminal(id);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
const existing = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!existing) return null;
|
|
|
|
|
if (actorAgentId && existing.assigneeAgentId && existing.assigneeAgentId !== actorAgentId) {
|
|
|
|
|
throw conflict("Only assignee can release issue");
|
|
|
|
|
}
|
2026-02-20 15:48:22 -06:00
|
|
|
if (
|
|
|
|
|
actorAgentId &&
|
|
|
|
|
existing.status === "in_progress" &&
|
|
|
|
|
existing.assigneeAgentId === actorAgentId &&
|
|
|
|
|
existing.checkoutRunId &&
|
|
|
|
|
!sameRunLock(existing.checkoutRunId, actorRunId ?? null)
|
|
|
|
|
) {
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
const stale = await isTerminalOrMissingHeartbeatRun(existing.checkoutRunId);
|
|
|
|
|
if (!stale) {
|
|
|
|
|
throw conflict("Only checkout run can release issue", {
|
|
|
|
|
issueId: existing.id,
|
|
|
|
|
assigneeAgentId: existing.assigneeAgentId,
|
|
|
|
|
checkoutRunId: existing.checkoutRunId,
|
|
|
|
|
actorRunId: actorRunId ?? null,
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-20 15:48:22 -06:00
|
|
|
}
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
const updated = await db
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
.update(issues)
|
|
|
|
|
.set({
|
|
|
|
|
status: "todo",
|
|
|
|
|
assigneeAgentId: null,
|
2026-02-20 15:48:22 -06:00
|
|
|
checkoutRunId: null,
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
executionRunId: null,
|
|
|
|
|
executionAgentNameKey: null,
|
|
|
|
|
executionLockedAt: null,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
updatedAt: new Date(),
|
|
|
|
|
})
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
2026-02-25 08:38:37 -06:00
|
|
|
if (!updated) return null;
|
|
|
|
|
const [enriched] = await withIssueLabels(db, [updated]);
|
|
|
|
|
return enriched;
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
},
|
|
|
|
|
|
[codex] Fix stale issue execution run locks (#4258)
## Thinking Path
> - Paperclip is a control plane for AI-agent companies, so issue
checkout and execution ownership are core safety contracts.
> - The affected subsystem is the issue service and route layer that
gates agent writes by `checkoutRunId` and `executionRunId`.
> - PAP-1982 exposed a stale-lock failure mode where a terminal
heartbeat run could leave `executionRunId` pinned after checkout
ownership had moved or been cleared.
> - That stale execution lock could reject legitimate
PATCH/comment/release requests from the rightful assignee after a
harness restart.
> - This pull request centralizes terminal-run cleanup, applies it
before ownership-gated writes, and adds a board-only recovery endpoint
for operator intervention.
> - The benefit is that crashed or terminal runs no longer strand issues
behind stale execution locks, while live execution locks still block
conflicting writes.
## What Changed
- Added `issueService.clearExecutionRunIfTerminal()` to atomically lock
the issue/run rows and clear terminal or missing execution-run locks.
- Reused stale execution-lock cleanup from checkout,
`assertCheckoutOwner()`, and `release()`.
- Allowed the same assigned agent/current run to adopt an unowned
`in_progress` checkout after stale execution-lock cleanup.
- Updated release to clear `executionRunId`, `executionAgentNameKey`,
and `executionLockedAt`.
- Added board-only `POST /api/issues/:id/admin/force-release` with
company access checks, optional `clearAssignee=true`, and
`issue.admin_force_release` audit logging.
- Added embedded Postgres service tests and route integration tests for
stale-lock recovery, release behavior, and admin force-release
authorization/audit behavior.
- Documented the new force-release API in `doc/SPEC-implementation.md`.
## Verification
- `pnpm vitest run server/src/__tests__/issues-service.test.ts
server/src/__tests__/issue-stale-execution-lock-routes.test.ts` passed.
- `pnpm vitest run
server/src/__tests__/issue-stale-execution-lock-routes.test.ts
server/src/__tests__/approval-routes-idempotency.test.ts
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/__tests__/issue-telemetry-routes.test.ts` passed.
- `pnpm -r typecheck` passed.
- `pnpm build` passed.
- `git diff --check` passed.
- `pnpm lint` could not run because this repo has no `lint` command.
- Full `pnpm test:run` completed with 4 failures in existing route
suites: `approval-routes-idempotency.test.ts` (2),
`issue-comment-reopen-routes.test.ts` (1), and
`issue-telemetry-routes.test.ts` (1). Those same files pass when run
isolated and when run together with the new stale-lock route test, so
this appears to be a whole-suite ordering/mock-isolation issue outside
this patch path.
## Risks
- Medium: this changes ownership-gated write behavior. The new adoption
path is limited to the current run, the current assignee, `in_progress`
issues, and rows with no checkout owner after terminal-lock cleanup.
- Low: the admin force-release endpoint is board-only and
company-scoped, but misuse can intentionally clear a live lock. It
writes an audit event with prior lock IDs.
- No schema or migration changes.
> 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 coding agent (`gpt-5`), agentic coding with
terminal/tool use and local test execution.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-04-22 10:43:38 -05:00
|
|
|
adminForceRelease: async (id: string, options: { clearAssignee?: boolean } = {}) =>
|
|
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
await tx.execute(
|
|
|
|
|
sql`select ${issues.id} from ${issues} where ${issues.id} = ${id} for update`,
|
|
|
|
|
);
|
|
|
|
|
const existing = await tx
|
|
|
|
|
.select({
|
|
|
|
|
id: issues.id,
|
|
|
|
|
checkoutRunId: issues.checkoutRunId,
|
|
|
|
|
executionRunId: issues.executionRunId,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!existing) return null;
|
|
|
|
|
|
|
|
|
|
const patch: Partial<typeof issues.$inferInsert> = {
|
|
|
|
|
checkoutRunId: null,
|
|
|
|
|
executionRunId: null,
|
|
|
|
|
executionAgentNameKey: null,
|
|
|
|
|
executionLockedAt: null,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
};
|
|
|
|
|
if (options.clearAssignee) {
|
|
|
|
|
patch.assigneeAgentId = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = await tx
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set(patch)
|
|
|
|
|
.where(eq(issues.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!updated) return null;
|
|
|
|
|
|
|
|
|
|
const [enriched] = await withIssueLabels(tx, [updated]);
|
|
|
|
|
return {
|
|
|
|
|
issue: enriched,
|
|
|
|
|
previous: {
|
|
|
|
|
checkoutRunId: existing.checkoutRunId,
|
|
|
|
|
executionRunId: existing.executionRunId,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}),
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
listLabels: (companyId: string) =>
|
|
|
|
|
db.select().from(labels).where(eq(labels.companyId, companyId)).orderBy(asc(labels.name), asc(labels.id)),
|
|
|
|
|
|
|
|
|
|
getLabelById: (id: string) =>
|
|
|
|
|
db
|
|
|
|
|
.select()
|
|
|
|
|
.from(labels)
|
|
|
|
|
.where(eq(labels.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null),
|
|
|
|
|
|
|
|
|
|
createLabel: async (companyId: string, data: Pick<typeof labels.$inferInsert, "name" | "color">) => {
|
|
|
|
|
const [created] = await db
|
|
|
|
|
.insert(labels)
|
|
|
|
|
.values({
|
|
|
|
|
companyId,
|
|
|
|
|
name: data.name.trim(),
|
|
|
|
|
color: data.color,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
return created;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
deleteLabel: async (id: string) =>
|
|
|
|
|
db
|
|
|
|
|
.delete(labels)
|
|
|
|
|
.where(eq(labels.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null),
|
|
|
|
|
|
2026-03-13 08:49:11 -05:00
|
|
|
listComments: async (
|
|
|
|
|
issueId: string,
|
|
|
|
|
opts?: {
|
|
|
|
|
afterCommentId?: string | null;
|
|
|
|
|
order?: "asc" | "desc";
|
|
|
|
|
limit?: number | null;
|
|
|
|
|
},
|
|
|
|
|
) => {
|
|
|
|
|
const order = opts?.order === "asc" ? "asc" : "desc";
|
|
|
|
|
const afterCommentId = opts?.afterCommentId?.trim() || null;
|
2026-03-13 10:18:00 -05:00
|
|
|
const limit =
|
|
|
|
|
opts?.limit && opts.limit > 0
|
|
|
|
|
? Math.min(Math.floor(opts.limit), MAX_ISSUE_COMMENT_PAGE_LIMIT)
|
|
|
|
|
: null;
|
2026-03-13 08:49:11 -05:00
|
|
|
|
|
|
|
|
const conditions = [eq(issueComments.issueId, issueId)];
|
|
|
|
|
if (afterCommentId) {
|
|
|
|
|
const anchor = await db
|
|
|
|
|
.select({
|
|
|
|
|
id: issueComments.id,
|
|
|
|
|
createdAt: issueComments.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(and(eq(issueComments.issueId, issueId), eq(issueComments.id, afterCommentId)))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!anchor) return [];
|
|
|
|
|
conditions.push(
|
2026-03-13 10:18:00 -05:00
|
|
|
order === "asc"
|
2026-04-26 16:23:53 -07:00
|
|
|
? or(
|
|
|
|
|
gt(issueComments.createdAt, anchor.createdAt),
|
|
|
|
|
and(eq(issueComments.createdAt, anchor.createdAt), gt(issueComments.id, anchor.id)),
|
|
|
|
|
)!
|
|
|
|
|
: or(
|
|
|
|
|
lt(issueComments.createdAt, anchor.createdAt),
|
|
|
|
|
and(eq(issueComments.createdAt, anchor.createdAt), lt(issueComments.id, anchor.id)),
|
|
|
|
|
)!,
|
2026-03-13 08:49:11 -05:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = db
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
.select()
|
|
|
|
|
.from(issueComments)
|
2026-03-13 08:49:11 -05:00
|
|
|
.where(and(...conditions))
|
2026-03-13 10:18:00 -05:00
|
|
|
.orderBy(
|
|
|
|
|
order === "asc" ? asc(issueComments.createdAt) : desc(issueComments.createdAt),
|
|
|
|
|
order === "asc" ? asc(issueComments.id) : desc(issueComments.id),
|
|
|
|
|
);
|
2026-03-13 08:49:11 -05:00
|
|
|
|
|
|
|
|
const comments = limit ? await query.limit(limit) : await query;
|
2026-03-20 08:00:39 -05:00
|
|
|
const { censorUsernameInLogs } = await instanceSettings.getGeneral();
|
|
|
|
|
return comments.map((comment) => redactIssueComment(comment, censorUsernameInLogs));
|
2026-03-13 08:49:11 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getCommentCursor: async (issueId: string) => {
|
2026-03-13 14:53:30 -05:00
|
|
|
const [latest, countRow] = await Promise.all([
|
|
|
|
|
db
|
|
|
|
|
.select({
|
|
|
|
|
latestCommentId: issueComments.id,
|
|
|
|
|
latestCommentAt: issueComments.createdAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(eq(issueComments.issueId, issueId))
|
|
|
|
|
.orderBy(desc(issueComments.createdAt), desc(issueComments.id))
|
|
|
|
|
.limit(1)
|
|
|
|
|
.then((rows) => rows[0] ?? null),
|
|
|
|
|
db
|
|
|
|
|
.select({
|
|
|
|
|
totalComments: sql<number>`count(*)::int`,
|
|
|
|
|
})
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(eq(issueComments.issueId, issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null),
|
|
|
|
|
]);
|
2026-03-13 08:49:11 -05:00
|
|
|
|
|
|
|
|
return {
|
2026-03-13 14:53:30 -05:00
|
|
|
totalComments: Number(countRow?.totalComments ?? 0),
|
2026-03-13 08:49:11 -05:00
|
|
|
latestCommentId: latest?.latestCommentId ?? null,
|
|
|
|
|
latestCommentAt: latest?.latestCommentAt ?? null,
|
|
|
|
|
};
|
|
|
|
|
},
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
|
2026-03-05 11:02:22 -06:00
|
|
|
getComment: (commentId: string) =>
|
2026-03-20 08:00:39 -05:00
|
|
|
instanceSettings.getGeneral().then(({ censorUsernameInLogs }) =>
|
|
|
|
|
db
|
2026-03-05 11:02:22 -06:00
|
|
|
.select()
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(eq(issueComments.id, commentId))
|
2026-03-11 22:17:21 -05:00
|
|
|
.then((rows) => {
|
|
|
|
|
const comment = rows[0] ?? null;
|
2026-03-20 08:00:39 -05:00
|
|
|
return comment ? redactIssueComment(comment, censorUsernameInLogs) : null;
|
|
|
|
|
})),
|
2026-03-05 11:02:22 -06:00
|
|
|
|
[codex] Improve issue detail and issue-list UX (#3678)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - A core part of that is the operator experience around reading issue
state, agent chat, and sub-task structure
> - The current branch had a long run of issue-detail and issue-list UX
fixes that all improve how humans follow and steer active work
> - Those changes mostly live in the UI/chat surface and should be
reviewed together instead of mixed with workspace/runtime work
> - This pull request packages the issue-detail, chat, markdown, and
sub-issue list improvements into one standalone change
> - The benefit is a cleaner, less jumpy, more reliable issue workflow
on desktop and mobile without coupling it to unrelated server/runtime
refactors
## What Changed
- Stabilized issue chat runtime wiring, optimistic comment handling,
queued-comment cancellation, and composer anchoring during live updates
- Fixed several issue-detail rendering and navigation regressions
including placeholder bleed, local polling scope, mobile inbox-to-issue
transitions, and visible refresh resets
- Improved markdown and rich-content handling with advisory image
normalization, editor fallback behavior, touch mention recovery, and
`issue:` quicklook links
- Refined sub-issue behavior with parent-derived defaults, current-user
inheritance fixes, empty-state cleanup, and a reusable issue-list
presentation for sub-issues
- Added targeted UI tests for the new issue-detail, chat scroll/message,
placeholder-data, markdown, and issue-list behaviors
## Verification
- `pnpm vitest run ui/src/components/IssueChatThread.test.tsx
ui/src/components/MarkdownEditor.test.tsx
ui/src/components/IssuesList.test.tsx
ui/src/context/LiveUpdatesProvider.test.tsx
ui/src/lib/issue-chat-messages.test.ts
ui/src/lib/issue-chat-scroll.test.ts
ui/src/lib/issue-detail-subissues.test.ts
ui/src/lib/query-placeholder-data.test.tsx
ui/src/hooks/usePaperclipIssueRuntime.test.tsx`
## Risks
- Medium: this branch touches the highest-traffic issue-detail UI paths,
so regressions would show up as chat/thread or sub-issue UX glitches
- The changes are UI-heavy and would benefit from reviewer screenshots
or a quick manual browser pass before merge
## Model Used
- OpenAI Codex coding agent (GPT-5-class runtime in Codex CLI; exact
deployed model ID is not exposed in this environment), reasoning
enabled, tool use and local code execution enabled
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [ ] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-14 12:50:48 -05:00
|
|
|
removeComment: async (commentId: string) => {
|
|
|
|
|
const currentUserRedactionOptions = {
|
|
|
|
|
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return db.transaction(async (tx) => {
|
|
|
|
|
const [comment] = await tx
|
|
|
|
|
.delete(issueComments)
|
|
|
|
|
.where(eq(issueComments.id, commentId))
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
if (!comment) return null;
|
|
|
|
|
|
|
|
|
|
await tx
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({ updatedAt: new Date() })
|
|
|
|
|
.where(eq(issues.id, comment.issueId));
|
|
|
|
|
|
|
|
|
|
return redactIssueComment(comment, currentUserRedactionOptions.enabled);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2026-04-02 09:11:49 -05:00
|
|
|
addComment: async (
|
|
|
|
|
issueId: string,
|
|
|
|
|
body: string,
|
|
|
|
|
actor: { agentId?: string; userId?: string; runId?: string | null },
|
|
|
|
|
) => {
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
const issue = await db
|
|
|
|
|
.select({ companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
|
|
|
|
|
if (!issue) throw notFound("Issue not found");
|
|
|
|
|
|
2026-03-20 08:00:39 -05:00
|
|
|
const currentUserRedactionOptions = {
|
|
|
|
|
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
|
|
|
};
|
|
|
|
|
const redactedBody = redactCurrentUserText(body, currentUserRedactionOptions);
|
2026-03-02 16:55:37 -06:00
|
|
|
const [comment] = await db
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
.insert(issueComments)
|
|
|
|
|
.values({
|
|
|
|
|
companyId: issue.companyId,
|
|
|
|
|
issueId,
|
|
|
|
|
authorAgentId: actor.agentId ?? null,
|
|
|
|
|
authorUserId: actor.userId ?? null,
|
2026-04-02 09:11:49 -05:00
|
|
|
createdByRunId: actor.runId ?? null,
|
2026-03-11 22:17:21 -05:00
|
|
|
body: redactedBody,
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
})
|
2026-03-02 16:55:37 -06:00
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
// Update issue's updatedAt so comment activity is reflected in recency sorting
|
|
|
|
|
await db
|
|
|
|
|
.update(issues)
|
|
|
|
|
.set({ updatedAt: new Date() })
|
|
|
|
|
.where(eq(issues.id, issueId));
|
|
|
|
|
|
2026-03-20 08:00:39 -05:00
|
|
|
return redactIssueComment(comment, currentUserRedactionOptions.enabled);
|
Add server routes for companies, approvals, costs, and dashboard
New routes: companies, approvals, costs, dashboard, authz. New
services: companies, approvals, costs, dashboard, heartbeat,
activity-log. Add auth middleware and structured error handling.
Expand existing agent and issue routes with richer CRUD operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 09:07:27 -06:00
|
|
|
},
|
|
|
|
|
|
2026-02-20 10:31:56 -06:00
|
|
|
createAttachment: async (input: {
|
|
|
|
|
issueId: string;
|
|
|
|
|
issueCommentId?: string | null;
|
|
|
|
|
provider: string;
|
|
|
|
|
objectKey: string;
|
|
|
|
|
contentType: string;
|
|
|
|
|
byteSize: number;
|
|
|
|
|
sha256: string;
|
|
|
|
|
originalFilename?: string | null;
|
|
|
|
|
createdByAgentId?: string | null;
|
|
|
|
|
createdByUserId?: string | null;
|
|
|
|
|
}) => {
|
|
|
|
|
const issue = await db
|
|
|
|
|
.select({ id: issues.id, companyId: issues.companyId })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, input.issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issue) throw notFound("Issue not found");
|
|
|
|
|
|
|
|
|
|
if (input.issueCommentId) {
|
|
|
|
|
const comment = await db
|
|
|
|
|
.select({ id: issueComments.id, companyId: issueComments.companyId, issueId: issueComments.issueId })
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(eq(issueComments.id, input.issueCommentId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!comment) throw notFound("Issue comment not found");
|
|
|
|
|
if (comment.companyId !== issue.companyId || comment.issueId !== issue.id) {
|
|
|
|
|
throw unprocessable("Attachment comment must belong to same issue and company");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return db.transaction(async (tx) => {
|
|
|
|
|
const [asset] = await tx
|
|
|
|
|
.insert(assets)
|
|
|
|
|
.values({
|
|
|
|
|
companyId: issue.companyId,
|
|
|
|
|
provider: input.provider,
|
|
|
|
|
objectKey: input.objectKey,
|
|
|
|
|
contentType: input.contentType,
|
|
|
|
|
byteSize: input.byteSize,
|
|
|
|
|
sha256: input.sha256,
|
|
|
|
|
originalFilename: input.originalFilename ?? null,
|
|
|
|
|
createdByAgentId: input.createdByAgentId ?? null,
|
|
|
|
|
createdByUserId: input.createdByUserId ?? null,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
const [attachment] = await tx
|
|
|
|
|
.insert(issueAttachments)
|
|
|
|
|
.values({
|
|
|
|
|
companyId: issue.companyId,
|
|
|
|
|
issueId: issue.id,
|
|
|
|
|
assetId: asset.id,
|
|
|
|
|
issueCommentId: input.issueCommentId ?? null,
|
|
|
|
|
})
|
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: attachment.id,
|
|
|
|
|
companyId: attachment.companyId,
|
|
|
|
|
issueId: attachment.issueId,
|
|
|
|
|
issueCommentId: attachment.issueCommentId,
|
|
|
|
|
assetId: attachment.assetId,
|
|
|
|
|
provider: asset.provider,
|
|
|
|
|
objectKey: asset.objectKey,
|
|
|
|
|
contentType: asset.contentType,
|
|
|
|
|
byteSize: asset.byteSize,
|
|
|
|
|
sha256: asset.sha256,
|
|
|
|
|
originalFilename: asset.originalFilename,
|
|
|
|
|
createdByAgentId: asset.createdByAgentId,
|
|
|
|
|
createdByUserId: asset.createdByUserId,
|
|
|
|
|
createdAt: attachment.createdAt,
|
|
|
|
|
updatedAt: attachment.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
listAttachments: async (issueId: string) =>
|
|
|
|
|
db
|
|
|
|
|
.select({
|
|
|
|
|
id: issueAttachments.id,
|
|
|
|
|
companyId: issueAttachments.companyId,
|
|
|
|
|
issueId: issueAttachments.issueId,
|
|
|
|
|
issueCommentId: issueAttachments.issueCommentId,
|
|
|
|
|
assetId: issueAttachments.assetId,
|
|
|
|
|
provider: assets.provider,
|
|
|
|
|
objectKey: assets.objectKey,
|
|
|
|
|
contentType: assets.contentType,
|
|
|
|
|
byteSize: assets.byteSize,
|
|
|
|
|
sha256: assets.sha256,
|
|
|
|
|
originalFilename: assets.originalFilename,
|
|
|
|
|
createdByAgentId: assets.createdByAgentId,
|
|
|
|
|
createdByUserId: assets.createdByUserId,
|
|
|
|
|
createdAt: issueAttachments.createdAt,
|
|
|
|
|
updatedAt: issueAttachments.updatedAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueAttachments)
|
|
|
|
|
.innerJoin(assets, eq(issueAttachments.assetId, assets.id))
|
|
|
|
|
.where(eq(issueAttachments.issueId, issueId))
|
|
|
|
|
.orderBy(desc(issueAttachments.createdAt)),
|
|
|
|
|
|
|
|
|
|
getAttachmentById: async (id: string) =>
|
|
|
|
|
db
|
|
|
|
|
.select({
|
|
|
|
|
id: issueAttachments.id,
|
|
|
|
|
companyId: issueAttachments.companyId,
|
|
|
|
|
issueId: issueAttachments.issueId,
|
|
|
|
|
issueCommentId: issueAttachments.issueCommentId,
|
|
|
|
|
assetId: issueAttachments.assetId,
|
|
|
|
|
provider: assets.provider,
|
|
|
|
|
objectKey: assets.objectKey,
|
|
|
|
|
contentType: assets.contentType,
|
|
|
|
|
byteSize: assets.byteSize,
|
|
|
|
|
sha256: assets.sha256,
|
|
|
|
|
originalFilename: assets.originalFilename,
|
|
|
|
|
createdByAgentId: assets.createdByAgentId,
|
|
|
|
|
createdByUserId: assets.createdByUserId,
|
|
|
|
|
createdAt: issueAttachments.createdAt,
|
|
|
|
|
updatedAt: issueAttachments.updatedAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueAttachments)
|
|
|
|
|
.innerJoin(assets, eq(issueAttachments.assetId, assets.id))
|
|
|
|
|
.where(eq(issueAttachments.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null),
|
|
|
|
|
|
|
|
|
|
removeAttachment: async (id: string) =>
|
|
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
const existing = await tx
|
|
|
|
|
.select({
|
|
|
|
|
id: issueAttachments.id,
|
|
|
|
|
companyId: issueAttachments.companyId,
|
|
|
|
|
issueId: issueAttachments.issueId,
|
|
|
|
|
issueCommentId: issueAttachments.issueCommentId,
|
|
|
|
|
assetId: issueAttachments.assetId,
|
|
|
|
|
provider: assets.provider,
|
|
|
|
|
objectKey: assets.objectKey,
|
|
|
|
|
contentType: assets.contentType,
|
|
|
|
|
byteSize: assets.byteSize,
|
|
|
|
|
sha256: assets.sha256,
|
|
|
|
|
originalFilename: assets.originalFilename,
|
|
|
|
|
createdByAgentId: assets.createdByAgentId,
|
|
|
|
|
createdByUserId: assets.createdByUserId,
|
|
|
|
|
createdAt: issueAttachments.createdAt,
|
|
|
|
|
updatedAt: issueAttachments.updatedAt,
|
|
|
|
|
})
|
|
|
|
|
.from(issueAttachments)
|
|
|
|
|
.innerJoin(assets, eq(issueAttachments.assetId, assets.id))
|
|
|
|
|
.where(eq(issueAttachments.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!existing) return null;
|
|
|
|
|
|
|
|
|
|
await tx.delete(issueAttachments).where(eq(issueAttachments.id, id));
|
|
|
|
|
await tx.delete(assets).where(eq(assets.id, existing.assetId));
|
|
|
|
|
return existing;
|
|
|
|
|
}),
|
|
|
|
|
|
2026-02-17 20:07:14 -06:00
|
|
|
findMentionedAgents: async (companyId: string, body: string) => {
|
|
|
|
|
const re = /\B@([^\s@,!?.]+)/g;
|
|
|
|
|
const tokens = new Set<string>();
|
|
|
|
|
let m: RegExpExecArray | null;
|
2026-03-20 16:38:55 +00:00
|
|
|
while ((m = re.exec(body)) !== null) {
|
|
|
|
|
const normalized = normalizeAgentMentionToken(m[1]);
|
|
|
|
|
if (normalized) tokens.add(normalized.toLowerCase());
|
|
|
|
|
}
|
2026-03-21 14:48:10 -05:00
|
|
|
|
|
|
|
|
const explicitAgentMentionIds = extractAgentMentionIds(body);
|
|
|
|
|
if (tokens.size === 0 && explicitAgentMentionIds.length === 0) return [];
|
2026-02-17 20:07:14 -06:00
|
|
|
const rows = await db.select({ id: agents.id, name: agents.name })
|
|
|
|
|
.from(agents).where(eq(agents.companyId, companyId));
|
2026-03-21 14:48:10 -05:00
|
|
|
const resolved = new Set<string>(explicitAgentMentionIds);
|
|
|
|
|
for (const agent of rows) {
|
|
|
|
|
if (tokens.has(agent.name.toLowerCase())) {
|
|
|
|
|
resolved.add(agent.id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return [...resolved];
|
2026-02-17 20:07:14 -06:00
|
|
|
},
|
|
|
|
|
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
findMentionedProjectIds: async (
|
|
|
|
|
issueId: string,
|
|
|
|
|
opts?: { includeCommentBodies?: boolean },
|
|
|
|
|
) => {
|
2026-03-02 13:31:58 -06:00
|
|
|
const issue = await db
|
|
|
|
|
.select({
|
|
|
|
|
companyId: issues.companyId,
|
|
|
|
|
title: issues.title,
|
|
|
|
|
description: issues.description,
|
|
|
|
|
})
|
|
|
|
|
.from(issues)
|
|
|
|
|
.where(eq(issues.id, issueId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!issue) return [];
|
|
|
|
|
|
|
|
|
|
const mentionedIds = new Set<string>();
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
for (const source of [issue.title, issue.description ?? ""]) {
|
2026-03-02 13:31:58 -06:00
|
|
|
for (const projectId of extractProjectMentionIds(source)) {
|
|
|
|
|
mentionedIds.add(projectId);
|
|
|
|
|
}
|
|
|
|
|
}
|
Sync/master post pap1497 followups 2026 04 15 (#3779)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - The board depends on issue, inbox, cost, and company-skill surfaces
to stay accurate and fast while agents are actively working
> - The PAP-1497 follow-up branch exposed a few rough edges in those
surfaces: stale active-run state on completed issues, missing creator
filters, oversized issue payload scans, and placeholder issue-route
parsing
> - Those gaps make the control plane harder to trust because operators
can see misleading run state, miss the right subset of work, or pay
extra query/render cost on large issue records
> - This pull request tightens those follow-ups across server and UI
code, and adds regression coverage for the affected paths
> - The benefit is a more reliable issue workflow, safer high-volume
cost aggregation, and clearer board/operator navigation
## What Changed
- Added the `v2026.415.0` release changelog entry.
- Fixed stale issue-run presentation after completion and reused the
shared issue-path parser so literal route placeholders no longer become
issue links.
- Added creator filters to the Issues page and Inbox, including
persisted filter-state normalization and regression coverage.
- Bounded issue detail/list project-mention scans and trimmed large
issue-list payload fields to keep issue reads lighter.
- Hardened company-skill list projection and cost/finance aggregation so
large markdown blobs and large summed values do not leak into list
responses or overflow 32-bit casts.
- Added targeted server/UI regression tests for company skills,
costs/finance, issue mention scanning, creator filters, inbox
normalization, and issue reference parsing.
## Verification
- `pnpm exec vitest run
server/src/__tests__/company-skills-service.test.ts
server/src/__tests__/costs-service.test.ts
server/src/__tests__/issues-goal-context-routes.test.ts
server/src/__tests__/issues-service.test.ts ui/src/lib/inbox.test.ts
ui/src/lib/issue-filters.test.ts ui/src/lib/issue-reference.test.ts`
- `gh pr checks 3779`
Current pass set on the PR head: `policy`, `verify`, `e2e`,
`security/snyk (cryppadotta)`, `Greptile Review`
## Risks
- Creator filter options are derived from the currently loaded
issue/agent data, so very sparse result sets may not surface every
historical creator until they appear in the active dataset.
- Cost/finance aggregate casts now use `double precision`; that removes
the current overflow risk, but future schema changes should keep
large-value aggregation behavior under review.
- Issue detail mention scanning now skips comment-body scans on the
detail route, so any consumer that relied on comment-only project
mentions there would need to fetch them separately.
## Model Used
- OpenAI Codex, GPT-5-based coding agent with terminal tool use and
local code execution in the Paperclip workspace. Exact internal model
ID/context-window exposure is not surfaced in this session.
## 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 run tests locally and they pass
- [x] I have added or updated tests where applicable
- [ ] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-04-15 21:13:56 -05:00
|
|
|
|
|
|
|
|
if (opts?.includeCommentBodies !== false) {
|
|
|
|
|
const comments = await db
|
|
|
|
|
.select({ body: issueComments.body })
|
|
|
|
|
.from(issueComments)
|
|
|
|
|
.where(eq(issueComments.issueId, issueId));
|
|
|
|
|
|
|
|
|
|
for (const comment of comments) {
|
|
|
|
|
for (const projectId of extractProjectMentionIds(comment.body)) {
|
|
|
|
|
mentionedIds.add(projectId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 13:31:58 -06:00
|
|
|
if (mentionedIds.size === 0) return [];
|
|
|
|
|
|
|
|
|
|
const rows = await db
|
|
|
|
|
.select({ id: projects.id })
|
|
|
|
|
.from(projects)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
eq(projects.companyId, issue.companyId),
|
|
|
|
|
inArray(projects.id, [...mentionedIds]),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
const valid = new Set(rows.map((row) => row.id));
|
|
|
|
|
return [...mentionedIds].filter((projectId) => valid.has(projectId));
|
|
|
|
|
},
|
|
|
|
|
|
2026-02-17 20:07:14 -06:00
|
|
|
getAncestors: async (issueId: string) => {
|
2026-02-20 10:31:56 -06:00
|
|
|
const raw: Array<{
|
2026-02-20 16:04:05 -06:00
|
|
|
id: string; identifier: string | null; title: string; description: string | null;
|
2026-02-17 20:07:14 -06:00
|
|
|
status: string; priority: string;
|
|
|
|
|
assigneeAgentId: string | null; projectId: string | null; goalId: string | null;
|
|
|
|
|
}> = [];
|
|
|
|
|
const visited = new Set<string>([issueId]);
|
|
|
|
|
const start = await db.select().from(issues).where(eq(issues.id, issueId)).then(r => r[0] ?? null);
|
|
|
|
|
let currentId = start?.parentId ?? null;
|
2026-02-20 10:31:56 -06:00
|
|
|
while (currentId && !visited.has(currentId) && raw.length < 50) {
|
2026-02-17 20:07:14 -06:00
|
|
|
visited.add(currentId);
|
|
|
|
|
const parent = await db.select({
|
2026-02-20 16:04:05 -06:00
|
|
|
id: issues.id, identifier: issues.identifier, title: issues.title, description: issues.description,
|
2026-02-17 20:07:14 -06:00
|
|
|
status: issues.status, priority: issues.priority,
|
|
|
|
|
assigneeAgentId: issues.assigneeAgentId, projectId: issues.projectId,
|
|
|
|
|
goalId: issues.goalId, parentId: issues.parentId,
|
|
|
|
|
}).from(issues).where(eq(issues.id, currentId)).then(r => r[0] ?? null);
|
|
|
|
|
if (!parent) break;
|
2026-02-20 10:31:56 -06:00
|
|
|
raw.push({
|
2026-02-20 16:04:05 -06:00
|
|
|
id: parent.id, identifier: parent.identifier ?? null, title: parent.title, description: parent.description ?? null,
|
2026-02-17 20:07:14 -06:00
|
|
|
status: parent.status, priority: parent.priority,
|
|
|
|
|
assigneeAgentId: parent.assigneeAgentId ?? null,
|
|
|
|
|
projectId: parent.projectId ?? null, goalId: parent.goalId ?? null,
|
|
|
|
|
});
|
|
|
|
|
currentId = parent.parentId ?? null;
|
|
|
|
|
}
|
2026-02-20 10:31:56 -06:00
|
|
|
|
|
|
|
|
// Batch-fetch referenced projects and goals
|
|
|
|
|
const projectIds = [...new Set(raw.map(a => a.projectId).filter((id): id is string => id != null))];
|
|
|
|
|
const goalIds = [...new Set(raw.map(a => a.goalId).filter((id): id is string => id != null))];
|
|
|
|
|
|
2026-02-25 08:38:37 -06:00
|
|
|
const projectMap = new Map<string, {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description: string | null;
|
|
|
|
|
status: string;
|
|
|
|
|
goalId: string | null;
|
|
|
|
|
workspaces: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
companyId: string;
|
|
|
|
|
projectId: string;
|
|
|
|
|
name: string;
|
2026-02-25 21:35:33 -06:00
|
|
|
cwd: string | null;
|
2026-02-25 08:38:37 -06:00
|
|
|
repoUrl: string | null;
|
|
|
|
|
repoRef: string | null;
|
|
|
|
|
metadata: Record<string, unknown> | null;
|
|
|
|
|
isPrimary: boolean;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
}>;
|
|
|
|
|
primaryWorkspace: {
|
|
|
|
|
id: string;
|
|
|
|
|
companyId: string;
|
|
|
|
|
projectId: string;
|
|
|
|
|
name: string;
|
2026-02-25 21:35:33 -06:00
|
|
|
cwd: string | null;
|
2026-02-25 08:38:37 -06:00
|
|
|
repoUrl: string | null;
|
|
|
|
|
repoRef: string | null;
|
|
|
|
|
metadata: Record<string, unknown> | null;
|
|
|
|
|
isPrimary: boolean;
|
|
|
|
|
createdAt: Date;
|
|
|
|
|
updatedAt: Date;
|
|
|
|
|
} | null;
|
|
|
|
|
}>();
|
2026-02-20 10:31:56 -06:00
|
|
|
const goalMap = new Map<string, { id: string; title: string; description: string | null; level: string; status: string }>();
|
|
|
|
|
|
|
|
|
|
if (projectIds.length > 0) {
|
2026-02-25 08:38:37 -06:00
|
|
|
const workspaceRows = await db
|
|
|
|
|
.select()
|
|
|
|
|
.from(projectWorkspaces)
|
|
|
|
|
.where(inArray(projectWorkspaces.projectId, projectIds))
|
|
|
|
|
.orderBy(desc(projectWorkspaces.isPrimary), asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id));
|
|
|
|
|
const workspaceMap = new Map<string, Array<(typeof workspaceRows)[number]>>();
|
|
|
|
|
for (const workspace of workspaceRows) {
|
|
|
|
|
const existing = workspaceMap.get(workspace.projectId);
|
|
|
|
|
if (existing) existing.push(workspace);
|
|
|
|
|
else workspaceMap.set(workspace.projectId, [workspace]);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-20 10:31:56 -06:00
|
|
|
const rows = await db.select({
|
|
|
|
|
id: projects.id, name: projects.name, description: projects.description,
|
|
|
|
|
status: projects.status, goalId: projects.goalId,
|
|
|
|
|
}).from(projects).where(inArray(projects.id, projectIds));
|
|
|
|
|
for (const r of rows) {
|
2026-02-25 08:38:37 -06:00
|
|
|
const projectWorkspaceRows = workspaceMap.get(r.id) ?? [];
|
|
|
|
|
const workspaces = projectWorkspaceRows.map((workspace) => ({
|
|
|
|
|
id: workspace.id,
|
|
|
|
|
companyId: workspace.companyId,
|
|
|
|
|
projectId: workspace.projectId,
|
|
|
|
|
name: workspace.name,
|
|
|
|
|
cwd: workspace.cwd,
|
|
|
|
|
repoUrl: workspace.repoUrl ?? null,
|
|
|
|
|
repoRef: workspace.repoRef ?? null,
|
|
|
|
|
metadata: (workspace.metadata as Record<string, unknown> | null) ?? null,
|
|
|
|
|
isPrimary: workspace.isPrimary,
|
|
|
|
|
createdAt: workspace.createdAt,
|
|
|
|
|
updatedAt: workspace.updatedAt,
|
|
|
|
|
}));
|
|
|
|
|
const primaryWorkspace = workspaces.find((workspace) => workspace.isPrimary) ?? workspaces[0] ?? null;
|
|
|
|
|
projectMap.set(r.id, {
|
|
|
|
|
...r,
|
|
|
|
|
workspaces,
|
|
|
|
|
primaryWorkspace,
|
|
|
|
|
});
|
2026-02-20 10:31:56 -06:00
|
|
|
// Also collect goalIds from projects
|
|
|
|
|
if (r.goalId && !goalIds.includes(r.goalId)) goalIds.push(r.goalId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (goalIds.length > 0) {
|
|
|
|
|
const rows = await db.select({
|
|
|
|
|
id: goals.id, title: goals.title, description: goals.description,
|
|
|
|
|
level: goals.level, status: goals.status,
|
|
|
|
|
}).from(goals).where(inArray(goals.id, goalIds));
|
|
|
|
|
for (const r of rows) goalMap.set(r.id, r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return raw.map(a => ({
|
|
|
|
|
...a,
|
|
|
|
|
project: a.projectId ? projectMap.get(a.projectId) ?? null : null,
|
|
|
|
|
goal: a.goalId ? goalMap.get(a.goalId) ?? null : null,
|
|
|
|
|
}));
|
2026-02-17 20:07:14 -06:00
|
|
|
},
|
2026-02-16 13:31:58 -06:00
|
|
|
};
|
|
|
|
|
}
|