2026-03-16 15:41:48 -05:00
|
|
|
import { and, count, eq, gte, inArray, lt, sql } from "drizzle-orm";
|
2026-03-03 08:45:26 -06:00
|
|
|
import type { Db } from "@paperclipai/db";
|
2026-02-17 20:14:05 -06:00
|
|
|
import {
|
|
|
|
|
companies,
|
2026-03-16 09:25:39 -05:00
|
|
|
companyLogos,
|
|
|
|
|
assets,
|
2026-02-17 20:14:05 -06:00
|
|
|
agents,
|
|
|
|
|
agentApiKeys,
|
|
|
|
|
agentRuntimeState,
|
2026-02-19 14:02:17 -06:00
|
|
|
agentTaskSessions,
|
2026-02-17 20:14:05 -06:00
|
|
|
agentWakeupRequests,
|
|
|
|
|
issues,
|
|
|
|
|
issueComments,
|
|
|
|
|
projects,
|
|
|
|
|
goals,
|
|
|
|
|
heartbeatRuns,
|
|
|
|
|
heartbeatRunEvents,
|
|
|
|
|
costEvents,
|
2026-03-14 22:00:12 -05:00
|
|
|
financeEvents,
|
2026-04-07 18:20:35 -05:00
|
|
|
issueReadStates,
|
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
|
|
|
approvalComments,
|
2026-02-17 20:14:05 -06:00
|
|
|
approvals,
|
|
|
|
|
activityLog,
|
2026-02-19 15:43:52 -06:00
|
|
|
companySecrets,
|
2026-02-23 14:40:32 -06:00
|
|
|
joinRequests,
|
|
|
|
|
invites,
|
|
|
|
|
principalPermissionGrants,
|
|
|
|
|
companyMemberships,
|
2026-04-07 18:20:35 -05:00
|
|
|
companySkills,
|
[codex] Improve transient recovery and Codex model refresh (#4383)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Adapter execution and retry classification decide whether agent work
pauses, retries, or recovers automatically
> - Transient provider failures need to be classified precisely so
Paperclip does not convert retryable upstream conditions into false hard
failures
> - At the same time, operators need an up-to-date model list for
Codex-backed agents and prompts should nudge agents toward targeted
verification instead of repo-wide sweeps
> - This pull request tightens transient recovery classification for
Claude and Codex, updates the agent prompt guidance, and adds Codex
model refresh support end-to-end
> - The benefit is better automatic retry behavior plus fresher
operator-facing model configuration
## What Changed
- added Codex usage-limit retry-window parsing and Claude extra-usage
transient classification
- normalized the heartbeat transient-recovery contract across adapter
executions and heartbeat scheduling
- documented that deferred comment wakes only reopen completed issues
for human/comment-reopen interactions, while system follow-ups leave
closed work closed
- updated adapter-utils prompt guidance to prefer targeted verification
- added Codex model refresh support in the server route, registry,
shared types, and agent config form
- added adapter/server tests covering the new parsing, retry scheduling,
and model-refresh behavior
## Verification
- `pnpm exec vitest run --project @paperclipai/adapter-utils
packages/adapter-utils/src/server-utils.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-claude-local
packages/adapters/claude-local/src/server/parse.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-codex-local
packages/adapters/codex-local/src/server/parse.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/adapter-model-refresh-routes.test.ts
server/src/__tests__/adapter-models.test.ts
server/src/__tests__/claude-local-execute.test.ts
server/src/__tests__/codex-local-execute.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-retry-scheduling.test.ts`
## Risks
- Moderate behavior risk: retry classification affects whether runs
auto-recover or block, so mistakes here could either suppress needed
retries or over-retry real failures
- Low workflow risk: deferred comment wake reopening is intentionally
scoped to human/comment-reopen interactions so system follow-ups do not
revive completed issues unexpectedly
> 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
- [ ] 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-24 09:40:40 -05:00
|
|
|
documents,
|
2026-03-03 08:45:26 -06:00
|
|
|
} from "@paperclipai/db";
|
2026-03-16 09:25:39 -05:00
|
|
|
import { notFound, unprocessable } from "../errors.js";
|
Add sandbox environment support (#4415)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The environment/runtime layer decides where agent work executes and
how the control plane reaches those runtimes.
> - Today Paperclip can run locally and over SSH, but sandboxed
execution needs a first-class environment model instead of one-off
adapter behavior.
> - We also want sandbox providers to be pluggable so the core does not
hardcode every provider implementation.
> - This branch adds the Sandbox environment path, the provider
contract, and a deterministic fake provider plugin.
> - That required synchronized changes across shared contracts, plugin
SDK surfaces, server runtime orchestration, and the UI
environment/workspace flows.
> - The result is that sandbox execution becomes a core control-plane
capability while keeping provider implementations extensible and
testable.
## What Changed
- Added sandbox runtime support to the environment execution path,
including runtime URL discovery, sandbox execution targeting,
orchestration, and heartbeat integration.
- Added plugin-provider support for sandbox environments so providers
can be supplied via plugins instead of hardcoded server logic.
- Added the fake sandbox provider plugin with deterministic behavior
suitable for local and automated testing.
- Updated shared types, validators, plugin protocol definitions, and SDK
helpers to carry sandbox provider and workspace-runtime contracts across
package boundaries.
- Updated server routes and services so companies can create sandbox
environments, select them for work, and execute work through the sandbox
runtime path.
- Updated the UI environment and workspace surfaces to expose sandbox
environment configuration and selection.
- Added test coverage for sandbox runtime behavior, provider seams,
environment route guards, orchestration, and the fake provider plugin.
## Verification
- Ran locally before the final fixture-only scrub:
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
- Ran locally after the final scrub amend:
- `pnpm vitest run server/src/__tests__/runtime-api.test.ts`
- Reviewer spot checks:
- create a sandbox environment backed by the fake provider plugin
- run work through that environment
- confirm sandbox provider execution does not inherit host secrets
implicitly
## Risks
- This touches shared contracts, plugin SDK plumbing, server runtime
orchestration, and UI environment/workspace flows, so regressions would
likely show up as cross-layer mismatches rather than isolated type
errors.
- Runtime URL discovery and sandbox callback selection are sensitive to
host/bind configuration; if that logic is wrong, sandbox-backed
callbacks may fail even when execution succeeds.
- The fake provider plugin is intentionally deterministic and
test-oriented; future providers may expose capability gaps that this
branch does not yet cover.
## Model Used
- OpenAI Codex coding agent on a GPT-5-class backend in the
Paperclip/Codex harness. Exact backend model ID is not exposed
in-session. Tool-assisted workflow with shell execution, file editing,
git history inspection, 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
- [ ] 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-24 12:15:53 -07:00
|
|
|
import { environmentService } from "./environments.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
|
|
|
|
|
|
|
|
export function companyService(db: Db) {
|
2026-02-23 16:08:10 -06:00
|
|
|
const ISSUE_PREFIX_FALLBACK = "CMP";
|
Add sandbox environment support (#4415)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The environment/runtime layer decides where agent work executes and
how the control plane reaches those runtimes.
> - Today Paperclip can run locally and over SSH, but sandboxed
execution needs a first-class environment model instead of one-off
adapter behavior.
> - We also want sandbox providers to be pluggable so the core does not
hardcode every provider implementation.
> - This branch adds the Sandbox environment path, the provider
contract, and a deterministic fake provider plugin.
> - That required synchronized changes across shared contracts, plugin
SDK surfaces, server runtime orchestration, and the UI
environment/workspace flows.
> - The result is that sandbox execution becomes a core control-plane
capability while keeping provider implementations extensible and
testable.
## What Changed
- Added sandbox runtime support to the environment execution path,
including runtime URL discovery, sandbox execution targeting,
orchestration, and heartbeat integration.
- Added plugin-provider support for sandbox environments so providers
can be supplied via plugins instead of hardcoded server logic.
- Added the fake sandbox provider plugin with deterministic behavior
suitable for local and automated testing.
- Updated shared types, validators, plugin protocol definitions, and SDK
helpers to carry sandbox provider and workspace-runtime contracts across
package boundaries.
- Updated server routes and services so companies can create sandbox
environments, select them for work, and execute work through the sandbox
runtime path.
- Updated the UI environment and workspace surfaces to expose sandbox
environment configuration and selection.
- Added test coverage for sandbox runtime behavior, provider seams,
environment route guards, orchestration, and the fake provider plugin.
## Verification
- Ran locally before the final fixture-only scrub:
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
- Ran locally after the final scrub amend:
- `pnpm vitest run server/src/__tests__/runtime-api.test.ts`
- Reviewer spot checks:
- create a sandbox environment backed by the fake provider plugin
- run work through that environment
- confirm sandbox provider execution does not inherit host secrets
implicitly
## Risks
- This touches shared contracts, plugin SDK plumbing, server runtime
orchestration, and UI environment/workspace flows, so regressions would
likely show up as cross-layer mismatches rather than isolated type
errors.
- Runtime URL discovery and sandbox callback selection are sensitive to
host/bind configuration; if that logic is wrong, sandbox-backed
callbacks may fail even when execution succeeds.
- The fake provider plugin is intentionally deterministic and
test-oriented; future providers may expose capability gaps that this
branch does not yet cover.
## Model Used
- OpenAI Codex coding agent on a GPT-5-class backend in the
Paperclip/Codex harness. Exact backend model ID is not exposed
in-session. Tool-assisted workflow with shell execution, file editing,
git history inspection, 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
- [ ] 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-24 12:15:53 -07:00
|
|
|
const environmentsSvc = environmentService(db);
|
2026-02-23 16:08:10 -06:00
|
|
|
|
2026-03-16 09:25:39 -05:00
|
|
|
const companySelection = {
|
|
|
|
|
id: companies.id,
|
|
|
|
|
name: companies.name,
|
|
|
|
|
description: companies.description,
|
|
|
|
|
status: companies.status,
|
|
|
|
|
issuePrefix: companies.issuePrefix,
|
|
|
|
|
issueCounter: companies.issueCounter,
|
|
|
|
|
budgetMonthlyCents: companies.budgetMonthlyCents,
|
|
|
|
|
spentMonthlyCents: companies.spentMonthlyCents,
|
[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
|
|
|
attachmentMaxBytes: companies.attachmentMaxBytes,
|
2026-03-16 09:25:39 -05:00
|
|
|
requireBoardApprovalForNewAgents: companies.requireBoardApprovalForNewAgents,
|
2026-04-02 09:11:49 -05:00
|
|
|
feedbackDataSharingEnabled: companies.feedbackDataSharingEnabled,
|
|
|
|
|
feedbackDataSharingConsentAt: companies.feedbackDataSharingConsentAt,
|
|
|
|
|
feedbackDataSharingConsentByUserId: companies.feedbackDataSharingConsentByUserId,
|
|
|
|
|
feedbackDataSharingTermsVersion: companies.feedbackDataSharingTermsVersion,
|
2026-03-16 09:25:39 -05:00
|
|
|
brandColor: companies.brandColor,
|
|
|
|
|
logoAssetId: companyLogos.assetId,
|
|
|
|
|
createdAt: companies.createdAt,
|
|
|
|
|
updatedAt: companies.updatedAt,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function enrichCompany<T extends { logoAssetId: string | null }>(company: T) {
|
|
|
|
|
return {
|
|
|
|
|
...company,
|
|
|
|
|
logoUrl: company.logoAssetId ? `/api/assets/${company.logoAssetId}/content` : null,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 15:41:48 -05:00
|
|
|
function currentUtcMonthWindow(now = new Date()) {
|
|
|
|
|
const year = now.getUTCFullYear();
|
|
|
|
|
const month = now.getUTCMonth();
|
|
|
|
|
return {
|
|
|
|
|
start: new Date(Date.UTC(year, month, 1, 0, 0, 0, 0)),
|
|
|
|
|
end: new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0)),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function getMonthlySpendByCompanyIds(
|
|
|
|
|
companyIds: string[],
|
|
|
|
|
database: Pick<Db, "select"> = db,
|
|
|
|
|
) {
|
|
|
|
|
if (companyIds.length === 0) return new Map<string, number>();
|
|
|
|
|
const { start, end } = currentUtcMonthWindow();
|
|
|
|
|
const rows = await database
|
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({
|
|
|
|
|
companyId: costEvents.companyId,
|
|
|
|
|
spentMonthlyCents: sql<number>`coalesce(sum(${costEvents.costCents}), 0)::double precision`,
|
|
|
|
|
})
|
2026-03-16 15:41:48 -05:00
|
|
|
.from(costEvents)
|
|
|
|
|
.where(
|
|
|
|
|
and(
|
|
|
|
|
inArray(costEvents.companyId, companyIds),
|
|
|
|
|
gte(costEvents.occurredAt, start),
|
|
|
|
|
lt(costEvents.occurredAt, end),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.groupBy(costEvents.companyId);
|
|
|
|
|
return new Map(rows.map((row) => [row.companyId, Number(row.spentMonthlyCents ?? 0)]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateCompanySpend<T extends { id: string; spentMonthlyCents: number }>(
|
|
|
|
|
rows: T[],
|
|
|
|
|
database: Pick<Db, "select"> = db,
|
|
|
|
|
) {
|
|
|
|
|
const spendByCompanyId = await getMonthlySpendByCompanyIds(rows.map((row) => row.id), database);
|
|
|
|
|
return rows.map((row) => ({
|
|
|
|
|
...row,
|
|
|
|
|
spentMonthlyCents: spendByCompanyId.get(row.id) ?? 0,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 09:25:39 -05:00
|
|
|
function getCompanyQuery(database: Pick<Db, "select">) {
|
|
|
|
|
return database
|
|
|
|
|
.select(companySelection)
|
|
|
|
|
.from(companies)
|
|
|
|
|
.leftJoin(companyLogos, eq(companyLogos.companyId, companies.id));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-23 16:08:10 -06:00
|
|
|
function deriveIssuePrefixBase(name: string) {
|
|
|
|
|
const normalized = name.toUpperCase().replace(/[^A-Z]/g, "");
|
|
|
|
|
return normalized.slice(0, 3) || ISSUE_PREFIX_FALLBACK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function suffixForAttempt(attempt: number) {
|
|
|
|
|
if (attempt <= 1) return "";
|
|
|
|
|
return "A".repeat(attempt - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isIssuePrefixConflict(error: unknown) {
|
|
|
|
|
const constraint = typeof error === "object" && error !== null && "constraint" in error
|
|
|
|
|
? (error as { constraint?: string }).constraint
|
|
|
|
|
: typeof error === "object" && error !== null && "constraint_name" in error
|
|
|
|
|
? (error as { constraint_name?: string }).constraint_name
|
|
|
|
|
: undefined;
|
|
|
|
|
return typeof error === "object"
|
|
|
|
|
&& error !== null
|
|
|
|
|
&& "code" in error
|
|
|
|
|
&& (error as { code?: string }).code === "23505"
|
|
|
|
|
&& constraint === "companies_issue_prefix_idx";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createCompanyWithUniquePrefix(data: typeof companies.$inferInsert) {
|
|
|
|
|
const base = deriveIssuePrefixBase(data.name);
|
|
|
|
|
let suffix = 1;
|
|
|
|
|
while (suffix < 10000) {
|
|
|
|
|
const candidate = `${base}${suffixForAttempt(suffix)}`;
|
|
|
|
|
try {
|
|
|
|
|
const rows = await db
|
|
|
|
|
.insert(companies)
|
|
|
|
|
.values({ ...data, issuePrefix: candidate })
|
|
|
|
|
.returning();
|
|
|
|
|
return rows[0];
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (!isIssuePrefixConflict(error)) throw error;
|
|
|
|
|
}
|
|
|
|
|
suffix += 1;
|
|
|
|
|
}
|
|
|
|
|
throw new Error("Unable to allocate unique issue prefix");
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
return {
|
2026-03-16 15:41:48 -05:00
|
|
|
list: async () => {
|
|
|
|
|
const rows = await getCompanyQuery(db);
|
|
|
|
|
const hydrated = await hydrateCompanySpend(rows);
|
|
|
|
|
return hydrated.map((row) => enrichCompany(row));
|
|
|
|
|
},
|
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-16 15:41:48 -05:00
|
|
|
getById: async (id: string) => {
|
|
|
|
|
const row = await getCompanyQuery(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
|
|
|
.where(eq(companies.id, id))
|
2026-03-16 15:41:48 -05:00
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) return null;
|
|
|
|
|
const [hydrated] = await hydrateCompanySpend([row], db);
|
|
|
|
|
return enrichCompany(hydrated);
|
|
|
|
|
},
|
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-16 09:25:39 -05:00
|
|
|
create: async (data: typeof companies.$inferInsert) => {
|
|
|
|
|
const created = await createCompanyWithUniquePrefix(data);
|
Add sandbox environment support (#4415)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies.
> - The environment/runtime layer decides where agent work executes and
how the control plane reaches those runtimes.
> - Today Paperclip can run locally and over SSH, but sandboxed
execution needs a first-class environment model instead of one-off
adapter behavior.
> - We also want sandbox providers to be pluggable so the core does not
hardcode every provider implementation.
> - This branch adds the Sandbox environment path, the provider
contract, and a deterministic fake provider plugin.
> - That required synchronized changes across shared contracts, plugin
SDK surfaces, server runtime orchestration, and the UI
environment/workspace flows.
> - The result is that sandbox execution becomes a core control-plane
capability while keeping provider implementations extensible and
testable.
## What Changed
- Added sandbox runtime support to the environment execution path,
including runtime URL discovery, sandbox execution targeting,
orchestration, and heartbeat integration.
- Added plugin-provider support for sandbox environments so providers
can be supplied via plugins instead of hardcoded server logic.
- Added the fake sandbox provider plugin with deterministic behavior
suitable for local and automated testing.
- Updated shared types, validators, plugin protocol definitions, and SDK
helpers to carry sandbox provider and workspace-runtime contracts across
package boundaries.
- Updated server routes and services so companies can create sandbox
environments, select them for work, and execute work through the sandbox
runtime path.
- Updated the UI environment and workspace surfaces to expose sandbox
environment configuration and selection.
- Added test coverage for sandbox runtime behavior, provider seams,
environment route guards, orchestration, and the fake provider plugin.
## Verification
- Ran locally before the final fixture-only scrub:
- `pnpm -r typecheck`
- `pnpm test:run`
- `pnpm build`
- Ran locally after the final scrub amend:
- `pnpm vitest run server/src/__tests__/runtime-api.test.ts`
- Reviewer spot checks:
- create a sandbox environment backed by the fake provider plugin
- run work through that environment
- confirm sandbox provider execution does not inherit host secrets
implicitly
## Risks
- This touches shared contracts, plugin SDK plumbing, server runtime
orchestration, and UI environment/workspace flows, so regressions would
likely show up as cross-layer mismatches rather than isolated type
errors.
- Runtime URL discovery and sandbox callback selection are sensitive to
host/bind configuration; if that logic is wrong, sandbox-backed
callbacks may fail even when execution succeeds.
- The fake provider plugin is intentionally deterministic and
test-oriented; future providers may expose capability gaps that this
branch does not yet cover.
## Model Used
- OpenAI Codex coding agent on a GPT-5-class backend in the
Paperclip/Codex harness. Exact backend model ID is not exposed
in-session. Tool-assisted workflow with shell execution, file editing,
git history inspection, 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
- [ ] 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-24 12:15:53 -07:00
|
|
|
await environmentsSvc.ensureLocalEnvironment(created.id);
|
2026-03-16 09:25:39 -05:00
|
|
|
const row = await getCompanyQuery(db)
|
|
|
|
|
.where(eq(companies.id, created.id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!row) throw notFound("Company not found after creation");
|
2026-03-16 15:41:48 -05:00
|
|
|
const [hydrated] = await hydrateCompanySpend([row], db);
|
|
|
|
|
return enrichCompany(hydrated);
|
2026-03-16 09:25:39 -05: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-03-16 09:25:39 -05:00
|
|
|
update: (
|
|
|
|
|
id: string,
|
|
|
|
|
data: Partial<typeof companies.$inferInsert> & { logoAssetId?: string | null },
|
|
|
|
|
) =>
|
|
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
const existing = await getCompanyQuery(tx)
|
|
|
|
|
.where(eq(companies.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!existing) return null;
|
|
|
|
|
|
|
|
|
|
const { logoAssetId, ...companyPatch } = data;
|
|
|
|
|
|
|
|
|
|
if (logoAssetId !== undefined && logoAssetId !== null) {
|
|
|
|
|
const nextLogoAsset = await tx
|
|
|
|
|
.select({ id: assets.id, companyId: assets.companyId })
|
|
|
|
|
.from(assets)
|
|
|
|
|
.where(eq(assets.id, logoAssetId))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!nextLogoAsset) throw notFound("Logo asset not found");
|
|
|
|
|
if (nextLogoAsset.companyId !== existing.id) {
|
|
|
|
|
throw unprocessable("Logo asset must belong to the same company");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updated = await tx
|
|
|
|
|
.update(companies)
|
|
|
|
|
.set({ ...companyPatch, updatedAt: new Date() })
|
|
|
|
|
.where(eq(companies.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!updated) return null;
|
|
|
|
|
|
|
|
|
|
if (logoAssetId === null) {
|
|
|
|
|
await tx.delete(companyLogos).where(eq(companyLogos.companyId, id));
|
|
|
|
|
} else if (logoAssetId !== undefined) {
|
|
|
|
|
await tx
|
|
|
|
|
.insert(companyLogos)
|
|
|
|
|
.values({
|
|
|
|
|
companyId: id,
|
|
|
|
|
assetId: logoAssetId,
|
|
|
|
|
})
|
|
|
|
|
.onConflictDoUpdate({
|
|
|
|
|
target: companyLogos.companyId,
|
|
|
|
|
set: {
|
|
|
|
|
assetId: logoAssetId,
|
|
|
|
|
updatedAt: new Date(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (logoAssetId !== undefined && existing.logoAssetId && existing.logoAssetId !== logoAssetId) {
|
|
|
|
|
await tx.delete(assets).where(eq(assets.id, existing.logoAssetId));
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-16 15:41:48 -05:00
|
|
|
const [hydrated] = await hydrateCompanySpend([{
|
2026-03-16 09:25:39 -05:00
|
|
|
...updated,
|
|
|
|
|
logoAssetId: logoAssetId === undefined ? existing.logoAssetId : logoAssetId,
|
2026-03-16 15:41:48 -05:00
|
|
|
}], tx);
|
|
|
|
|
|
|
|
|
|
return enrichCompany(hydrated);
|
2026-03-16 09:25:39 -05: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
|
|
|
|
|
|
|
|
archive: (id: string) =>
|
2026-03-16 09:25:39 -05:00
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
const updated = await tx
|
|
|
|
|
.update(companies)
|
|
|
|
|
.set({ status: "archived", updatedAt: new Date() })
|
|
|
|
|
.where(eq(companies.id, id))
|
|
|
|
|
.returning()
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
|
|
|
|
if (!updated) return null;
|
|
|
|
|
const row = await getCompanyQuery(tx)
|
|
|
|
|
.where(eq(companies.id, id))
|
|
|
|
|
.then((rows) => rows[0] ?? null);
|
2026-03-16 15:41:48 -05:00
|
|
|
if (!row) return null;
|
|
|
|
|
const [hydrated] = await hydrateCompanySpend([row], tx);
|
|
|
|
|
return enrichCompany(hydrated);
|
2026-03-16 09:25:39 -05:00
|
|
|
}),
|
2026-02-17 20:14:05 -06:00
|
|
|
|
|
|
|
|
remove: (id: string) =>
|
|
|
|
|
db.transaction(async (tx) => {
|
|
|
|
|
// Delete from child tables in dependency order
|
|
|
|
|
await tx.delete(heartbeatRunEvents).where(eq(heartbeatRunEvents.companyId, id));
|
2026-02-19 14:02:17 -06:00
|
|
|
await tx.delete(agentTaskSessions).where(eq(agentTaskSessions.companyId, id));
|
2026-04-07 18:20:35 -05:00
|
|
|
await tx.delete(activityLog).where(eq(activityLog.companyId, id));
|
2026-02-17 20:14:05 -06:00
|
|
|
await tx.delete(heartbeatRuns).where(eq(heartbeatRuns.companyId, id));
|
|
|
|
|
await tx.delete(agentWakeupRequests).where(eq(agentWakeupRequests.companyId, id));
|
|
|
|
|
await tx.delete(agentApiKeys).where(eq(agentApiKeys.companyId, id));
|
|
|
|
|
await tx.delete(agentRuntimeState).where(eq(agentRuntimeState.companyId, id));
|
|
|
|
|
await tx.delete(issueComments).where(eq(issueComments.companyId, id));
|
|
|
|
|
await tx.delete(costEvents).where(eq(costEvents.companyId, id));
|
2026-03-14 22:00:12 -05:00
|
|
|
await tx.delete(financeEvents).where(eq(financeEvents.companyId, id));
|
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
|
|
|
await tx.delete(approvalComments).where(eq(approvalComments.companyId, id));
|
2026-02-17 20:14:05 -06:00
|
|
|
await tx.delete(approvals).where(eq(approvals.companyId, id));
|
2026-02-19 15:43:52 -06:00
|
|
|
await tx.delete(companySecrets).where(eq(companySecrets.companyId, id));
|
2026-02-23 14:40:32 -06:00
|
|
|
await tx.delete(joinRequests).where(eq(joinRequests.companyId, id));
|
|
|
|
|
await tx.delete(invites).where(eq(invites.companyId, id));
|
|
|
|
|
await tx.delete(principalPermissionGrants).where(eq(principalPermissionGrants.companyId, id));
|
|
|
|
|
await tx.delete(companyMemberships).where(eq(companyMemberships.companyId, id));
|
2026-04-07 18:20:35 -05:00
|
|
|
await tx.delete(companySkills).where(eq(companySkills.companyId, id));
|
|
|
|
|
await tx.delete(issueReadStates).where(eq(issueReadStates.companyId, id));
|
[codex] Improve transient recovery and Codex model refresh (#4383)
## Thinking Path
> - Paperclip orchestrates AI agents for zero-human companies
> - Adapter execution and retry classification decide whether agent work
pauses, retries, or recovers automatically
> - Transient provider failures need to be classified precisely so
Paperclip does not convert retryable upstream conditions into false hard
failures
> - At the same time, operators need an up-to-date model list for
Codex-backed agents and prompts should nudge agents toward targeted
verification instead of repo-wide sweeps
> - This pull request tightens transient recovery classification for
Claude and Codex, updates the agent prompt guidance, and adds Codex
model refresh support end-to-end
> - The benefit is better automatic retry behavior plus fresher
operator-facing model configuration
## What Changed
- added Codex usage-limit retry-window parsing and Claude extra-usage
transient classification
- normalized the heartbeat transient-recovery contract across adapter
executions and heartbeat scheduling
- documented that deferred comment wakes only reopen completed issues
for human/comment-reopen interactions, while system follow-ups leave
closed work closed
- updated adapter-utils prompt guidance to prefer targeted verification
- added Codex model refresh support in the server route, registry,
shared types, and agent config form
- added adapter/server tests covering the new parsing, retry scheduling,
and model-refresh behavior
## Verification
- `pnpm exec vitest run --project @paperclipai/adapter-utils
packages/adapter-utils/src/server-utils.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-claude-local
packages/adapters/claude-local/src/server/parse.test.ts`
- `pnpm exec vitest run --project @paperclipai/adapter-codex-local
packages/adapters/codex-local/src/server/parse.test.ts`
- `pnpm exec vitest run --project @paperclipai/server
server/src/__tests__/adapter-model-refresh-routes.test.ts
server/src/__tests__/adapter-models.test.ts
server/src/__tests__/claude-local-execute.test.ts
server/src/__tests__/codex-local-execute.test.ts
server/src/__tests__/heartbeat-process-recovery.test.ts
server/src/__tests__/heartbeat-retry-scheduling.test.ts`
## Risks
- Moderate behavior risk: retry classification affects whether runs
auto-recover or block, so mistakes here could either suppress needed
retries or over-retry real failures
- Low workflow risk: deferred comment wake reopening is intentionally
scoped to human/comment-reopen interactions so system follow-ups do not
revive completed issues unexpectedly
> 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
- [ ] 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-24 09:40:40 -05:00
|
|
|
await tx.delete(documents).where(eq(documents.companyId, id));
|
2026-02-17 20:14:05 -06:00
|
|
|
await tx.delete(issues).where(eq(issues.companyId, id));
|
2026-03-16 09:25:39 -05:00
|
|
|
await tx.delete(companyLogos).where(eq(companyLogos.companyId, id));
|
|
|
|
|
await tx.delete(assets).where(eq(assets.companyId, id));
|
2026-02-17 20:14:05 -06:00
|
|
|
await tx.delete(goals).where(eq(goals.companyId, id));
|
|
|
|
|
await tx.delete(projects).where(eq(projects.companyId, id));
|
|
|
|
|
await tx.delete(agents).where(eq(agents.companyId, id));
|
|
|
|
|
const rows = await tx
|
|
|
|
|
.delete(companies)
|
|
|
|
|
.where(eq(companies.id, id))
|
|
|
|
|
.returning();
|
|
|
|
|
return rows[0] ?? null;
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
stats: () =>
|
|
|
|
|
Promise.all([
|
|
|
|
|
db
|
|
|
|
|
.select({ companyId: agents.companyId, count: count() })
|
|
|
|
|
.from(agents)
|
|
|
|
|
.groupBy(agents.companyId),
|
|
|
|
|
db
|
|
|
|
|
.select({ companyId: issues.companyId, count: count() })
|
|
|
|
|
.from(issues)
|
|
|
|
|
.groupBy(issues.companyId),
|
|
|
|
|
]).then(([agentRows, issueRows]) => {
|
|
|
|
|
const result: Record<string, { agentCount: number; issueCount: number }> = {};
|
|
|
|
|
for (const row of agentRows) {
|
|
|
|
|
result[row.companyId] = { agentCount: row.count, issueCount: 0 };
|
|
|
|
|
}
|
|
|
|
|
for (const row of issueRows) {
|
|
|
|
|
if (result[row.companyId]) {
|
|
|
|
|
result[row.companyId].issueCount = row.count;
|
|
|
|
|
} else {
|
|
|
|
|
result[row.companyId] = { agentCount: 0, issueCount: row.count };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}),
|
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
|
|
|
};
|
|
|
|
|
}
|